To give some more context to 'exploring a new protocol + API in the context in a library':

As Kurtis Rainbolt-Greene also mentioned: (A datatype supporting this) structure-preserving transformation operation is known as an `(Endo)functor <https://en.wikipedia.org/wiki/Functor_(functional_programming)>` (and in some other places as a `Mappable`).

`Enumerable`, on the other hand, implements the transformation operation known in other languages as `Foldable <https://en.wikipedia.org/wiki/Fold_(higher-order_function)>` (or, in more maths-heavy contexts, a `Catamorphism <https://en.wikipedia.org/wiki/Catamorphism>`). This transformation is inherently /not/ structure-preserving. And while that means the structure is lost, it also allows it to be implemented by a much wider range of commonly-used datatypes (such as maps, sets, file handles, etc.) that cannot implement a structure-preserving mapping.

It also means that `Enum`/`Stream` play more nicely with `for` expressions, which are built around the same abstraction.

Not everything always turns the result into a list; that is what `Collectable` is for (which is known in some other languages as `Semigroup`/`Monoid <https://en.wikipedia.org/wiki/Monoid>`). Many `Enum`functions and also `for` allow you to specify a `Collectable` directly. But for those who do not, there is no issue with first turning the result into an intermediate list and turning that into a `Collectable` later at the end of a pipeline But it is also always fine to first create a list and only turn it into the desired `Collectable` later using `Enum.into`. This is always possible (because of what lists are, mathematically speaking,) and also usually reasonably efficient.

And for some (but not all) collections, structure is not really lost when converting to a list and back. (Such as maps (when viewed as key-value pairs) and sets).


Does that mean that `Functor`s are never used/never useful in Elixir? No. There are definite use-cases, though not really in Elixir's standard library.
Currently we see them used in two ways in the ecosystem:
1) Certain datastructures (ex: trees, graphs, tensors) really desire a way to transform their elements while preserving their structure. Elixir libraries implementing them often define a specialized `map` or `transform` function. 2) Libraries exist which define a generalized `Functor` protocol. The most well-known is probably Witchcraft <https://hex.pm/packages/witchcraft>. But for many projects a dependency like this might be overkill.


I hope this information can help you to make an informed decision on how to design your Sources/Sinks library. There is quite a bit of prior art out there which you can look at to get started :-)

~Marten/Qqwy

On 16/10/2023 09:36, José Valim wrote:
Enum always returns lists by design, because it was designed to be a single abstraction that can:

1. enumerate elements while skipping
2. support both eager and lazy with the same protocol
3. support halting (such as take)
4. support zipping
5. does not cause dangling resources (so you can work with files)

Many resources and operations above do not work with the concept of returning the same data structure. For example, how do I return the same data-structure for Enum.map(1..10, &:rand.uniform/1)? Or for an io stream? Or for a repo stream?

Not even something as simple as: Enum.map(map, fn {_, v} -> v * 2 end)? So when exactly Enum.map would return a map data structure?

And, as you said, this would be majorly backwards incompatible.

What you want to achieve needs to be done by a new protocol and a new API. So I recommend exploring it within the context of your library. :)

On Mon, Oct 16, 2023 at 9:23 AM Mihai Potra <m...@mpotra.com> wrote:

    The *Enum module* currently in many of its defined functions (i.e.
    /reject/2/) relies on lists and forces /Enumerable/
    implementations to operate and returns lists.

    One example of this is the /Enum.reject/2/ function that operates
    with /Enumerable.reduce/3/ but then it pipes everything into
    /:lists.reverse/1. /This forces all /Enumerable/ implementations
    to return lists so that /Enum.reject/2/ always returns a list.

    Ideally, /Enum/ module would work with any /Enumerable/
    implementation and its function return the Enumerable data type
    that the implementation provides. Having Enum conform to operating
    with Enumerables this way would allow less friction in working
    with enumerables and better support for implementing
    Enumerable/Collectable concepts with structs and data types.

    As an example, removing a key or key-value from a map would be a
    simple /Enum.reject/ call instead of piping the map into the
    Enum.reject/2 and then into a list to map conversion.
    I've stumbled upon this while writing a library that would operate
    with Source and Sinks as enumerables and collectables to allow for
    all sorts of predefined types and custom defined structs, and got
    blocked at abstracting away some of its core features.

    The good news is that finding a solutions might not be /that/
    complicated and I'd be happy to make a PR for it /(already tried
    replacing :lists.reverse/1 with a different function that respects
    the data type returned by Enumerable.reduce/3)/
    /
    /
    *Challenges*
    The biggest challenge here is obviously backwards compatibility
    with code that relies on the Enum functions that return a list to
    keep returning a list, so here's where I'd want a discussion and
    guidance on the best course of action. As far as I can see,
    there's three options:

    *1.* Have a new module written out of `/Enum/` (say /Enum2/) and
    deprecate Enum in the future - a better module name than just
    Enum2 is definitely a must (looking for suggestions). /I suspect
    this would also open the door to simplifying the Stream module./
    /
    /
    *2.* Extend the /Enumerable/ protocol to include a `reverse/1`
    function, *and* a `new/1` function to replace the empty list
    constructor `[]` used by Enum. Need to consider existing user code
    that implements Enumerable as it would break without defining
    these two new functions.

    *3.* Create a new protocol that Enum would use, with fallback to
    current behaviour. Best for supporting current user
    implementations of Enumerable and keeping current behaviour, but
    doesn't make sense on the long term.

    Also as a last decision to be made is how to deal with Map.
    Ideally, Enum functions would also return a Map if the enumerable
    is a map, but many rely on the current behaviour of it returning
    lists because this has been the status quo. This makes me favour
    option 1 of having a new module to replace Enum in the future.

    Looking forward to hearing your thoughts and suggestions/proposals
    for how to best take on this.

    Kind regards,
    Mihai

-- 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 elixir-lang-core+unsubscr...@googlegroups.com.
    To view this discussion on the web visit
    
https://groups.google.com/d/msgid/elixir-lang-core/9d0f6096-43aa-441b-9060-1a4949ff8e67n%40googlegroups.com
    
<https://groups.google.com/d/msgid/elixir-lang-core/9d0f6096-43aa-441b-9060-1a4949ff8e67n%40googlegroups.com?utm_medium=email&utm_source=footer>.

--
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 elixir-lang-core+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/CAGnRm4JXT7V%2BMnk6i2WFzJZbNQ0G23x14WvhdUG_aXPbxohB5Q%40mail.gmail.com <https://groups.google.com/d/msgid/elixir-lang-core/CAGnRm4JXT7V%2BMnk6i2WFzJZbNQ0G23x14WvhdUG_aXPbxohB5Q%40mail.gmail.com?utm_medium=email&utm_source=footer>.

--
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 elixir-lang-core+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/elixir-lang-core/13d9bfb0-c5b3-4c76-bce7-e2caa982ed61%40resilia.nl.

Reply via email to