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.

Reply via email to