[Proposal] Kernel.if_match/2 - A happy medium between `if` and `case`

30 views
Skip to first unread message

Joel Wietelmann

unread,
Oct 17, 2019, 3:04:04 PM10/17/19
to elixir-lang-core
Scenario: You have one pattern you want to match. You do not want to raise a NoMatchError when the value fails to match this pattern.

Probably the best available thing to do is to use a `case` statement:

case foo do
%{bar: "baz"} -> "hello i match"
_ -> nil
end

However, that default case can get pretty repetitive when it's frequently nothing, and it gets even more awkward inside of EEx:

<%= case foo do %>
<% %{bar: "baz"} -> %>
hello i match
<% _ -> %>
<% end %>

There's also `Kernel.match?/2`, which is awkward to use in an `if` statement because it doesn't read like any pattern matching expression that an Elixir developer has typically ever written:

if match?(%{bar: "baz"}, foo) do
"yes"
end

Here is an alternative idea, which I think is significantly easier to read than the above options:

if_match %{bar: "baz"} = foo do
"yes"
else
"no"
end

if_match %{bar: "not baz"} = foo do
"yes"
end

<%= if_match %{bar: "baz"} = foo do %>
hello i match
<% end %>

Proof of concept code:

defmacro if_match(expression, do: do_clause) do
quote do
match(unquote(expression), do: unquote(do_clause), else: nil)
end
end

defmacro if_match({:=, _, [pattern, value]}, do: do_clause, else: else_clause) do
quote do
case unquote(value) do
unquote(pattern) ->
unquote(do_clause)

_ ->
unquote(else_clause)
end
end
end

Michael St Clair

unread,
Oct 17, 2019, 3:07:48 PM10/17/19
to elixir-lang-core
Is there a reason or limitation `if` couldn't be changed to handle this instead of throwing a NoMatchError?

Joel Wietelmann

unread,
Oct 17, 2019, 3:12:36 PM10/17/19
to elixir-lang-core
You raise a good point. If I could write this without worrying about NoMatchErrors, that would solve all my complaints:
if %{bar: "baz"} = foo, do: "yes"
But I thought changing the `if` statement might be a harder sell.

José Valim

unread,
Oct 17, 2019, 3:15:41 PM10/17/19
to elixir-l...@googlegroups.com
Thanks Joel for the proposal.

I am not a big fan of a macro changing what = means. = typically means pattern matching and a MatchClause error in case they don't match. Sure, we can change what it means, but if = starts meaning different things in different places, developers would constantly start to second guess themselves.

Even inside for and with-comprehensions, = means MatchClause error and they introduce an explicit <- construct for "soft matching". So maybe we could do:

<%= if %{bar: bar} <- foo do %>
  ...
<% end %>

But then you might as well use with:

<%= with %{bar: bar} <- foo do %>
  Do that
<% end %>

Although the best solution here would be to move the logic to helper functions. I would prefer to avoid pattern matching in templates.


José Valim
Skype: jv.ptec
Founder and Director of R&D


--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/4944626e-7cde-4200-ae3a-e573102246a2%40googlegroups.com.

Greg Vaughn

unread,
Oct 17, 2019, 3:17:49 PM10/17/19
to elixir-l...@googlegroups.com
The whole point of `Kernel.match?/2` is to convert the NoMatchError into a simple true/false. Sure, maybe it takes a little bit of getting used to, but you're overstating your case. I predict that with more Elixir experience, you'll see the function used more and will gain familiarity with it.

Create and use your helper macro in your codebase if you want, but I don't want to see this extra complexity in the core language/libraries.

--
Greg Vaughn

Kelvin Raffael Stinghen

unread,
Oct 17, 2019, 3:20:22 PM10/17/19
to elixir-l...@googlegroups.com
Just mentioning I really like the idea of allowing the use of `<-` on `if`s. So 👍 from me

Best,
Kelvin Stinghen
kelvin....@me.com

--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-co...@googlegroups.com.

Michael St Clair

