[Python-ideas] How do you think about these language extensions?(Thautwarm)

Chris Barker chris.barker at noaa.gov
Mon Aug 21 18:44:52 EDT 2017


On Sat, Aug 19, 2017 at 3:34 AM, ?? ? <twshere at outlook.com> wrote:

> Could you please think this way to define a variable:
>
> >> var = expr() -> g1(_) if f(_) else g2(_)
>
> which equals
>
> >> test = f(expr())
> >> var  = g1(test) if f(test) else g2(test)
>

OK, I do see this as a nice way to avoid as many "temp" variables, though
in this example, I am confused:

in the above version, It seems to me that the equivelent wordy version is:

temp = expr()
var = g1(temp) if f(temp) else g(temp)

rather than the f(expr()) -- i.e. you seem to have called f() on expr and
extra time? Maybe just a typo.

or, of course:

var = g1(expr()) if f(expr()) else g(expr())

which I can see would be bad if expr() is expensive (or even worse, has
side effects)

so I'm coming around this this, though you could currently write that as:

_ = expr(); g1(_) if f(_) else g(_)

not that different!

Also, there is something to be said for giving a name to expr() -- it may
make the code more readable.


In another words,I can clearly state what I mean to say in order of my
> thinking.
>

well, in the above case, particularly if you use a meaningful name, rather
than "temp" or "test", then you are still writing in in the order of
meaning.

(though elsewhere in this thread there are better examples of how the
current nested function call syntax does reverse the logical order of
operations)


> For example,
>
> >> lambda x: f(g(x)) -> map(_, range(100))
>
> The codes above means that I'm stressing on what(an action) I'm going to
> do on an object "range(100)".
>

This is still putting the range(100) at the end of the expression, rather
than making it clear that you are starting with it.

and putting that much logic in a lambda can be confusing -- in fact, I'm
still not sure what that does! (I guess I am still not sure of the order of
operations is the lambda expression (f(g(x))) or the whole thing? if not
the whole thing, then:

is it the same as ?:

(f(g(x)) for x in range(100))

I'm also seeing a nested function there -- f(g(x)) which is what I thought
you were trying to avoid -- maybe:

lambda x: (g(x) -> f(_)) -> map(_, range(100))

???

In general, much of this seems to be trying to make map cleaner or more
clear -- but python has comprehensions, which so far work better, and are
more compact and clear for the examples you have provided.

granted, deeply nested comprehensions can be pretty ugly -- maybe this will
be clearer for those??


However, sometimes the actions are not important, so if we want to stress
> on what we're going to do something on, we write this codes:
>
> >> range(100) -> map( lambda x:f(g(x)), _ )
>

OK, so THAT makes more sense to me  -- start with the "source data", then
go to the action on it.

but again, is that really clearer than the comprehension (generator
expression - why don't we call that a generator comprehension?):

(f(g(x)) for x in range(100))

 maybe this would be better:

range(100) -> (f(g(x)) for x in _)

it does put the source data up front -- and could be nicer for nested
comprehensions.

Hmm, maybe this is an example of the kind of thing I've needed to do is
illustrative:


[s.upper() for s in
        (s.replace('"','') for s in
        (s.strip() for s in
        line.split()))]

would be better as:

line.split() -> (s.strip() for s in _) -> (s.replace('"','') for s in _) ->
 [s.upper() for s in _]

though, actually, really best as:

[s.strip().replace('"','').upper() for s in line.split()]

(which only works for methods, not general functions)

but for functions:

[fun3(fun2(fun1(x))) for x in an_iterable]


so, backwards logic, but that's it for the benefit.

So still having a hard time comeing up with an example that's notable
better...

>> someone -> dosomething( _, options=options) \
>            -> is_meeting_some_conditions( _ )  \
>            -> result1() if _ else result2()    where:
> options = ...
> result1 = lambda: ...
> result2 = lambda: ...
> def dosomething(obj, options) -> Any:
> ...
>
> def is_meeting_some_conditions( event : Any ) -> bool :
> ...
>

again with the lambdas -- this is all making me think that this is about
making Python a better functional language, which I'm not sure is a goal of
Python...

but anyway, the real extra there is the where: clause

But that seems to be doing the opposite -- putting the definitions of what
you are actually doing AFTER the logic>

I'm going to chain all this logic together
and by the way, this is what that logic is...

If we really  wanted to have a kind of context like that, maybe something
more like a context manager on the fly:

with:
    options = ...
    result1 = lambda: ...
    result2 = lambda: ...
    def dosomething(obj, options) -> Any:
        ...

    def is_meeting_some_conditions( event : Any ) -> bool :
        ...
do:
    (result1() if is_meeting_some_conditions(
                 dosomething( someone, options=options))
         else result2()

> Also,  we need to remember that functions can take *args, **kwargs, etc,
> > and can return a tuple of just about anything -- not sure how well that
> > maps to the "pipe" model.
>
> I think that using "pipe" model cannot be the right choice.
>
> We don't need to worry about this problem if we use the grammar I've
> implemented yet :)
>
>   >> (lambda x: (x%5, x) ) ->  max( range(99), key = _)
>   >> 94
>
>   >> def max_from_seq(*args): return max(args)
>   >> [1,2,3] -> max_from_seq(*_)
>   >> 3
>

this gets uglier if we have both *args and **kwargs.....

Which maybe is OK -- don't use it with complex structures like that.

 For example, sometimes we just need to know that surface area of a
> cylinder is
>
>    2*S_top + S_side
>
>  If someone see the codes, he may not need to know how S_top and S_side
> are evaluated,getting
>  a knowledge of what it means to is enough.
>  And if you want to get more about how to evaluate S_side and S_top, just
> see
>  the next "where syntax" and find the answers.
>

how is that clearer than:

S_topo = something
S_side = something else
surface_area = 2*S_top + S_side

???
(Or, of course, defining a function)

Sure, we see the: some expression..."where" some definitions structure a
lot in technical papers, but frankly:

I'd probably rather see the definitions first

and/or

the definitions are often only there to support you if you don't already
know the nomenclature -- when you go back to read the paper again, you may
not need the where. Coding is different, I'd rather see stuff defined
BEFORE it is used.


>> Here is an example to use flowpython, which gives the permutations of a
> sequence.
> >>
> >>     from copy import deepcopy
> >>     permutations = .seq -> seq_seq where:
> >>         condic+[] seq:
> >>             case (a,  ) => seq_seq = [a,]
> >>             case (a, b) => seq_seq = [[a,b],[b,a]]
> >>             case (a,*b) =>
> >>                 seq_seq = permutations(b) -> map(.x -> insertAll(x, a),
>  _) -> sum(_, []) where:
> >>                      insertAll = . x, a -> ret where:
> >>                          ret = [ deepcopy(x) -> _.insert(i, a) or _ for
> i in  (len(x) -> range(_+1))  ]
>
> > I find that almost unreadable.
>

me too.


> Too many new features all at once, it's
> > like trying to read a completely unfamiliar language.
>

exactly -- this seems to be an effort to make Python a different language!

This algorithm can be fixed a little because the second case is redundant.
> And here is the regular Python codes transformed
> from the codes above.
>

looks like we lost indenting, so I'm going to try to fix that:

from copy import deepcopy

def permutations(seq):
    try:
        # the first case
        (a, ) = seq
        return [a ,]
    except:
        try:
            # the third case (the second case is redundant)
            def insertAll(x, a):
                # insertAll([1,2,3], 0) -> [[0, 1, 2, 3], [1, 0, 2, 3], [1,
2, 0, 3], [1, 2, 3, 0]]
                ret = []
                for i in range( len(x) + 1 ):
                    tmp = deepcopy(x)
                    tmp.insert(i, a)
                    ret.append(tmp)
                return ret

            (a, *b) = seq
            tmp = permutations(b)
            tmp = map(lambda x : insertAll(x, a) , tmp)

            return sum(tmp, []) # sum([[1,2,3], [-1,-2,-3]], []) ->
[1,2,3,-1,-2,-3]
        except:
            # no otherwise!
            pass

Have I got that right? but anyway, there has GOT to be a more pythonic way
to write that! And I say that because this feels to me like trying to write
functional code in Python in an unnatural-for-python way, then saying we
need to add features to python to make that natural.

SoL I think the challenge is:

find some nice compeling examples
write them in a nice pythonic way
show us that that these new features would allow a cleaner, more readable
solution.

Steven did have a nice example of that:

result = (myfile.readlines()
                 -> map(str.strip)
                 -> filter( lambda s: not s.startwith('#') )
                 -> sorted
                 -> collapse  # collapse runs of identical lines
                 -> extract_dates
                 -> map(date_to_seconds)
                 -> min
                 )

Though IIUC, the proposal would make that:

result = (myfile.readlines()
                 -> map(str.strip, _)
                 -> filter( lambda s: not s.startwith('#'), _ )
                 -> sorted( _ )
                 -> collapse( _ )  # collapse runs of identical lines
                 -> extract_dates( _ )
                 -> map(date_to_seconds, _)
                 -> min(_)
                 )


The current Python for that might be:

result = min((date_to_seconds(d) for d in
                extract_dates(
                  collapse(
                    sorted([s for s in
                      (s.strip() for line in myfile.readlines)
                      if not s.startswith]
                      )))))

Which really does make the point that nesting comprehension gets ugly fast!

So "don't do that":

lines = collapse(sorted((l.strip().split("#")[0] for l in
myfile.readlines())))
dates = min((date_to_seconds(extract_date(l)) for l in lines))

or any number of other ways -- clearer, less clear??

-CHB

-- 

Christopher Barker, Ph.D.
Oceanographer

Emergency Response Division
NOAA/NOS/OR&R            (206) 526-6959   voice
7600 Sand Point Way NE   (206) 526-6329   fax
Seattle, WA  98115       (206) 526-6317   main reception

Chris.Barker at noaa.gov
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20170821/6ee067ab/attachment-0001.html>


More information about the Python-ideas mailing list