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.

Reply via email to