47

Can you use a Regular Expression inside a case in Elixir?

So something along the lines of this:

case some_string do
  "string"        -> # do something
  ~r/string[\d]+/ -> # do something
  _               -> # do something
end
Daisuke Shimamoto
  • 5,206
  • 6
  • 32
  • 37

2 Answers2

47

With case it is not possible, but you can use cond:

cond do
  some_string == "string"                     -> # do something
  String.match?(some_string, ~r/string[\d]+/) -> # do something
  true                                        -> # do something
end

The reason is that there is no way to hook into the pattern matching by calling special functions for specific values. I guess you got the idea from Ruby, which implements this by defining the special operator ===. This will be implicitly called by Ruby's case statement and for a regex it will match the given value.

Patrick Oscity
  • 53,604
  • 17
  • 144
  • 168
  • 1
    I see. Thanks! I did indeed get the idea from Ruby ;) Didn't know about the `===`. Thanks for that too! – Daisuke Shimamoto Jan 07 '16 at 08:09
  • 4
    `===` in Ruby will do all sorts of things: e.g. for a class name it will check if the value is an instance of the class, for ranges it will check if the value is in the range, for anonymous functions(procs/lambdas) it will even call the function, etc. I actually love that Elixir does not have this kind of obscure magic. Maybe in Elixir I need to type a little bit more but at least I know what's going on ;-) – Patrick Oscity Jan 07 '16 at 08:15
30

As Patrick said in his answer, there is nothing built-in for this, and cond is probably your best option.

But to add another option and to show off the flexibility of Elixir: Since case is just a macro in Elixir, you could implement your own macro like regex_case to do this.

You should keep in mind that this might make the code harder to understand for new people on the project, but if you do a lot of regex matching, maybe the trade-off could make sense. You be the judge.

I implemented this a while ago, just to see that it was possible:

defmodule RegexCase do
  defmacro regex_case(string, do: lines) do
    new_lines = Enum.map lines, fn ({:->, context, [[regex], result]}) ->
      condition = quote do: String.match?(unquote(string), unquote(regex))
      {:->, context, [[condition], result]}
    end

    # Base case if nothing matches; "cond" complains otherwise.
    base_case = quote do: (true -> nil)
    new_lines = new_lines ++ base_case

    quote do
      cond do
        unquote(new_lines)
      end
    end
  end
end

defmodule Run do
  import RegexCase

  def run do
    regex_case "hello" do
      ~r/x/ -> IO.puts("matches x")
      ~r/e/ -> IO.puts("matches e")
      ~r/y/ -> IO.puts("matches y")
    end
  end
end

Run.run
Henrik N
  • 15,786
  • 5
  • 82
  • 131
  • 2
    Very true and indeed the trade-off would be difficult. Thanks for the code sample too! – Daisuke Shimamoto Jan 10 '16 at 08:13
  • 6
    Since someone emailed me to ask about the license of this code: I consider it to be in the public domain. So feel free to use it without attribution or strings attached. – Henrik N Feb 23 '18 at 17:02