# 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.

``````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!"
``````
1 Like

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.

2 Likes

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.

http://erlang.org/doc/reference_manual/expressions.html#guards

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))
``````
2 Likes

not sure I entirely understand what you mean by multi guard…

but elixir 1.6 is bringing in defguard and defguardp - which might be relevant…

https://github.com/elixir-lang/elixir/blob/v1.6/CHANGELOG.md#defguard-and-defguardp

Yep I know it could be that way. But as I said if i got a lot of paramters it becomes quite unreadable like this below:

``````  defp do_something([y1, y2, m1, m2, d1, d2 | _])
when d1 < 2, do: process(16, y1, y2, m1, m2, d1, d2)

defp do_something([y1, y2, m1, m2, d1, d2 | _])
when d1 < 4, do: process(20, y1, y2, m1, m2, d1, d2)

defp do_something([y1, y2, m1, m2, d1, d2 | _])
when d1 < 6, do: process(26, y1, y2, m1, m2, d1, d2)

defp do_something([y1, y2, m1, m2, d1, d2 | _])
when d1 < 8, do: process(32, y1, y2, m1, m2, d1, d2)
``````

The only readable way is use `cond` as peerreyanders states. For me cond is just like if and case, trying to avoid it as I can.

PS. bmi is just an example code from Haskell tutorial.

By multi guards I mean function definition with multiple bodies depending on specified guard, just like cond but simplified:

`````` defp do_something([y1, y2, m1, m2, d1, d2 | _])
when d1 < 2, do: process(16, y1, y2, m1, m2, d1, d2)
when d1 < 4, do: process(20, y1, y2, m1, m2, d1, d2)
when d1 < 6, do: process(26, y1, y2, m1, m2, d1, d2)
when d1 < 8, do: process(32, y1, y2, m1, m2, d1, d2)
end
``````

just like definition with `cond`:

`````` defp do_something([y1, y2, m1, m2, d1, d2 | _])
cond do
d1 < 2 -> process(16, y1, y2, m1, m2, d1, d2)
d1 < 4 -> process(20, y1, y2, m1, m2, d1, d2)
d1 < 6 -> process(26, y1, y2, m1, m2, d1, d2)
d1 < 8 -> process(32, y1, y2, m1, m2, d1, d2)
end
end
``````

Why? Anti-if campaign?‡ Ultimately that campaign was driving towards the Replace Type Code with Polymorphism refactoring - there is nothing wrong with conditionals as long as they are used with discipline.

And ultimately multiple function clauses are just a `case/2` in disguise!

‡ yet another example of “software development by slogan”

It is also important to recognize that the function clauses are not separate functions - they define parts of the same function.

The Replace Conditional with Lambda approach is closer to Replace Type Code with Strategy.

2 Likes

Wow there is even such campaign? Didn’t know about it.

There is nothing wrong about conditionals at all. I just try to avoid them if iI got such opportunity. For me personally code becomes little more readable.

Don’t get it personal peerreynders: and case/2 probably under the hood got goto instructions

Well, `goto` is useful but unfortunately it got abused so it got abolished. Similarly mutable state can be useful but immutability by default has distinct advantages.

1 Like

`or` and multiple `when`s 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 `when`s only the sub-expression is considered `false`.

2 Likes

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.

3 Likes

The only difference between case and function heads is the exception generated when no clause matches - `CaseClauseError` vs `FunctionClauseError` - in internal passes the only thing the compiler works with is `case`, everything else is lowered into that.

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

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