Pattern-matching in Ruby is pretty cool

Hercules Lemke Merscher - Dec 21 '22 - - Dev Community

Before moving ahead, think for a moment about how would you parse this:

$ cd /
$ ls
dir a
14848514 b.txt
8504156 c.dat
dir d
$ cd a
$ ls
dir e
29116 f
2557 g
62596 h.lst
$ cd e
$ ls
584 i
$ cd ..
$ cd ..
$ cd d
$ ls
4060174 j
8033020 d.log
5626152 d.ext
7214296 k
Enter fullscreen mode Exit fullscreen mode

Certainly, not difficult. Some conditionals and string manipulation here and there, it is the first thing that comes to mind. Though trivial, it may not be obvious or pleasant to read afterward, or for a new pair of eyes.

Ruby 2.7 introduced pattern-matching as an experimental feature, and since Ruby 3.0 it is not considered experimental anymore.

In case you don’t know what pattern matching is, it’s a feature that allows you to specify patterns to match against data. It is similar to regular expression matching, but with the ability to match against arbitrary data structures, rather than just strings.

The content above can be simply pattern-matched this way:

input.split("\n").each do |line|
  cmd = line.split(" ")
  case cmd
  in ["$", "ls"]
    # matches listing entries
  in ["$", "cd", ".."]
    # matches leaving a directory
  in ["$", "cd", dir]
    # matches entering a directory
  in ["dir", dir]
    # matches directory entry
  in [size, filename]
    # matches file entry
  end
end
Enter fullscreen mode Exit fullscreen mode

That’s exactly what I did on day 7 of the advent of code! The resolution just came naturally and easily realizing that it would be a perfect case to use pattern-matching.

At first, it seems like a switch-case statement on steroids, which is true, but it’s so much more!

Arrays, hashes, and objects can be matched and bound:

# Matching against arrays
[1, 2, x, 4] = [1, 2, 3, 4]  # x is now bound to 3
[a, b, *rest] = [1, 2, 3, 4]  # a is bound to 1, b is bound to 2, and rest is bound to [3, 4]

# Matching against hashes
{a: 1, b: 2} = {a: 1, b: 2}  # a is bound to 1, b is bound to 2
{a: 1, **rest} = {a: 1, b: 2, c: 3}  # a is bound to 1, and rest is bound to {b: 2, c: 3}

# Matching against objects
Point = Struct.new(:x, :y)
point = Point.new(1, 2)
Point.new(x, y) = point  # x is bound to 1, y is bound to 2
Enter fullscreen mode Exit fullscreen mode

Give it a try! You won’t regret it!


If you liked this post, consider subscribing to my newsletter Bit Maybe Wise.

You can also follow me on Twitter and Mastodon.


Photo by Georg Eiermann on Unsplash

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .