3
votes

Warning: this is an attempt to be clever. I am aware that I probably should just leave it readable, but that doesn't mean I don't want to know how to be clever :-P

I have something like this (contrived example):

def glue(%{"alpha" => alpha, "beta" => beta}) do
  cond do
    alpha && beta ->
      alpha <> beta
    alpha ->
      IO.puts("oops, you forgot to give me alpha!")
    true ->
      IO.puts("oops, you forgot to give me beta!")
  end
end

but would like to instead have:

def glue(%{"alpha" => alpha, "beta" => beta}) do
  cond do
    alpha && beta ->
      alpha <> beta
    true ->
      something
      |> need_field()
  end
end

defp need_field(something) do
  IO.puts("oops, you forgot to give me " <> something)
end

Is there a clever way to get the name of the empty variable?

I was thinking along the lines of somehow using alpha || beta to get the populated one, then print the name of the other one, but I can't quite seem to get there.

1
You will need to use macros for something like this. - Justin Wood
What are you expecting alpha or beta to be if alpha && beta returns false? Will they be nil, or can they be false? - Steve Pallen
The assumption is that they may be nil, cannot be false. - TrivialCase

1 Answers

3
votes

You have to be careful with matching on map keys. If the map does not contain the key, then your function head will not match. You could do this:

def glue(%{"alpha" => alpha, "beta" => beta} = map) do
  cond do
    alpha && beta ->
      alpha <> beta
    true ->
      need_field(map)
  end
end

defp need_field(map) do
  Enum.each(map, fn 
    {k, nil} -> IO.puts "You forgot to give me #{k}"
    _ -> nil
  end
end

Now, if you expecting the map to be missing the field, then you could add a second function clause:

def glue(map) do
  Enum.each ~w(alpha beta), fn key -> 
    if map[key], do: nil, else: IO.puts("You forgot to give me #{key}")
  end
end

Here is the complete solution if its a map.

def glue(%{"alpha" => alpha, "beta" => beta} = map) do
  cond do
    alpha && beta ->
      alpha <> beta
    true ->
      need_field(map)
  end
end
def glue(map) do
  Enum.each ~w(alpha beta), fn key -> 
    if map[key], do: nil, else: IO.puts("You forgot to give me #{key}")
  end
end

defp need_field(map) do
  Enum.each(map, fn 
    {k, nil} -> IO.puts "You forgot to give me #{k}"
    _ -> nil
  end
end

If the input is a struct, then you don't have to worry about the default clause since a struct has to have all the keys. In this case, you can get the keys from the struct by

map
|> Map.from_struct 
|> Enum.each(...