On Fri, Dec 06, 2019 at 09:11:44AM -0400, Juancarlo AƱez wrote:

[...]
> > Sure, but in this case, it isn't a fragment of a larger function, and
> > that's not what it looks like. If it looked like what you wrote, I would
> > understand it. But it doesn't, so I didn't really understand what it was
> > supposed to do, until I read the equivalent version using first/next.
> >
> 
> Exactly my point.

Indeed, and I agree with that. But I still don't see what advantage 
there is to having a `first` builtin which does so little. It's a really 
thin wrapper around `next` that:

    calls iter() on its iterable argument
    supplies a default
    and then calls next() with two arguments

I guess my question is asking you to justify adding a builtin rather 
than just educating people how to use next effectively.

This is how I would implement the function in Python:

    def first(iterable, default=None):
        return next(iter(iterable), default)


But there's a major difference in behaviour depending on your input, and 
one which is surely going to lead to bugs from people who didn't realise 
that iterator arguments and iterable arguments will behave differently:

    # non-iterator iterable
    py> obj = [1, 2, 3, 4]
    py> [first(obj) for __ in range(5)]
    [1, 1, 1, 1, 1]

    # iterator
    py> obj = iter([1, 2, 3, 4])
    py> [first(obj) for __ in range(5)]
    [1, 2, 3, 4, None]


We could document the difference in behaviour, but it will still bite 
people and surprise them.

We could, I guess, eliminate the difference by adding the ability to 
peek ahead to the next value of an arbitrary iterator without consuming 
that value. This would have to be done by the interpreter, not in Python 
code, and it would open new complexities.

Consider a generator which yields values which depend, in some complex 
and unpredicatable way, on *when* it is called. Say, the amount of disk 
space available, or the number of records in a database, or the current 
time. If I peek into the generator, I would see the time at the moment I 
peeked:

    now = get_current_time_generator()
    peek(now)  # returns 11:25:30am

Since peek can't literally see into the future, it cannot be otherwise.

But what happens when I call next?

    time.sleep(60)
    next(now)  # what will this return?

There are two alternatives:

1. `next(now)` will return 11:25:30am, the same value that peek gave;

2. `next(now)` will return 11:27:30am, the current time. 

Option 1 keeps the invariant that peeking will show you the next value 
without advancing the iterable, but it violates the invariant that 
`next(now)` yields the current time.

Option 2 keeps the `next` invariant, but violates the `peek` invariant.

Whichever option we choose, peeking into arbitrary iterators will break 
somebody's expectations.

Bringing it back to `first`:


* It seems to me that `first` adds very little that `next` doesn't 
  already give us.

* The simple and obvious implementation of `first` would have a
  troublesome difference in behaviour between iterator arguments
  and non-iterator arguments.

* To eliminate that difference would require the ability to peek 
  ahead into arbitrary iterators, including generators, which is
  a much bigger change, and equally troublesome.


-- 
Steven
_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at 
https://mail.python.org/archives/list/python-ideas@python.org/message/REYDJFCXQNQG4SAWKELQMCGM77IZG47Q/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to