And we can’t forget about https://hexdocs.pm/elixir/List.html#insert_at/3 On Thursday, June 5, 2025 at 9:26:16 AM UTC+2 benjamin...@gmail.com wrote:
> [el|list] is pipeable as well, and if you are working with Erlang > strings/binaries you can use > https://www.erlang.org/doc/apps/stdlib/string.html#pad/4 > > I think it’s important not to stare us self blind on a specific syntax. > All the functions there are currently available helps highlighting that the > lists we have in the BEAM are double linked list. > > When I first read this thread all I could think about was that it could > create more confusion for new developers who might believe that our lists > are arrays. > > Although you can easily write your own macro or functions to deal with > this, I don’t see the value in adding it to stdlib at this point. However, > I do see value in expanding the documentation with examples on what to do > in situations like these as they are excellent cases. > > On Wednesday, June 4, 2025 at 11:15:47 PM UTC+2 > william.l...@cargosense.com wrote: > >> benjamin...@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 < >> benjamin...@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-co...@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/97613496-335d-4a8b-b0bc-d34b65bdfb82n%40googlegroups.com.