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.

Reply via email to