unread,
Oct 17, 2019, 3:24:56 PM10/17/19
to elixir-lang-core
With does seem like the right thing in this case. 

Although to add I don't think I have ever done pattern matching in an if for this reason. Seems like if you are wrapping pattern matching in an if you want to know if the match works. I think supporting `<-` in an `if` would be good if there are issues with just using `=` to make things simpler. Specifically when using with and an else statement when only checking a single match.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-l...@googlegroups.com.

Joel Wietelmann

unread,
Oct 17, 2019, 3:25:09 PM10/17/19
to elixir-lang-core
José: I like the `<-` option. That `with` expression is alright until you want an `else` and then you're in awkward territory again.


On Thursday, October 17, 2019 at 3:20:22 PM UTC-4, Kelvin Raffael Stinghen wrote:
Just mentioning I really like the idea of allowing the use of `<-` on `if`s. So 👍 from me

Best,
Kelvin Stinghen
kelvin....@me.com

On Oct 17, 2019, at 16:12, Joel Wietelmann <jo...@revelry.co> wrote:

You raise a good point. If I could write this without worrying about NoMatchErrors, that would solve all my complaints:
if %{bar: "baz"} = foo, do: "yes"
But I thought changing the `if` statement might be a harder sell.

On Thursday, October 17, 2019 at 3:07:48 PM UTC-4, Michael St Clair wrote:
Is there a reason or limitation `if` couldn't be changed to handle this instead of throwing a NoMatchError?

--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-l...@googlegroups.com.

Kelvin Raffael Stinghen

unread,
Oct 17, 2019, 3:31:26 PM10/17/19
to elixir-l...@googlegroups.com
> With does seem like the right thing in this case. 

His argument is that using a `case` for that is quite noisy, and `with` would be no different, since he would need to do something like:

    with %{bar: “baz”} <- foo do
      “hello i match”
    else
      _ -> nil
    end

Since when `with` does not match and no `else` clause is given, the return value is the unmatched value, instead of `nil` as I would expect it to be on something like:

    if %{bar: “baz”} <- foo do
      “hello i match”
    end

Best,
Kelvin Stinghen
kelvin....@me.com

To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/c3e612e7-507e-4ede-9175-7c8809d05eee%40googlegroups.com.

Kelvin Raffael Stinghen

unread,
Oct 17, 2019, 3:33:10 PM10/17/19
to elixir-l...@googlegroups.com
Also, I would be happy to work on that feature and open a PR if you decide to go ahead with that.

Best,
Kelvin Stinghen
kelvin....@me.com

To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/f9841970-bdef-4e67-b088-e680d2ae850c%40googlegroups.com.

Kelvin Raffael Stinghen

unread,
Oct 17, 2019, 3:37:53 PM10/17/19
to elixir-l...@googlegroups.com
On thing to think about though is the expression: `if nil <- nil, do: :hello, else: :world`. What should it return?

Best,
Kelvin Stinghen
kelvin....@me.com

On Oct 17, 2019, at 16:24, Michael St Clair <micha...@gmail.com> wrote:

To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/c3e612e7-507e-4ede-9175-7c8809d05eee%40googlegroups.com.

Joel Wietelmann

unread,
Oct 17, 2019, 3:39:06 PM10/17/19
to elixir-lang-core
On the one hand, I think this use of `with` might have a pretty reasonable , even in EEx:

<%= with {:ok, _} <- foo do %>
yes
<% else _ -> %>
no
<% end %>

On the other hand, what it means is much clearer as:

<%= if {:ok, _} <- foo do %>
yes
<% else %>
no
<% end %>

Best,
Kelvin Stinghen
kelvin....@me.com

José Valim

unread,
Oct 17, 2019, 3:39:14 PM10/17/19
to elixir-l...@googlegroups.com
I think I expressed myself poorly. I was not proposing to support <- on if, I am saying with is already a great alternative which is very close to what is being proposed.

The only complaint about “with” would be: 1. you want an else clause, 2. you find the else unpleasant on EEx, and 3. you *have* for some reason to pattern match inside EEx.

