Skip to content

Instantly share code, notes, and snippets.

@fschuindt
Created Nov 20, 2019
Embed
What would you like to do?

This is a example on pattern matching.

Given the Map/Hash input:

input = %{name: "Alice Foo", points: 100, email: "alicefoo@myemail.com"}

The goal is to determine the user rank, by its points:

  • 0..50 -> Starter
  • 50..200 -> Member
  • >200 -> Veteran

The code in Elixir using pattern matching:

defmodule Rank do
  @moduledoc """
  Offers means to perform user ranking computations.
  """

  @typedoc """
  A user rank.
  """
  @type rank :: :starter | :member | :veteran

  @doc """
  Finds the rank of a given user.
  """
  @spec find(%{points: integer()}) :: rank()
  def find(%{points: n}) when n <= 50, do: :starter
  def find(%{points: n}) when n <= 200, do: :member
  def find(_user), do: :veteran
end

Here's how you use it:

iex(1)> Rank.find(input)
:member

The same code in Ruby:

class Rank
  # Offer means to perform ranking computations.

  # Finds the rank of a given user.
  def self.find(user)
    case user[:points]
    when 0..50
      :starter
    when 50..200
      :member
    else
      :veteran
    end
  end
end

And running it:

irb(main):001:0> Rank.find(input)
=> :member

Patter matching makes the code more elegant, expressive and easier to read.

@oliverswitzer
Copy link

oliverswitzer commented May 4, 2020

class Rank
  def self.find(user)
    return :starter if (0..50).include? user[:points]
    return :member if (50..200).include? user[:points]
   
    :veteran
  end
end

This honestly reads more like English to me than the elixir example.

Case statements are generally looked down upon to solve problems like these in any language...

@fschuindt
Copy link
Author

fschuindt commented May 4, 2020

@oliverswitzer

class Rank
  def self.find(user)
    return :starter if (0..50).include? user[:points]
    return :member if (50..200).include? user[:points]
   
    :veteran
  end
end

This honestly reads more like English to me than the elixir example.

Case statements are generally looked down upon to solve problems like these in any language...

What you did is great, and its the more "functional" approach to the solution in Ruby. You're using Ruby's so called "guard clauses", which is one of the fundamental functional building blocks. Your code in Ruby follows what I understand by functional as it's not only using guard clauses, but is also defining a class method, which is one of the ways to approach namespaced functions without keeping state, like classes and objects.

Though I must admit the examples I came up with are weak, this is a hard subject to discuss about. In this snippet here I was only trying to show the advantage of pattern matching. But keeping any code functional-like (as you did) is a advantage too, so I think it should be preferred for "high level" systems.

@cipater
Copy link

cipater commented May 15, 2020

There is also experimental support for pattern matching in ruby 2.7.0, giving us:

class Rank
  def self.find(user)
    case user
    in points: n if n <= 50  then :starter
    in points: n if n <= 200 then :member
    else :veteran
    end
  end
end

Or slightly more succinct:

class Rank
  def self.find(user)
    case user
    in points: ...50  then :starter
    in points: ...200 then :member
    else :veteran
    end
  end
end

@bingomanatee
Copy link

bingomanatee commented Jun 29, 2020

just to niggle - your code doesn't consider negative numbers :D

@cipater
Copy link

cipater commented Jun 29, 2020

If you're referring to the "beginless" range from 2.7:

irb(main):001:0> (...50).include?(-1)
=> true

@razenha
Copy link

razenha commented Feb 7, 2021

Am I the only one who thinks the Ruby code is way more expressive and easier to read? You just look at the code and even if you are a beginner programmer that doesn't know much Ruby you immediately understand what the code is supposed to do and how it works. As for elegance, simplicity is the ultimate elegance.

@akoskovacs
Copy link

akoskovacs commented Apr 5, 2021

@razenha Yeah I also have to agree with you, I am experimenting with Elixir currently and I still find its syntax much worse than Ruby. And I even used Erlang before. Ruby is just more expressive and easier to look at.

@kasvith
Copy link

kasvith commented Jul 30, 2021

In elixir, you can do it like following as well

With cond

  def find(%{points: n}) do
    cond do
      n <= 50 -> :starter
      n <= 200 -> :member
      true -> :veteran
    end
  end

With case

  def find(%{points: n}) do
    case n do
      n when n <= 50 -> :starter
      n when n <= 200 -> :member
      _ -> :veteran
    end
  end

But I still like the function head pattern matching with guards(can be weird at first, at least for me but then it makes sense).
This allows the compiler to optimize the function call. 💪

If you don't like it there are other options like above ⬆️

After all both Ruby and Elixir looks fine to me. ✌️

@thetrung
Copy link

thetrung commented Apr 30, 2022

class Rank
  def self.find(user)
    case user
    in points: ...50  then :starter
    in points: ...200 then :member
    else :veteran
    end
  end
end

I still like to read Ruby version more..
it’s just beautiful 🍷 like poems

but certainly, his team need Elixir for performance, which Ruby could be blamed for. Although, they could also just use Rust to avoid data race, gc event spike and memory errors/leak in runtime that any GC language could have.

otherwise, favoring new thing doesn’t mean old thing are bad. It may be just a more suitable, comfortable option to ppl that they got reasons to go for.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment