sudostack

sudostack

Elixir version of a safe navigation operator? (navigating nil in maps/structs)

Having written a lot more Phoenix templates as of late, I’m doing a lot more attribute checks than I would like. In Ruby 2.3 we could use the #try method or the safe-navigation operator &. to navigate possible nested nils. Here, if address was nil, then it shouldn’t try to access city:

Ruby:
user.try(:address).try(:city) OR user&.address&.city as opposed to user && user.address && user.address.city / writing nested if expressions/statements.

Is there a nicer way to navigate nested maps or structs in Elixir, so that code that I write in EEX could be more terse? Or is there a better pattern to not have an exception raised if I’m trying to access a nested field found in a parent field that is nil?

Most Liked

josevalim

josevalim

Creator of Elixir

Pattern matching is the answer. :slight_smile: Instead of:

if user && user.address && user.address.city do
  ...
else
  ...
end

do:

case user do
  %{address: %{city: city}} when is_binary(city) -> ...
  _ -> ...
end
josevalim

josevalim

Creator of Elixir

One year later but the answer to your question is get_in+Access.key. :slight_smile:

michalmuskala

michalmuskala

def, defmacro, and defmodule are already plain macros.

Most people working on the compiler would love to get rid of this too, but it’s a backwards-incompatible change. So we have to live with it until 2.0.
You can easily introduce a new lexical scope by wrapping in try do <code> end.

Furthermore, Elixir has hygienic macros, so it won’t leak between contexts. You’re building the variable AST by hand working really hard to work around the regular (hygienic) macro mechanisms and then complaining it’s not hygienic :wink:
What’s more, you can rebind variables, so it’s perfectly fine to have something like this in reduce:

case unquote(maybe_map) do
  nil -> nil
  map -> Map.get(map, unquote(k), nil)
end

This leads us to yet another realisation that we never bind any variable using = so nothing will ever leak, even with current implementation:

iex(1)> case %{} do
...(1)>   map -> map
...(1)> end
%{}
iex(2)> map
** (CompileError) iex:2: undefined function map/0

This means the whole thing can be simplified to:

defmodule SafeNilGet do
  defmacro sng({var, _meta, ctx} = ast) when is_atom(var) and is_atom(ctx) do
    ast
  end
  defmacro sng({{:., _, [v, k]}, _, []}) when is_atom(k) do
    quote do
      case sng(unquote(v)) do
        nil -> nil
        map -> Map.get(map, unquote(k), nil)
      end
    end
  end
end
iex> import SafeNilGet
SafeNilGet
iex> map = %{blah: %{blorp: %{bleep: 42}}}
%{blah: %{blorp: %{bleep: 42}}}
iex> sng map.blah.blorp.bleep
42
iex> sng map.blah.wrong.bleep
nil
iex> binding()
[map: %{blah: %{blorp: %{bleep: 42}}}]

Last Post!

tomekowal

tomekowal

That makes sense. I was testing on 1.12.3

I like that change a lot because it is more intuitive to me and makes the common problem of safely navigating easier to solve.
I hope it lands in 1.13 even though it is a breaking change for people depending on the BadMapError.

Where Next?

Popular in Questions Top

vegabook
I’m brand new to Phoenix and I have stripped one of the demo applications to the bone. I just want to get an svg up on the screen. Here i...
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
fayddelight
I tried installing elixir 1.11.2 erlang 23.3.4 via asdf in my zsh shell. Enabled the versions locally and globally. When I list them ...
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New

Other popular topics Top

New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
JakeBecker
TL;DR: I’ve just released an implementation of Microsoft’s IDE-independent Language Server Protocol for Elixir. It adds language support ...
1144 54921 245
New
aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID&lt;0.412.0&gt; terminating ** (Postgrex.Error) FATAL...
New

We're in Beta

About us Mission Statement