Proposed new syntax

Chris Angelico rosuav at gmail.com
Mon Aug 14 12:16:22 EDT 2017


On Tue, Aug 15, 2017 at 1:54 AM, Ian Kelly <ian.g.kelly at gmail.com> wrote:
> On Mon, Aug 14, 2017 at 9:40 AM, Chris Angelico <rosuav at gmail.com> wrote:
>> On Tue, Aug 15, 2017 at 1:33 AM, Ian Kelly <ian.g.kelly at gmail.com> wrote:
>>> On Sun, Aug 13, 2017 at 8:36 AM, Steve D'Aprano
>>>> Sure. In Haskell, comprehensions are *implicit* loops, rather than explicit like
>>>> in Python.
>>>
>>> No, they aren't. Haskell list comprehensions use lazy evaluation.
>>> Here's an example of an infinite list comprehension:
>>>
>>> Prelude> let squares = [ x ** 2 | x <- [1 ..] ] :: [Float]
>>> Prelude> :print squares
>>> squares = (_t1::[Float])
>>>
>>> You might say that this is more like a generator expression but the
>>> result is in fact a list. We can evaluate the first four elements:
>>>
>>> Prelude> print $ take 4 squares
>>> [1.0,4.0,9.0,16.0]
>>>
>>> And then see that these have been lazily evaluated in the list:
>>>
>>> Prelude> :print squares
>>> squares = 1.0 : 4.0 : 9.0 : 16.0 : (_t2::[Float])
>>>
>>> A Haskell list comprehension is not a loop at all.
>>
>> What if you don't take the first four, but instead take just the tenth
>> element? Will the preceding elements be calculated too, or do you have
>> a sparse list? If the latter, it's not really comparable to a Python
>> list, but to some sort of cached mapping from input values to output
>> values, which in Python I would implement as a dict with a __missing__
>> method. And if the former, well, that's still going to have a loop,
>> and it's definitely like a genexp, but genexps have more functionality
>> than they do in Python.
>
> The answer is, it depends. If it can avoid evaluating the earlier
> elements it will:
>
> Prelude> squares !! 10
> 121.0
> Prelude> :print squares
> squares = 1.0 : 4.0 : 9.0 : 16.0 : (_t3::Float) : (_t4::Float) :
>           (_t5::Float) : (_t6::Float) : (_t7::Float) : (_t8::Float) : 121.0 :
>           (_t9::[Float])
>
> But sometimes it can't:
>
> Prelude> let triangle = [ (i,j) | i <- [1..], j <- [1..i*i] ]
> Prelude> triangle !! 10
> (3,6)
> Prelude> :print triangle
> triangle = (1,1) : (2,1) : (2,2) : (2,3) : (2,4) : (3,1) : (3,2) :
>            (3,3) : (3,4) : (3,5) : (3,6) : (_t10::[(Integer, Integer)])

Well, no, it doesn't depend. It evaluates only those elements that get
requested. You just requested all the earlier elements as part of
calculating a triangle. :)

So it's really nothing to do with a Python list, nor a list
comprehension. It's like this lazy map:

class Map(dict):
    def __init__(self, func, *coll):
        self.func = func
        self.coll = coll # Collections, not iterables
    def __missing__(self, key):
        self[key] = self.func(*(coll[key] for coll in self.coll))
        return self[key]

>>> squares = Map(lambda x: x*x, range(10000))
>>> squares[5]
25
>>> squares[1000]
1000000
>>> squares
{5: 25, 1000: 1000000}

Except that the indices used are restricted, presumably. to counting
numbers. But still, what you call a lazy list is a function (in the
mathematical sense), but not a true collection.

ChrisA



More information about the Python-list mailing list