Getting started with Erlang's `maybe_expr`
Assumptions
You are using rebar3 to build your project. You are using OTP 25.
Introduction
I am an Erlang beginner and I am currently building a Slack bot to learn more. Here is some code I wrote recently,
{ok, ChannelId} = map_utils:recursive_get([<<"channel">>, <<"id">>], Payload),
{ok, UserId} = map_utils:recursive_get([<<"user">>, <<"id">>], Payload),
{ok, TeamId} = map_utils:recursive_get([<<"team">>, <<"id">>], Payload),
{ok, ResponseUrl} = map_utils:recursive_get([<<"response_url">>], Payload),
The Payload
here is a decoded JSON body from Slack. The map_utils:recursive_get/2
function
takes the path to a JSON entry and extracts it if possible, given this JSON
{
"channel": {
"id": "value"
}
}
If we ran this JSON through my HTTP handlers, this code would succeed,
{ok, <<"value">>} = map_utils:recursive_get([<<"channel">>, <<"id">>]),
{error, not_found} = map_utils:recursive_get([<<"hello">>, <<"world">>]),
When the ChannelId
, UserId
, etc are all extracted from the Payload
properly everything is great. However, if any of the pattern matches fails
everything seems to get dropped into the void. This is obviously problematic
when you are building an application. Thankfully, I discovered maybe_expr
!
With maybe_expr
, the code will look more like this,
-record(interact_payload, {channel_id, user_id, team_id, response_url})
% ...
maybe
{ok, ChannelId} ?= map_utils:recursive_get([<<"channel">>, <<"id">>], Payload),
{ok, UserId} ?= map_utils:recursive_get([<<"user">>, <<"id">>], Payload),
{ok, TeamId} ?= map_utils:recursive_get([<<"team">>, <<"id">>], Payload),
{ok, ResponseUrl} ?= map_utils:recursive_get([<<"response_url">>], Payload)
{ok, #interact_payload{channel_id = ChannelId, user_id = UserId, team_id = TeamId, response_url = ResponseUrl}}
else
{error, not_found} ->
logger:error(...),
{error, not_found};
{error, not_found, Reason} ->
logger:error(...),
{error, not_found}
end,
Instead of dropping anything into the void, the else
clause can be used to pattern
match out any failure cases. Here, we match {error, not_found}
and {error,
not_found, Reason}
and log that we had an unexpected error.
This feature is currently “experimental” in OTP 25. However, it is becoming standardized over the next few OTP releases. See this highlight for more information.
Setting up rebar3
Credit to this forum entry for the details. With OTP 25, first
create the file config/vm.args
if it doesn’t exist and add,
-enable-feature maybe_expr
Then set this in your environment,
export ERL_FLAGS="-args_file config/vm.args"
Or, prepend ERL_FLAGS="-args_file config/vm.args"
to your rebar3
commands.
Reminder, you can skip this step in OTP 26.
Enabling the feature
In your Erlang files you only need to add (after your module definition),
-module(...).
-feature(maybe_expr, enable).
Done. Supposedly in OTP 27 you won’t need to do either of these steps!
More information
You can read the Erlang Enhancement Process proposal here.
If you’ve ever written Haskell before this is essentially ExceptT e IO
a
where the e
can be literally anything. It is your job in Erlang
to catch all the cases. You can add type checking to your Erlang code with
something like eqWAlizer and type annotations via dialyzer. I was
first exposed to type annotations in LYAH’s dialyzer
introduction.
If there is anything you are curious about in Erlang, please ask me about it on BlueSky or the Erlanger’s Slack. I would like to write more blog posts and learn more about Erlang.