benjaminschult...@gmail.com good point about `:lists.append/2` being available. But I don't see how you can prepend with `:lists.flatten/2` in a way that's pipe-able. Can you show an example?
On Wed, Jun 4, 2025 at 4:53 PM benjamin...@gmail.com < benjaminschult...@gmail.com> wrote: > You can pipe into append and prepend today with > https://www.erlang.org/doc/apps/stdlib/lists.html#append/2 and > https://www.erlang.org/doc/apps/stdlib/lists.html#flatten/2 > > This feels like more of a documentation issue than something missing in > the stdlib. > > On Monday, June 2, 2025 at 10:17:37 PM UTC+2 william.l...@cargosense.com > wrote: > >> I'm for adding `List` functions that make piping easier and help with >> discoverability. Though I agree they won't often be the best choice for the >> reasons discussed. >> >> I will note that I think there are actually 4 operations that need to be >> considered if the goal is pipe-ergonomics (names TBD): >> >> defmodule List do >> # For a new single element >> def prepend(list, x), do: [x | list] >> def append(list, x), do: list ++ [x] >> # For a new list >> def prepend_list(list, new_list), do: new_list ++ list >> def append_list(list, new_list), do: list ++ new_list >> end >> >> I think a clear distinction between functions that take an element and >> functions that take a list is crucial. In this thread I saw a few instances >> where `x ++ list` was needed but `[x | list]` was used instead, which I >> think highlights that the mistake is easy to make. >> >> As for needing both `prepend_list` and `append_list`, making `++` >> pipe-able is only convenient if both argument orders are available. >> Otherwise, you have to drop back into intermediate variables or `then` to >> get a `prepend_list`. >> >> I'll also note that I'm at like a 6/10 on this feature: I'm on the >> pro-side of the fence but not by a lot. >> >> On Fri, May 30, 2025 at 6:35 PM Christopher Keele <christ...@gmail.com> >> wrote: >> >>> > We do have Enum.concat/2 and typically we don't repeat functions in >>> the Enum module within the List module. >>> >>> I would argue that it wouldn't be a repetition as there would be a >>> material difference between them: List.concat would obey ++ semantics, >>> meaning 1) it would only work with actual lists as a first argument rather >>> than any Enumberable and runtime error otherwise, but hopefully be caught >>> by typing systems; and 2) allow construction of improper lists via non-list >>> non-Enumberables in the second argument. Sometimes that's desired behaviour >>> but it does feel footgunny now that I say it, though. Certainly something >>> that would need to be outlined in the function docs if implemented, it does >>> give me some pause. >>> >>> > My biggest concern is that I don't think piping to prepend leads to >>> easier to read code here, because you have to reverse the order in your >>> head (the order you read the lines is the opposite of the order it will >>> appear in the list). >>> >>> I agree the provided example can be confusing way to model many >>> solutions, and may be an indicator of a need for a simpler refactor. But I >>> think your point is orthogonal to the List.prepend discussion—the same >>> problem exists when chaining [ | ] through intermediate variables. It's a >>> (valid) argument against prepending at all, not against this API. For >>> example, an experienced Elixirist will know they'll need to pipe the result >>> of a recursive defp-function-implemented reduce into a :lists.reverse at >>> the end, regardless of the API they used to prepend along the way—and >>> sometimes piping into a prepend would save noisy intermediate variables >>> along the way. I do not see the availability of List.prepend greatly >>> influencing developers to build backwards lists more often when >>> inappropriate, but we may disagree there. >>> >>> FWIW my usecase is almost never chaining multiple prepends—usually I >>> reach for it as a finisher when map/reducing a series of transforms on a >>> list of dynamic values, and adding a special case/hardcoded value at the >>> end; same with the proposed List.concat. The ergonomics of the pipe >>> operator truly shine in these map/reduces, so it can be painful to have to >>> create an intermediate variable just to use the literal operators to >>> conclude building the desired list. As an example, mapping over a database >>> table to create options for a select, and wanting to prepend the null >>> option that is not modeled in the source data set. >>> >>> >>> On Friday, May 30, 2025 at 4:47:40 PM UTC-5 José Valim wrote: >>> >>>> We do have Enum.concat/2 and typically we don't repeat functions in the >>>> Enum module within the List module. >>>> >>>> My biggest concern is that I don't think piping to prepend leads to >>>> easier to read code here, because you have to reverse the order in your >>>> head (the order you read the lines is the opposite of the order it will >>>> appear in the list) >>>> >>>> Given this code: >>>> >>>> >>>> dto.ledger.accounts >>>> |> Enum.reject(fn %{account_number: acc_number} -> >>>> acc_number in [debit_acc_number, credit_acc_number] >>>> end) >>>> |> then(fn accounts -> [get_ordered_debit_accounts() | accounts]) >>>> |> then(fn accounts -> [get_ordered_credit_accounts() | accounts] >>>> end) >>>> |> then(fn accounts -> accounts ++ retrieve_unordered_accounts() >>>> end) >>>> |> then(&Map.replace(dto.ledger, :accounts, &1)) >>>> >>>> I would rather write (keeping roughly the same structure to make it >>>> easier to compare): >>>> >>>> accounts = >>>> Enum.reject(dto.ledger.accounts, fn %{account_number: >>>> acc_number} -> >>>> >>>> acc_number in [debit_acc_number, credit_acc_number] >>>> end) >>>> >>>> accounts = [get_ordered_credit_accounts(), get_ordered_debit_accounts() >>>> | accounts] >>>> Map.replace(dto.ledger, :accounts, accounts ++ >>>> retrieve_unordered_accounts()) >>>> >>>> Especially if I am calling functions with *ordered* in the name. >>>> >>>> >>>> *José Valimhttps://dashbit.co/ <https://dashbit.co/>* >>>> >>>> >>>> On Fri, May 30, 2025 at 11:34 PM Christopher Keele <christ...@gmail.com> >>>> wrote: >>>> >>>>> Some thoughts in no particular order: >>>>> >>>>> - Generally, the Elixir stdlb does not support multiple ways of doing >>>>> things, especially with operators (see: no Integer.add/2, etc). I >>>>> generally >>>>> like this. >>>>> - The other container that uses a < ... | ... > update syntax (maps) >>>>> does have a dedicated function for this (Map.update). I also like this >>>>> because piping Map operations is so common. >>>>> - After a decade of Elixir I still reach for List.concat and >>>>> List.prepend every few months. I'm intimate with the operators and not an >>>>> overzealous piper, but when piping I still expect it to be there. If I >>>>> wrote Elixir exclusively then I would probably fully adjust. >>>>> >>>>> > So I think that the implementation of this can just perform a >>>>> "remap" of the ++ operator and the [e | list] expression. >>>>> >>>>> I agree, in fact I would implement this as a compile-time inline >>>>> <https://hexdocs.pm/elixir/Kernel.html#module-inlining>. (I believe just >>>>> invoking :erlang >>>>> <https://github.com/elixir-lang/elixir/blob/7b20c281d521aa7aa2ad2baa1e9ae6c579d79d0c/lib/elixir/lib/kernel.ex#L1590> >>>>> is insufficient, IIRC there is something going on in the .erl compiler as >>>>> well to enable this performance characteristic. (Found it—here >>>>> <https://github.com/elixir-lang/elixir/blob/41d1a721ad52e9fbc5f1770b74c3c1c31bccd2d0/lib/elixir/src/elixir_rewrite.erl#L89> >>>>> .)) >>>>> >>>>> > Similarly, it rarely occurred to me to use `[elem | list]`. I >>>>> instead looked for `List.prepend/2`, then `Enum.prepend/2`. When neither >>>>> of >>>>> those existed, I had to resort to elixirforum/slack/stackoverflow/etc to >>>>> lead me to the `[elem | list]` syntax. >>>>> >>>>> One could argue this is working as intended. Preferentially, though, >>>>> you would have first discovered the correct operators by consulting the >>>>> List moduledocs when you could not find the expected function. >>>>> >>>>> However, we could implement the expected functions here to 1) support >>>>> the pipe use-case and 2) have an indexable and discoverable point in the >>>>> documentation to direct folk to the operators, at least for concat and >>>>> prepend. This is my preference. Implementing an append gives us another >>>>> discoverable place to advise against it, so there's an argument there too >>>>> I >>>>> guess. >>>>> ------------------------------ >>>>> I also wonder if it would be possible to somehow attach metadata to >>>>> the list operators so that they show up when searching for the expected >>>>> function equivalent. The current situation is pretty poor for these >>>>> operators. >>>>> >>>>> Today, typeahead for "prepend" shows nothing related. A full search >>>>> for "prepend" eventually shows something almost relevant in 11th place >>>>> (the >>>>> ++ docs telling you when to prefer [ | ]). The List moduledoc instructions >>>>> for [ | ] show up in 15th place. There is no entry for the actual infix >>>>> list concat operator in Kernel or SpecialForms as it is context-dependent. >>>>> Map and tuple literals have entries in SpecialForms (%{} and {} >>>>> respectively), but there is no similar entry for list literals ([]). >>>>> >>>>> Similarly, there are no related results for ++ when searching >>>>> "concat". "concatenation" brings up the relevant operator in 10th place. >>>>> Neither turn up anything related in the typeahead. >>>>> ------------------------------ >>>>> Overall, I'm pro List.prepend/2 and List.concat/2. Documentation >>>>> search aside, I think the friction of discovering the operators could be >>>>> reduced by mentioning them in dedicated function docs, and alongside the >>>>> pipe-usecase and parallels to the Map APIs, there is a sufficiently >>>>> compelling case to be made to abandon the one-way-to-do-it principle here. >>>>> >>>>> Conversely, reaching for List.append/2 should produce some form of >>>>> friction and lead the programmer through a learning experience. Whether or >>>>> not the "resort to elixirforum/slack/stackoverflow/etc" experience is a >>>>> productive sort of friction, I don't know. >>>>> >>>>> I think we should investigate improving the hexdocs situation for >>>>> these operators regardless. >>>>> >>>>> Finally, if these are implemented as inlined-at-compile-time to their >>>>> operator forms, it occurs to me we could have the formatter auto-correct >>>>> non-pipe usage to their operator forms. I'm a little wary of that, but >>>>> could be convinced otherwise. >>>>> On Tuesday, May 27, 2025 at 5:16:30 PM UTC-5 Dallin Osmun wrote: >>>>> >>>>>> This was a hurdle for me when I first started coding in Elixir. >>>>>> Whenever I wanted to update a data structure, I'd look through the >>>>>> standard >>>>>> library for the appropriate function. >>>>>> >>>>>> Instead of using `list1 ++ list2` I looked for `List.concat/2`. When >>>>>> that didn't exist I'd look for and find `Enum.concat/2`. Similarly, it >>>>>> rarely occurred to me to use `[elem | list]`. I instead looked for >>>>>> `List.prepend/2`, then `Enum.prepend/2`. When neither of those existed, I >>>>>> had to resort to elixirforum/slack/stackoverflow/etc to lead me to the >>>>>> `[elem | list]` syntax. >>>>>> >>>>>> All that to say, I'm torn on this proposal. On the one hand it could >>>>>> help those new to elixir get un-stuck faster. On the other hand, those >>>>>> same >>>>>> coders wouldn't be pushed to learn the `[a | b]` syntax. >>>>>> >>>>>> On Saturday, May 24, 2025 at 1:21:22 AM UTC-6 sabi...@gmail.com >>>>>> wrote: >>>>>> >>>>>>> My concern is that adding List.append/2 would send the wrong signal, >>>>>>> since it has the wrong performance characteristics. >>>>>>> It is a pattern that people coming from an imperative need to >>>>>>> unlearn so we shouldn't make it more convenient. >>>>>>> We also just deprecated >>>>>>> <https://hexdocs.pm/elixir/changelog.html#4-hard-deprecations> >>>>>>> Tuple.append/2. >>>>>>> >>>>>>> While List.prepend/2 doesn't suffer this issue, I'm not sure the >>>>>>> pipe-ability alone is enough to justify it, esp. since there is a >>>>>>> first-class syntax for prepending: [h | t]. >>>>>>> It feels to me that then/2 gives us the ability to use it with the >>>>>>> pipe if we want to, with the flexibility of choosing what the first >>>>>>> argument is: >>>>>>> >>>>>>> ... |> then(&[elem | &1]) >>>>>>> >>>>>>> ... |> then(&[&1 | list]) >>>>>>> >>>>>>> >>>>>>> Le sam. 17 mai 2025 à 07:24, Almir Neto <almir.a...@gmail.com> a >>>>>>> écrit : >>>>>>> >>>>>>>> Today, if you want to pipe an append or a prepend, you must use >>>>>>>> then/2 to achieve this. This can be useful if the first elements >>>>>>>> of a list must be inserted in order through a pipe. Let me show an >>>>>>>> example: >>>>>>>> >>>>>>>> dto.ledger.accounts >>>>>>>> |> Enum.reject(fn %{account_number: acc_number} -> >>>>>>>> acc_number in [debit_acc_number, credit_acc_number] >>>>>>>> end) >>>>>>>> |> then(fn accounts -> [get_ordered_debit_accounts() | accounts]) >>>>>>>> |> then(fn accounts -> [get_ordered_credit_accounts() | accounts] >>>>>>>> end) >>>>>>>> |> then(fn accounts -> accounts ++ retrieve_unordered_accounts() >>>>>>>> end) >>>>>>>> |> then(&Map.replace(dto.ledger, :accounts, &1)) >>>>>>>> >>>>>>>> Obviously, that one is too simple and can be done in one line >>>>>>>> without losing too much readability, like this: >>>>>>>> >>>>>>>> dto.ledger.accounts >>>>>>>> |> Enum.reject(fn %{account_number: acc_number} -> >>>>>>>> acc_number in [debit_acc_number, credit_acc_number] >>>>>>>> end) >>>>>>>> |> then(fn accounts -> [get_ordered_debit_accounts(), >>>>>>>> get_ordered_credit_accounts() | accounts]) >>>>>>>> |> Kernel.++(retrieve_unordered_accounts()) >>>>>>>> |> then(&Map.replace(dto.ledger, :accounts, &1)) >>>>>>>> >>>>>>>> But it could be cleaner if it could just do this: >>>>>>>> >>>>>>>> dto.ledger.accounts >>>>>>>> |> Enum.reject(fn %{account_number: acc_number} -> >>>>>>>> acc_number in [debit_acc_number, credit_acc_number] >>>>>>>> end) >>>>>>>> |> List.prepend(get_ordered_debit_accounts()) >>>>>>>> |> List.prepend(get_ordered_credit_accounts()) >>>>>>>> |> List.append(retrieve_unordered_accounts()) >>>>>>>> |> then(&Map.replace(dto.ledger, :accounts, &1)) >>>>>>>> >>>>>>>> So I think that the implementation of this can just perform a >>>>>>>> "remap" of the ++ operator and the [e | list] expression. >>>>>>>> >>>>>>>> def append(list, element) when is_list(list), do: list ++ [element] >>>>>>>> def prepend(list, element) when is_list(list), do: [element | list] >>>>>>>> >>>>>>>> >>>>>>>> This feature is more of a syntactic sugar than something >>>>>>>> innovative; it would be a way to keep the code more vertical and >>>>>>>> easier to >>>>>>>> read for some. >>>>>>>> >>>>>>>> *I will be glad to send a PR.* >>>>>>>> >>>>>>>> >>>>>>>> -- >>>>>>>> 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-co...@googlegroups.com. >>>>>>>> To view this discussion visit >>>>>>>> https://groups.google.com/d/msgid/elixir-lang-core/ed3ccb00-5069-4b66-9d6f-132eadc7ea90n%40googlegroups.com >>>>>>>> <https://groups.google.com/d/msgid/elixir-lang-core/ed3ccb00-5069-4b66-9d6f-132eadc7ea90n%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-co...@googlegroups.com. >>>>> >>>> To view this discussion visit >>>>> https://groups.google.com/d/msgid/elixir-lang-core/37c4788d-662d-4de4-98e1-06913db75665n%40googlegroups.com >>>>> <https://groups.google.com/d/msgid/elixir-lang-core/37c4788d-662d-4de4-98e1-06913db75665n%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-co...@googlegroups.com. >>> >> To view this discussion visit >>> https://groups.google.com/d/msgid/elixir-lang-core/ef1c181b-513f-40ac-9f17-24cdfbb453b5n%40googlegroups.com >>> <https://groups.google.com/d/msgid/elixir-lang-core/ef1c181b-513f-40ac-9f17-24cdfbb453b5n%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 visit > https://groups.google.com/d/msgid/elixir-lang-core/db899a13-2a01-4d0a-bd06-881541d51d4bn%40googlegroups.com > <https://groups.google.com/d/msgid/elixir-lang-core/db899a13-2a01-4d0a-bd06-881541d51d4bn%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 visit https://groups.google.com/d/msgid/elixir-lang-core/CABvJisP3WSaMAfNxks_mPdPYdsJrn4eTEo9-uDSuBSSZSdTR9A%40mail.gmail.com.