Hi Riccardo,
This is an interesting proposal! Unfortunately if the value you want to
compute is expensive, then you need to fallback to the usual `if` approach.
This is also specific to maps. It may be worth considering a more general
purpose mechanism, for example:
map
|> update_if(foo != nil, &Map.put(&1, :foo, foo))
|> update_if(bar != nil, &Map.put(&1, :bar, normalize_bar(bar)))
Being in Kernel also allows us to write it as a macro, which can be further
optimized. Not sure if update_if is the best name though. Suggestions and
comparisons to other languages are welcome.
On Fri, Jun 5, 2020 at 12:36 PM Riccardo Binetti <[email protected]> wrote:
> Hi everybody,
>
> this is my first proposal so I hope I get the format right, I've already
> checked in the mailing list and I haven't found similar proposals.
>
> *Problem*
>
> When manually constructing a map using external options or the result of a
> pattern match on a struct, I often find myself in the situation where I
> have to add a value only if it is non-nil.
>
> The possible approaches available today are these
>
>
> *Using if*
>
>
> def some_fun(s) do
> MyStruct{
> foo: foo,
> bar: bar,
> baz: baz
> } = s
>
> map = %{a: "hello", b: 42}
>
> map =
> if foo do
> Map.put(map, :foo, foo)
> else
> map
> end
>
> map =
> if bar do
> Map.put(map, :bar, bar)
> else
> map
> end
>
> if bar do
> Map.put(map, :baz, baz)
> else
> map
> end
> end
>
>
> This is the obvious solution, but it gets very verbose very quickly
>
>
> *Populating the map and then filtering*
>
> def some_fun(s) do
> MyStruct{
> foo: foo,
> bar: bar,
> baz: baz
> } = s
>
> %{a: "hello", b: 42}
> |> Map.put(:foo, foo)
> |> Map.put(:bar, bar)
> |> Map.put(:baz, baz)
> |> Enum.filter(fn {_k, v} -> v end)
> |> Enum.into(%{})
> end
>
> This is more concise, but it creates intermediate maps that are
> immediately thrown away. Moreover, if the initial map is very large, the
> filter operation is expensive since it traverses the whole map.
>
>
> *Implementing a maybe_put helper function*
>
> def some_fun(s) do
> MyStruct{
> foo: foo,
> bar: bar,
> baz: baz
> } = s
>
> %{a: "hello", b: 42}
> |> maybe_put(:foo, foo)
> |> maybe_put(:bar, bar)
> |> maybe_put(:baz, baz)
> end
>
> defp maybe_put(map, _key, nil), do: map
> defp maybe_put(map, key, value), do: Map.put(map, key, value)
>
> This is the solution I end implementing almost always. It is present in
> multiple modules across the codebase I work on (and/or put in a generic
> Utils module), and talking with my colleagues I found out that is a utility
> function that they also have to implement often and it has turned up at
> least a couple times[1][2] also in the Elixir Forum.
>
> [1]
> https://elixirforum.com/t/elixir-way-to-conditionally-update-a-map/17952/21?u=rbino
> [2]
> https://elixirforum.com/t/is-there-way-to-create-map-and-conditionally-exclude-some-keys/23315/5?u=rbino
>
> This brings me to my proposal, which is a more generic version of
> maybe_put.
>
> *Proposal*
>
> Add Map.put_if/4 to the Map module. This is more generic (and imho
> clearer) than maybe_put and can be used to solve the same problem without
> creating intermediate results and allowing the code to be clear and concise.
>
>
> def some_fun(s) do
> MyStruct{
> foo: foo,
> bar: bar,
> baz: baz
> } = s
>
> %{a: "hello", b: 42}
> |> Map.put_if(:foo, foo, foo != nil)
> |> Map.put_if(:bar, bar, bar != nil)
> |> Map.put_if(:baz, baz, baz != nil)
> end
>
> Let me know if you need any other information.
> --
> Riccardo
>
> --
> 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 [email protected].
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/elixir-lang-core/f513b901-fd93-4f28-88a2-f496a3c84a21o%40googlegroups.com
> <https://groups.google.com/d/msgid/elixir-lang-core/f513b901-fd93-4f28-88a2-f496a3c84a21o%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 [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/elixir-lang-core/CAGnRm4K_QVpD-eRG6JUOMYV5pVTLchpTgbO0fqN6pPCmuSL4NA%40mail.gmail.com.