
List comprehensions across languages (2007) - tosh
http://langexplr.blogspot.com/2007/02/list-comprehensions-across-languages_18.html
======
tom_mellior
For whatever it's worth, here are Prolog implementations of these examples
(the second one is specific to SWI-Prolog, there is no standardized file
system library, I believe). Programming in pure Prolog is a bit like
programming _only_ in terms of the constraints inside a list comprehension.

These implementations don't actually produce lists but enumerate answers one
by one, using backtracking. If you don't need all the answers, you just stop
the enumeration whenever you like, and later elements are not computed. This
is similar to lazy evaluation, and also works if there is an infinite sequence
of answers. If you do want a list of all (finitely many) solutions, you would
use one of several standard predicates (findall/3, setof/3, bagof/3) on the
backtracking version. But this is needed less often than beginners think.

    
    
        list_evenmember(List, EvenMember) :-
            member(EvenMember, List),
            EvenMember mod 2 =:= 0.
    
        directory_minsize_file(Directory, MinSize, File) :-
            directory_files(Directory, Files),
            member(File, Files),
            size_file(File, Size),
            Size > MinSize.
    
        abcd(A, B, C, D) :-
            member(A, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
            member(B, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
            member(C, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
            member(D, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
            A =\= 0,
            (1000 * A + 100 * B + 10 * C + D) * 4 =:= 1000 * D + 100 * C + 10 * B + A.

------
pyrale
I have never understood the benefits of making syntactic sugar for lists in
languages having higher order functions. Python kind-of gets away with it, but
I still don't get the benefits compared to using map and filter.

~~~
tsimionescu
I think a huge benefit is the readability of more complex expressions (e.g.
when you start introducing zip, groupBy, folds etc into the mix).

~~~
11235813213455
If you want to show some code examples of list comprehensions, I'd be glad to
show the equivalent without list-comprehensions in JS (which had a rejected
proposal for list-comphensions
[https://www.reddit.com/r/javascript/comments/5eottt/why_the_...](https://www.reddit.com/r/javascript/comments/5eottt/why_the_hell_did_they_have_to_remove_list/),
no regret though), so we can compare the readability

~~~
omaranto
How about finding triples a<b<c<30 of pairwise relatively prime integers such
that the sum of their cubes is a perfect cube? Here's a list comprehension in
Python (notice the weird syntax Python uses for "let x = y", namely "for x in
[y]" :P):

    
    
        [(a,b,c) for a in range(1,30)
                 for b in range(a+1,30)
                 if gcd(a,b)==1
                 for c in range(b+1,30)
                 if gcd(a,c)==1 and gcd(b,c)==1
                 for d in [int((a**3+b**3+c**3)**(1.0/3)+0.5)]
                 if a**3+b**3+c**3==d**3]
    

I doubt that would look any nicer with higher order functions. Specially if
you want to get the same efficiency: notice how here we test that gcd(a,b)==1
as soon as possible. In general efficient comprehensions involve pushing any
filtering as early as possible, so with higher order functions you get an
alternation of filter with map/flatmap.

Here's a (probably lame) attempt at a Python higher order version:

    
    
        flatmap = lambda f, l: [x for t in l for x in f(t)]
        
        flatmap(lambda a: flatmap(lambda b: flatmap(lambda c: (lambda d: [(a,b,c)] if a**3+b**3+c**3==d**3 else [])(int((a**3+b**3+c**3)**(1.0/3)+0.5)) if gcd(a,c)==1 and gcd(b,c)==1 else [], range(b+1, 30)) if gcd(a,b)==1 else [], range(a+1,30)), range(1,30))
        

Maybe it would look nicer with some filters thrown in instead of abusing
flatmap of empty lists, but it would be longer since you definitely need a
flatmap for a and b, and either a flatmap or map for c.

I'd love to see a cleaner version with higher order function.

~~~
11235813213455
the a:b notation for JS is still in proposal phase
[https://github.com/tc39/proposal-slice-
notation/pull/32](https://github.com/tc39/proposal-slice-notation/pull/32),
but I'll use it below to create ranges

I don't understand your `+0.5`, I've written below the transcription for your
description

    
    
        [...1:30].map(a => 
          [...a+1:30].filter(b => gcd(a,b)==1).map(b => 
            [...b+1:30].filter(c => gcd(a,c)==1
              && gcd(b,c)==1 
              && Number.isInteger((a**3+b**3+c**3)**(1/3))
            .map(c => [a, b, c])
          )
        ).flat(2)
    

Or another equivalent way:

    
    
        [...1:30].flatMap(a => 
          [...a+1:30].filter(b => gcd(a,b)==1).flatMap(b => 
            [...b+1:30].filter(c => gcd(a,c)==1 
              && gcd(b,c)==1 
              && Number.isInteger((a**3+b**3+c**3)**(1/3)))
            .flatMap(c => [[a, b, c]])
    

Or another way (generating first all the triples):

    
    
        [...1:30].flatMap(a => [...a+1:30].flatMap(b => [...b+1:30].flatMap(c => [[a, b, c]])))
          .filter(([a, b, c]) => gcd(a,b)==1 && gcd(a,c)==1
                              && gcd(b,c)==1 && Number.isInteger((a**3+b**3+c**3)**(1/3)))
    

with [https://github.com/tc39/proposal-iterator-
helpers](https://github.com/tc39/proposal-iterator-helpers), which would allow
lazy evaluation, and make more sense than above

    
    
        (1:30).flatMap(a => (a+1:30).flatMap(b => (b+1:30).flatMap(c => [[a, b, c]])))
          .filter(([a, b, c]) => gcd(a,b)==1 && gcd(a,c)==1
                              && gcd(b,c)==1 && Number.isInteger((a**3+b**3+c**3)**(1/3)))
    

without the slice-notation, it's really ugly (hence the proposal in the first
link)

    
    
        Array.from({length: 30},(_,a)=>a+1)
          .flatMap(a=>Array.from({length: 30-a-1},(_,b)=>a+b+1)
            .flatMap(b => Array.from({length: 30-b-1},(_,c)=>b+c+1).flatMap(c => [[a, b, c]])))
        .filter(([a, b, c]) => gcd(a,b)==1 && gcd(a,c)==1
                              && gcd(b,c)==1 && Number.isInteger((a**3+b**3+c**3)**(1/3)))

~~~
omaranto
Now that I think of it, even the flatMap version builds a lot of unnecessary
arrays. The comprehension isn't just more readable, it's also more efficient.
(Unless you're using a fancy compiler like GHC that does deforestation, in
which case the flatMap/filter version should be as efficient as the list
comprehension.)

~~~
11235813213455
Those 2 solutions below, that currently work in JS, are equivalent to the
python solution in terms of complexity

But, yes I'm not satisfied in terms of readability, I linked to a proposal for
iteration helpers, which would do this lazy-evaluation, I don't know if it can
do it deeply like you say, interesting thing to see

    
    
        function gcd(a, b) {
          return b ? gcd(b, a % b) : Math.abs(a);
        }
    
        function isInteger(n) {
          return Math.abs(Math.floor(n + .5) - n) <= Number.EPSILON;
        }
    
        const range = (start, end) => Array.from({length: end-start}, (_,i) => start+i)
    
        console.log(
          range(1, 30)
          .flatMap(a => range(a+1, 30).filter(b => gcd(a,b)==1)
            .flatMap(b => range(b+1, 30).filter(c => gcd(a,c)==1 && gcd(b,c)==1
                      && isInteger((a**3+b**3+c**3)**(1/3)))
              .flatMap(c => [[a, b, c]])
            )
          )
        )
    
        function* tripleSortedRelPrimeCubes(start, end) {
          for (let a=start; a<end; a++) {
            for (let b=a+1; b<end; b++) {
              for (let c=b+1; c<end; c++) {
                if (
                  gcd(a,b)==1
                  && gcd(a,c)==1
                  && gcd(b,c)==1
                  && isInteger((a**3+b**3+c**3)**(1/3))
                )
                  yield [a, b, c];
              }
            }
          }
        }
    
        console.log(
          [...tripleSortedRelPrimeCubes(1, 30)]
        )

------
maweki
What's kinda missing in the whole article is that the member expression is
actually map. The example is {x | generator(x) & filter(x) & filter(x)} while
you could actually {f(x) | generator(x) ...} which is basically map(f,xs). The
shown languages do support expressions in this component.

~~~
a-saleh
I don't think map is sufficient, apart from the trivial example where you bind
a single generator. Even in python, these behave as bind?

Or I didn't understand what you mean :)

~~~
maweki
The generator for X is shown and a filter is shown but not that you can put
any expression for X instead of just X. And this corresponds to map while the
other part, as shown corresponds to filter. So the full power is missing when
you don't put an expression.

------
useerup
pet peeve, but at least the C# examples (and probably others as well) can IMHO
be written more elegantly:

Example 1:

    
    
        static IEnumerable<int> GetEvenNumbers(IEnumerable<int> l) => l.Where(x => x%2 == 0)
    
    

Example 2:

    
    
        static IEnumerable<FileInfo> GetFilesGreaterThan(int size,string directory)
        {
          DirectoryInfo dirInfo = new DirectoryInfo(directory);
          return dirInfo.GetFiles().Where(x => x.Length >= size)
        }
    
    
    

Example 3:

    
    
        static void SolveProblem()
        {
            var results = 
            from a in Enumerable.Range(0,9)
            from b in Enumerable.Range(0,9)
            from c in Enumerable.Range(0,9)
            from d in Enumerable.Range(0,9)
            where (1000*a + 100*b + 10*c + d)*4 == 
                (1000*d + 100*c + 10*b + a) 
                && a != 0
            select new {A=a,B=b,C=c,D=d};
    
            foreach(var r in results) {
                Console.WriteLine($"A={r.A} B={r.B} C={r.C} D={r.D}");
            }
        }
    

One important point is not to confuse List and IEnumerable of .NET. What is
called "lists" in many languages are actually more akin to IEnumerable in .NET