I don’t think it is a scenario frequent enough to add exceptions to built-in constructs. In fact, the argument that something does not look good in EEx is unlikely to be enough reason to change the language, especially when you can extend it yourself. :)


--

Michael St Clair

unread,
Oct 17, 2019, 3:42:25 PM10/17/19
to elixir-lang-core
The pattern match succeeded so :hello

Best,
Kelvin Stinghen
kelvin....@me.com

To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-core+unsub...@googlegroups.com.

Michael St Clair

unread,
Oct 17, 2019, 3:43:16 PM10/17/19
to elixir-lang-core
I would say it also benefits outside of EEx having a simpler if/else when matching on a single thing

--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-l...@googlegroups.com.

José Valim

unread,
Oct 17, 2019, 3:56:06 PM10/17/19
to elixir-l...@googlegroups.com
> I would say it also benefits outside of EEx having a simpler if/else when matching on a single thing

Changes to the language are often more complex than they originally seem. Especially when we are adding special behaviour to a construct. Today "if" works with expressions. It doesn't modify its first argument in anyway, which is how most constructs in Elixir work.

So let's say we add "if foo <- bar do". Then someone may ask: does it mean <- is supported everywhere? Or maybe does "if" now supports multiple <- clauses, like "when" and "for" (the only constructs to accept <-) do?

Now imagine that you have:

if foo <- bar do
  ...
else
  ...
end

And now you want to add a new clause. In Elixir, this means we need to move to a "cond". Does we know need to support <- in a cond too? Would anyone think this is actually readable at first glance?

cond do
  foo <- bar ->

And this is just immediate consequences I can think from the top of my head. We are bound to have more.

José Valim
Skype: jv.ptec
Founder and Director of R&D

To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/bf9ca31f-b6e7-405b-b534-f70a00cfdb9b%40googlegroups.com.

Kelvin Raffael Stinghen

unread,
Oct 17, 2019, 7:07:43 PM10/17/19
to elixir-l...@googlegroups.com
 Then someone may ask: does it mean <- is supported everywhere? 

Well, someone might already ask that by looking at `for` and `with` and wondering if it works for `case`, `if` and `cond`.

Or maybe does "if" now supports multiple <- clauses, like "when" and "for" (the only constructs to accept <-) do?

This one is a good point, and maybe it would be a nice thing to make the answer to that question a “yes” and just merge all the clauses using `and`s.

> Does we know need to support <- in a cond too? Would anyone think this is actually readable at first glance?

Indeed, the pattern `<- … ->` is a bit strange to look at, but would still be a nice feature to have spread over all the conditional constructs. 

To be honest, I can’t really tell the overall complexity of the change, and neither what would the cost to compilation be (I suppose it’s big), but my point is that maybe it is worth the try at least.

I will create a lib with some macros for that, and try to benchmark the compilation, just out of curiosity. I let you know of the results.

Best,
Kelvin Stinghen
kelvin....@me.com

José Valim

unread,
Oct 17, 2019, 7:24:43 PM10/17/19
to elixir-l...@googlegroups.com


Well, someone might already ask that by looking at `for` and `with` and wondering if it works for `case`, `if` and `cond`.

You are missing the point. :) for and with do not accept any expression. They have their own rules. They always had. I cannot write “for whatever do”. But if/cond accepts any expression, as is, no special cases. That’s the line I don’t want to (and we won’t) blurry.

This one is a good point, and maybe it would be a nice thing to make the answer to that question a “yes” and just merge all the clauses using `and`
s.

Then it has to be made a special form and that has even further implications, so it is unlikely to happen either. And if we don’t make it, then if starts to look inconsistent (which was my point).

To be honest, I can’t really tell the overall complexity of the change, and neither what would the cost to compilation be (I suppose it’s big), but my point is that maybe it is worth the try at least.

There is no performance cost. The cost is all in complexity and it forcing for us to do other changes in the language. If you can’t tell the complexity, you can trust the opinion of someone who can (and is describing it) :)

