crabonature

crabonature

Multiple guards in Elixir

I’m still quite new to Elixir.

As I understand we got in Elixir “multi guards” as convention to simplify one large guard with or’s?:

def boolean(value) when map_size(value) < 1 or tuple_size(value) < 1 do
  :guard_passed
end

is almost the same as:

def multiguard(value)
    when map_size(value) < 1
    when tuple_size(value) < 1 do
  :guard_passed
end

There are multiclause anonymous functions as well:

larger_than_two? = fn
    n when is_integer(n) and n > 2 -> true
    n when is_integer(n) -> false
  end

But why there is no multi guard functions? As I understand instead of them I need to write cond expression or use multi clousers, but if I got in a function definition a lot of parameters or complicated pattern matching, only cond stays quite readable.

For example in Haskell:

bmiTell :: (RealFloat a) => a -> String  
bmiTell bmi  
    | bmi <= 18.5 = "You're underweight!"  
    | bmi <= 25.0 = "You're supposedly normal."  
    | bmi <= 30.0 = "You're little too much!"  
    | otherwise   = "No way!"  

It can be handy if we got something like this in Elixir, functions with multi guard bodies? What you think? Was there some talks in past about it?

@spec bmi_tell(float()) :: String.t()
def bmi_tell(bmi)
  when bmi <= 18.5, do: "You're underweight!"
  when bmi <= 25.0, do: "You're supposedly normal."
  when bmi <= 30.0, do: "You're little too much!"
  when true,        do: "No way!"

First Post!

mudasobwa

mudasobwa

Creator of Cure

It could be written as:

@spec bmi_tell(float()) :: String.t()
def bmi_tell(bmi)
  when bmi <= 18.5, do: "You're underweight!"
def bmi_tell(bmi)
  when bmi <= 25.0, do: "You're supposedly normal."
def bmi_tell(bmi)
  when bmi <= 30.0, do: "You're little too much!"
def bmi_tell(bmi)
  when true,        do: "No way!"

Yes, it’s a bit more verbose, but AFAICT the syntax you proposed is impossible to use as is, since def is a macro and it expects the keyword argument do to follow after guards.

Most Liked

NobbZ

NobbZ

or and multiple whens are not equivalent, especially when there are exceptions in the game:

iex(1)> defmodule M do                                                          
...(1)> def guard_or(x) when x <> "foo" == "xfoo" or is_integer(x), do: true    
...(1)> def guard_or(_), do: false                                              
...(1)> 
...(1)> def guard_when(x) when x <> "foo" == "xfoo" when is_integer(x), do: true
...(1)> def guard_when(_), do: false                                            
...(1)> end
{:module, M,
 <<70, 79, 82, 49, 0, 0, 4, 172, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 105,
   0, 0, 0, 11, 8, 69, 108, 105, 120, 105, 114, 46, 77, 8, 95, 95, 105, 110,
   102, 111, 95, 95, 9, 102, 117, 110, 99, ...>>, {:guard_when, 1}}
iex(2)> M.guard_or(1)                                                           
false
iex(3)> M.guard_or("x")
true
iex(4)> M.guard_when(1)             
true
iex(5)> M.guard_when("x")
true

When using or an exception makes the whole guard false, while with multiple whens only the sub-expression is considered false.

peerreynders

peerreynders

I think you may be fixating on guards too much. It’s important to keep in mind that guards only allow a restricted set of expressions,

The reason for restricting the set of valid expressions is that evaluation of a guard expression must be guaranteed to be free of side effects.

Expressions — Erlang System Documentation v29.0.2

and that guards are a conditional extension to pattern matching - i.e. they can appear whenever a pattern match is possible. There are other alternatives for conditionals such as case/2 (which is based on pattern matching and therefore also supports guards) and cond/1.

To me cond seems most appropriate for your bmi_tell/1 example:

defmodule Demo do

  def bmi_tell_cond(bmi),
    do: (
      cond do
        bmi <= 18.5 -> "You're underweight!"
        bmi <= 25.0 -> "You're supposedly normal."
        bmi <= 30.0 -> "You're little too much!"
        true        -> "No way!"
      end)

  def bmi_tell_case(value),
    do: (
      case value do
        bmi when bmi <= 18.5 -> "You're underweight!"
        bmi when bmi <= 25.0 -> "You're supposedly normal."
        bmi when bmi <= 30.0 -> "You're little too much!"
        _                    -> "No way!"
      end)

end

IO.inspect(Demo.bmi_tell_cond(18.0))
IO.inspect(Demo.bmi_tell_cond(24.0))
IO.inspect(Demo.bmi_tell_cond(29.0))
IO.inspect(Demo.bmi_tell_cond(31.0))

IO.inspect(Demo.bmi_tell_case(18.0))
IO.inspect(Demo.bmi_tell_case(24.0))
IO.inspect(Demo.bmi_tell_case(29.0))
IO.inspect(Demo.bmi_tell_case(31.0))
OvermindDL1

OvermindDL1

Literally even!

Something like this:

def blah(a) when a<0, do: 0
def blah(1), do: 1
def blah(a) when a>0, do: 2

Quite literally compiles ‘almost’ identically to (‘almost’ meaning it is more lambda’ish internally):

def blah(a) do
  case a do
    a when a<0 -> 0
    1 -> 1
    a when a>0 -> 2
    _ -> raise %MatchError{...}
  end
end

Actually it compiles into a simple dispatch tree. It’s not like ‘way’ smart in that it can combine all parts possible, but it is pretty decent. In general you can think of it as ‘testing’ the first branch, if that fails it tests the second, if that fails it tests the third, and so on. It is a linear slowdown with the number of cases (though linear more in C terms so it is still blazing fast compared to anything you could write explicitly, this is also why guard calls are restricted).

Precisely. Multiple when’s compiles to different branches in the dispatch tree, where or’s get compiled into the same dispatch branch.

Last Post!

OvermindDL1

OvermindDL1

Yep, an internal pass creates a trailing ‘accept anything’ case that throws that exception, but you can always add that manually yourself too. :slight_smile:

It is pretty cool how the erlang compiler works if anyone is bored and wants to look. :slight_smile:

Where Next?

Popular in Discussions Top

rms.mrcs
A couple of days ago I was discussing with a friend about different approaches to write microservices. He said that if he was going to w...
New
AstonJ
If so I (and hopefully others!) might have some tips for you :slight_smile: But first, please say which area you’re finding most challen...
New
lucaong
Hello Elixir and Nerves community, I have been working for a while on an open-source embedded key-value database for Elixir, that I call...
230 14403 124
New
marciol
Please, let me know if this kind of discussion already took place in another topic . Hi all, how do you consider if is better to build ...
New
RudManusachi
What configs will make sense to put to runtime.exs? – A bit of how I configure apps: I have generic configs in config/config.exs, dev...
New
fireproofsocks
I’ve been working on an Elixir project that has required a lot of scripting. I usually reach for Elixir because I like it more (and in th...
New
matthias_toepp
I’d love to hear what people think about Wisp, the new Gleam web framework started by Gleam’s primary creator Louis Pilfold. Gleam, alon...
New

Other popular topics Top

Qqwy
Update: How to use the Blogs &amp; Podcasts section You can post links to your blog posts or podcasts either in one of the Official Blog...
3271 131117 1222
New
grych
Hi folks, Few months ago I have announced the proof-of-concept of the library to manipulate the browsers DOM objects directly from Elixi...
639 54260 488
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
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
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
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

We're in Beta

About us Mission Statement