[Python-ideas] A more readable way to nest functions

zmo z+py+pyideas at m0g.net
Sat Jan 28 09:16:27 EST 2017


Hi list o/

This idea sounds fun, so as a thought experiment why not imagine one
way of integrating it in what I believe would be pythonic enough.

On Sat, Jan 28, 2017 at 12:41:24PM +0000, Ed Kellett wrote:
> FWIW, I'd spell it without the (), so it's simply a right-associative
> binary operator on expressions, (a -> b, a) -> b, rather than magic syntax.
>     print XYZ some_func XYZ another_func("Hello")

I agree this would look a bit more elegant. To focus on the feature of
that operator, instead of how to write it, I'll use XYZ instead of <| in
this post.

So, considering it's decided that the RHS is in charge of filling up all
the arguments of the LHS, how to deal with positional and keyword
arguments without introducing new syntax? Should it be by returning a
tuple of positional iterable and keyword dict? i.e.:

    def fn_a(*args, **kwarg):
        print("args: {}, kwarg: {}".format(args, kwarg))

    def fn_b():
        return (1,2,3), {'a':1, 'b':2, 'c':3}

    fn_a XYZ fn_b()

but then if we pass only positional would the following be ok?

    def fn_b():
        return (1,2,3)

or should it look like this one, it being a tuple, but with the second
part being empty:

    def fn_b():
        return (1,2,3),

so to avoid confusing if we want to pass a dict as second positional
argument of fn_a():

    def fn_b():
        return (1, {'a': 2}),

anyway, I guess it's pretty safe to assume that if fn_b() returns a
scalar, it'll be easy to assume it's just a single positional argument.

That being said, then if the chosen syntax is like the following:

>     print XYZ some_func XYZ another_func("Hello")

and given we decide to apply the rules I'm suggesting above, why not
make this function dumb simple, it being:

 * "take the RHS scalar or tuple, and apply it as arguments to the LHS"

Meaning that the above could also be written as:

    print XYZ some_func XYZ another_func XYZ "Hello"

Then the basic operator definition could be done with a dunder 
looking like:

    def __application__(self, other):
        if isinstance(other, Iterable):
            if (len(other) == 2
               and isinstance(other[0], tuple)
               and isinstance(other[1], dict)):
                return self(*other[0], **other[1])
            elif (len(other) == 1
               and isinstance(other[0], tuple):
                return self(*other[0])
        return self(other)

In practice, such a scheme would make it possible to have:

    print XYZ (("Hello World",), {"file": sys.stderr})

Another thing I'm wondering, should the whole syntax be an
expression? I believe it should, so it fits in python3 logic of
everything — except control statements — is an expression:

    print(fn_a XYZ fn_b(), file=sys.stderr)

But the danger is that it might lead to very long lines:

    print XYZ my_first_function XYZ my_second_function XYZ my_third_function XYZ my_fourth_function

leading to either continuing spaces or wrapping in parenthesis:

    print XYZ my_first_function \
          XYZ my_second_function \
          XYZ my_third_function \
          XYZ my_fourth_function

    (print XYZ my_first_function
           XYZ my_second_function
           XYZ my_third_function
           XYZ my_fourth_function)

but it would then be avoiding the silly stack of closing parenthesis:

    print(my_first_function(
            my_second_function(
              my_third_function(
                my_fourth_function()))))

All in all, it can be a nice syntactic sugar to have which could make it
more flexible working with higher order functions, but it with the way
I'm suggesting to comply with python's arguments handling, it offers
little advantages when the RHS is not filling LHS arguments:

    >>> print(all(map(lambda x: x>2, filter(lambda x: isinstance(x, int), range(0,3)))))
    True

vs

    >>> print XYZ all XYZ map XYZ (lambda x: x>2, filter(lambda x: isinstance(x, int), range(0,3))),
    True

Here, applying map onto all onto print offers a great readability, but
for passing arguments to map, not so much. So the question end up being:
is application of *all* arguments of a function from return value of
another function a common enough pattern to justify a new syntax that
would make it better *only* then?

Or maybe instead of passing a tuple of parameters could we stack
parameters up with the XYZ operator up until a callable is reached, so
that:

    >>> print XYZ all XYZ map XYZ lambda x: x>2 XYZ filter XYZ lambda x: isinstance(x, int) XYZ range(0,3)

But then how can it be told that we want:
    `(lambda x: isinstance(x), range(0,3)` to be fed to `filter`,
and not
    `range(0,3)` to be fed to `lambda x: isinstance(x, int)`?

But then it would be just another way to introduce currying as a
language feature with an operator, so we should then just discuss on how
to add currying as a language syntax "by the book", but I'm pretty sure
that's a topic already discussed before I joined this list ;-)

that was my €0.02

Cheers,

-- 
zmo


More information about the Python-ideas mailing list