But you are welcome to explore it. The language was made extensible for this reason. But I can say such change would not be accepted in core itself.


Kelvin Raffael Stinghen

unread,
Oct 17, 2019, 7:50:41 PM10/17/19
to elixir-l...@googlegroups.com
 But if/cond accepts any expression, as is, no special cases. That’s the line I don’t want to (and we won’t) blurry.

I see, I understand what you mean, maybe I still not fully understand all the reasoning behind it, but I trust you know what’s the best for it and I’m not seeing something you are.

Then it has to be made a special form and that has even further implications, so it is unlikely to happen either.

Not sure what are the further implications you mean, but I will study a bit more on the subject while trying to create that macro, maybe I will have those answers in the end.

And if we don’t make it, then if starts to look inconsistent (which was my point).

Yeah, I got it now, although to understand the kind of inconsistency you mean, one might actually know about some internals of the language that not too many people know. My point about the `for` and `with` inconsistency compared to `if` and `cond` was taking into account a totally lay person that might think `<-` is just another kind of "match operator”. But I see your point: it is not and it was never the intention for it to be.

Thinking about that actually just gave me an idea for the lib: a `left <- right` operator that works exactly like `=` but instead of raising when it doesn’t match, it returns `nil`. Will check how that goes and what are the implications of the conflict 

If you can’t tell the complexity, you can trust the opinion of someone who can (and is describing it) :)

Sure, yeah! I trust you and everyone else on the core team, you never let me down before, I am sure you have really good reasons for all the decisions you make :)

Just to be clear, I was just wondering about the feature, sorry about being stubborn, I know it’s very unlikely you would change a core feature like that too, will just shut up now and take advantage of the fact it is possible to do that on a lib.

Best,
Kelvin Stinghen
kelvin....@me.com

José Valim

unread,
Oct 17, 2019, 8:03:27 PM10/17/19
to elixir-l...@googlegroups.com
But I see your point: it is not and it was never the intention for it to be.

Exactly. It could be, I guess, but it certainly wasn’t designed as such.

> Thinking about that actually just gave me an idea for the lib: a `left <- right` operator that works exactly like `=` but instead of raising when it doesn’t match, it returns `nil`. Will check how that goes and what are the implications of the conflict 

But even that can have other consequences. For example, what is the precedence for <-? Does it work nicely with “and” and “or” and other logical operators?

So there is a lot of complexity involved! I was not trying to be harsh. :D Almost anything you may want to do may impact others areas.

Sven Gehring

unread,
Oct 18, 2019, 2:31:34 AM10/18/19
to elixir-l...@googlegroups.com
> There's also `Kernel.match?/2`, which is awkward to use [...]
It's neither more complex to write nor understand than `if_match`. 
I would argue it just seems awkward because you haven't used it a lot? -- At least it felt awkward to me the first few times I used it.

Assuming we ruled out `if_match`, the only alternative would be `<-`, custom use case of `=` or a similar solution.
Personally, I hate it when languages start using what feels like millions of "nice", "short", "clear" syntax trickeries for any given [edge] case.
It's just extremely hard to read in other people's code if you're not that experienced and a hassle to remember for yourselves, if you don't use it a lot.

I know I'm often times a bit of a buzzkill when it comes to little additions/changes like this but I feel the currently available solutions suffice.

-sven

--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-co...@googlegroups.com.

Kelvin Raffael Stinghen

unread,
Oct 18, 2019, 7:49:20 AM10/18/19
to elixir-l...@googlegroups.com
But even that can have other consequences. For example, what is the precedence for <-? Does it work nicely with “and” and “or” and other logical operators?
>
So there is a lot of complexity involved! I was not trying to be harsh. :D Almost anything you may want to do may impact others areas.

Yep! I understand, will discover those by myself by trying it, to not take more of your time and also because learning by hitting the head in the wall makes it more rememberable.

Thank you for the discussion though, really fruitful for me already. :D

Best,
Kelvin Stinghen
kelvin....@me.com

--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-co...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages