Hi José,

While the general concept of for-comprehensions is *flat_map*, the inner 
most function usually is not. This is because you usually don't want to 
flatten your values, just the surrounding lists. The previous example will 
throw an *undefined protocol* error if the inner *map *is *flat_map* because 
it will try to flatten the tuple *{i,j}. * 

While I am focusing on *Enum.product *for this first post, it is mostly 
just a precursor to *Stream.product. *This isn't exactly a proposition 
exclusive to Elixir, but a general Stream idea/function that I and a few 
others have worked on. Building heavily on Matthew Szudzik's "elegant" 
version of Cantor's pairing function.

The issue with getting the cartesian product (nested iteration) of Streams 
the default way is that it's unsafe. If the inner Stream doesn't halt, the 
outer Stream doesn't increment. This means that any number of valid Streams 
become invalid due to improper enumeration order. 

natrual_numbers = Stream.unfold(1, fn n -> n + 1 end)

Stream.product(natrual_numbers, natrual_numbers, natrual_numbers) # a, b ,c
|> Stream.filter(fn {a,b,c} -> a*a + b*b == c*c end) # is a right triangle
|> Stream.take_while(fn {a,b,c} -> (a*b)/2 <= max_area end) # has an area 
less than max_area

Using the nieve approach, like how *for* works, you end up just enumerating 
the last enumerable, *[{0,0,1}, {0,0,2}, {0,0,3} .... {0,0,9999999} ...]*. 
Despite trying to create a Stream that will produce every combination of 3 
natrual_numbers, you end up with the same stream you started with. Just two 
0's tacked on the front. 

This means normal product functions are unsafe with Streams. If any Stream 
(other than the outer most loop) doesn't halt, the new Stream has inaccessible 
values. This is the same issues as *concat*-ing two Streams. If the 1st 
doesn't halt, every value in the 2nd is inaccessible. 

But there is a way to create the product of N Streams that always safe, and 
still ordered**. *That's the main point of *Stream.product. *Not only 
adding safety but allowing you to use Streams is a few new ways. 

At this point, I just wanted to focus on the simpler part of this proposal 
to get the syntax nailed down. But if *Enum.product *seems redundant, I 
will move on to the meat of things: *Stream.product. *

On Friday, July 28, 2017 at 12:58:28 AM UTC-7, José Valim wrote:
>
> Hi Burdock,
>
> for-comprehensions map conceptually to flat_map (implementation-wise they 
> use reduce though). So the code above could be written as:
>
>     Enum.flat_map([:a,:b,:c], fn i ->
>       Enum.flat_map([1, 2], fn j ->
>         {i, j}
>       end)
>     end)
>
> The benefit is that it just works with Stream.
>
>
>
> *José Valim*
> www.plataformatec.com.br
> Skype: jv.ptec
> Founder and Director of R&D
>
> On Fri, Jul 28, 2017 at 2:19 AM, Burdock <[email protected] <javascript:>> 
> wrote:
>
>> Hello everyone,
>>
>> One of the most attractive features of list comprehensions is their 
>> ability to avoid nested loops
>> for i <- [:a, :b, :c], j <- [1, 2], do: {i, j} # Happy dev :)
>>
>> flat_map([:a, :b, :c], 
>>   fn i -> 
>>     map([1,2], 
>>       fn j -> 
>>         {i, j} 
>>       end)
>>   end) # Sad dev :(
>>
>> Something that I have wanted for a long time is a way to do the same 
>> thing as list comprehensions, but with the Enum syntax.
>> Enum.product([ [:a,:b,:c], [1,2] ])
>> |> Enum.map( {i, j} -> ... end) # Same tuple syntax as zip
>>
>> This *could* be extended further by adding nested access like *get_in/2* 
>>
>> carts = [
>>     products: [
>>     %{name: "shirt",
>>       coupons: 101, 202
>>     },%{name: "pants",
>>       coupons: 303, 404
>>     }] 
>>   }, %{
>>     products: [
>>     %{name: "T-shirt",
>>       coupons: 101, 202
>>     },%{name: "shorts",
>>       coupons: 303, 404
>>     }] 
>>   }
>> ]
>>
>> Enum.product(carts, [:products, :coupons])
>> |> Enum.map( {cart, product, coupon} -> ... end) # Every Layer of 
>> nesteing is kept available via tuple syntax
>>
>> # Alternative syntax where Enum.product/2 :: left, right -> 
>> Enum.product([left, right]) would work 
>>
>> Enum.product(carts, :products)
>> |> Enum.product(:coupons)
>> |> Enum.map( {cart, product, coupon} -> ... end) # Every Layer of 
>> nesteing is kept available via tuple syntax
>>
>> This is cool and all, but what about Stream.product? Unfortunately, 
>> Streams are not quite as simple. Streams don't always have a finite length.
>> natrual_numbers = Stream.unfold(1, fn n -> n + 1 end)
>> for i <- natrual_numbers, j <- natrual_numbers, do: {i, j} # ????????
>>
>> I don't want to get into too much depth on Stream.product here because 
>> making it work safely with Streams of unknown length requires a somewhat 
>> math heavy explanation. If we get the green light on Enum.product I will 
>> make the suggestion for Stream.product. The only thing that's important 
>> is that it is possible via some higher dimensional pairing function magic :)
>>
>> -- 
>> You received this message because you are subscribed to the Google Groups 
>> "elixir-lang-core" group.
>> To unsubscribe from this group and stop receiving emails from it, send an 
>> email to [email protected] <javascript:>.
>> To view this discussion on the web visit 
>> https://groups.google.com/d/msgid/elixir-lang-core/ef5b1679-d956-4ba2-b18f-1b63d1dcdf3a%40googlegroups.com
>>  
>> <https://groups.google.com/d/msgid/elixir-lang-core/ef5b1679-d956-4ba2-b18f-1b63d1dcdf3a%40googlegroups.com?utm_medium=email&utm_source=footer>
>> .
>> For more options, visit https://groups.google.com/d/optout.
>>
>
>

-- 
You received this message because you are subscribed to the Google Groups 
"elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/elixir-lang-core/8de03786-b714-478b-a791-6f9bb63d2cf4%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to