Maybe and when

I started using maybe in my codebase.
I have this kind of code:

discover_iface_type(Ifname) ->
    maybe
        {ok, Device} ?= validate_input:device(Ifname),
        {ok, DevInfo} ?= ip_addr_dev_info(Device),
        case maps:get(ifi_type, DevInfo, undefined) of
            undefined ->
                {error, not_tunnel};
            Type when is_atom(Type) ->
                {ok, Type}
        end
    else
        {error, _Reason} = E ->
            E
    end.

the problem with this is that I’d like to write:

discover_iface_type(Ifname) ->
    maybe
        {ok, Device} ?= validate_input:device(Ifname),
        {ok, DevInfo} ?= ip_addr_dev_info(Device),
        Type when is_atom(Type) ?= maps:get(ifi_type, DevInfo, undefined),
           {ok, Type}
    else
         undefined -> {error, not_found};
        {error, _Reason} = E ->
            E
    end.

One alternative is to write a more tedious code like:

discover_iface_type(Ifname) ->
    Getter = fun
      (undefined) -> {error, no_tunnel};
      (V) -> {ok, V} 
    end,
    maybe
        {ok, Device} ?= validate_input:device(Ifname),
        {ok, DevInfo} ?= ip_addr_dev_info(Device),
        {ok, Type} ?= Getter(maps:get(ifi_type, DevInfo, undefined)
            {ok, Type}
    else
        {error, _Reason} = E -> E
    end.

But we can extends the maps module to implement the fetch function like the Elixir Map.fetch

discover_iface_type(Ifname) ->
    maybe
        {ok, Device} ?= validate_input:device(Ifname),
        {ok, DevInfo} ?= ip_addr_dev_info(Device),
        {ok, Type} ?= maps:fetch(ifi_type, DevInfo)
            {ok, Type}
    else
        error -> {error, not_found};
        {error, _Reason} = E -> E
    end.

What do you think?

You mean like maps:find/2?

Anyway, when fased with a similar issue I’ve done it like this:

discover_iface_type(Ifname) ->
    maybe
        {ok, Device} ?= validate_input:device(Ifname),
        {ok, DevInfo} ?= ip_addr_dev_info(Device),
        Type = maps:get(ifi_type, DevInfo, undefined),
        true ?= is_atom(Type),
        {ok, Type}
    else
         false -> {error, not_found};
        {error, _Reason} = E ->
            E
    end.

It is not as pretty as having a when there (imo), but I think it is a bit better than wrapping it.

3 Likes

thanks a lot! is the maps:find performance equal to the classic maps:get?

Yes, they are the same. Just different ways of returning the same value.

If you want to be pedantic, I suppose that maps:get/2 is a bit faster as it does not have to build the {ok, ...} tuple, though I would be suprised if you noticed any difference unless your entire application is looking up things in maps without doing anything with the value.

1 Like

what about expanding the functionality of maybe adding the ability to use when?

There is a discussion about it here.

I started to implement it some time ago:

1 Like