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.