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}}}]
gregvaughn

gregvaughn

michalmuskala

michalmuskala

This is totally how Erlang works:

1> case #{} of
1>   Map ->
1>     X = Map
1> end.
#{}
2> X.
#{}

It’s even more wired - a variable can be bound or not depending on which branch you took:

1> case #{} of
1>   Int when is_integer(Int) ->
1>     X = Int;
1>   Other ->
1>     ok
1> end.
ok
2> X.
* 1: variable 'X' is unbound
3> case 1 of
3>   Int when is_integer(Int) ->
3>     X = Int;
3>   Other ->
3>     ok
3> end.
1
4> X.
1

Exactly - in the snippet you’re using =, so there’s a possibility of leaks. Without = there are no leaks possible.
The updated code you posted also does not need this dance with variable names - it won’t generate any warnings always using the same variable - my refactoring is in the gist sng.ex · GitHub.

Also - you can silence warnings in generated code by marking it with quote generated: true do

Where Next?

Popular in Questions Top

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
_russellb
I want to try my hand at web scraping. What tools/libraries do I need to use. I’m hoping to turn this into something professional so don’...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
script
If I have a string “1000 cfu/ml” . I want to remove the characters and / and space . So the string is like this "1000" What is the ...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? https://hexdocs.pm/ecto/Ecto.Repo.h...
New
kostonstyle
Hi all I want to have a unix time, from the current time plus 1 hour. DateTime.now + 1 hour How to get it in elixir? Thanks
New
chewm
Hi guys, nice to meet you to the whole forum, I’m new here, I’m trying to configure visual studio code for elixir, right now the intellis...
New
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
Mooodi
Given a string, how can I get access to its character by index? Enum.at("my_string", 2) doesn't work. Or rather, not char, but a substr...
New

Other popular topics Top

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
srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call th...
New
AstonJ
You’re a programmer, so you don’t need spoon feeding with the conventional drivel about “this is an integer.” No. You need to know what’s...
New
KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 35421 110
New
mgjohns61585
Could someone help me? I'm making my first elixir program, number guessing game. I can't figure out how to convert the user's guess from ...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
Fl4m3Ph03n1x
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: ) Hello all, this is ...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
AstonJ
by Lance Halvorsen Elixir and Phoenix are generating tremendous excitement as an unbeatable platform for building modern web application...
460 27162 124
New
Nvim
Elixir appears to be a superior language to Python. I don’t see any advantage of Python over Elixir. Are there any?
New

We're in Beta

About us Mission Statement