Empower Pattern Matching in Ruby
2020-04-10
RubyFunctional ProgrammingRuby2.7 provides Pattern Matching feature as an experimental one.
https://www.ruby-lang.org/en/news/2019/12/25/ruby-2-7-0-released/
I used to do pattern matching in Scala, so I'm excited to be able to use it in Ruby!
tl;dr
Rstructural Gem what I've been developing lets us coding more efficiently with pattern matching in Ruby.
What is Pattern Match?
This deck desribes it precisely.
It's enough to know what is pattern match in Ruby, however, let me give some examples as followings,
case [1, 2, [3, [4, 5]]]
in [a, b, [*c]]
puts "a+b: #{a+b}, rest:#{c}"
end
# => a+b: 3, rest:[3, [4, 5]]
case {a: 1, b: 2, c: {d: 3, e: [4, 5]}}
in {a:, c: {d:, e: [4, x]}}
puts "a: #{a}, c: #{c}, d: #{d}, x: #{x}"
end
# => a: 1, c: [3, [4, 5]], d: 3, x: 5
module HttpStatus
OK = 200
NotFound = 404
InternalServerError = 500
end
case 500
in HttpStatus::OK
puts "OK!"
in HttpStatus::NotFound | HttpStatus::InternalServerError
puts "NG!"
in unknown
puts "Unknown: #{unknown}"
end
# => NG!
Pattern Match brings a capability of intuitive expression to represent what code does.
Empower Pattern Matching
As you know, pattern matching is excellent. However, I'm eager to write Ruby codes more functional way as I do with Scala. Case class in Scala is to represent algebraic data types, and absolutely useful to write intuitive and expressive codes, especially when combining with pattern matching.
Thus, I import a kind of algebraic data type into the Ruby world.
It is Rstructural gem.
GitHub: https://github.com/petitviolet/rstructural
Rstructural provides Enum and Algebraic Data Type(ADT), and also applied types(Option, Either).
Enum
The simplest ADT is Enum, I think.
Unfortunately, Ruby does not have enum as default. On the other hand, Rails has ActiveRecord::Enum, but it's for ActiveRecord
as its name says so that the Enum is not for define data structure in pure Ruby world.
Rstructural provides Enum
to define enumerated values.
It looks like:
require 'rstructural'
module HttpStatus
extend Enum
OK = enum 200
NotFound = enum 404
InternalServerError = enum 500
end
case HttpStatus.of(500) # factory
in HttpStatus::OK
puts "OK!" # => OK!
in HttpStatus::NotFound | HttpStatus::InternalServerError
puts "NG!"
in unknown
puts "Unknown: #{unknown}"
end
HttpStatus.of(500).is_a?(HttpStatus)
# => true
Each enum value has a constant, and we can use them as a set of constants, HttpStatus
in this example.
Also, extending Enum
injects a factory method of
into the module. It brings us a sort of type-sensitive programming style.
Another merit of using Enum
is eliminating duplicated values, as:
module Status
extend Enum
OK = enum 1
NG = enum 2
Unknown = enum 2
end
# => ArgumentError (Enum '2' already defined in Struct)
This feature protects us from defining wrong constants.
ADT
Rstructural::ADT
brings characteristics of algebraic data type into Ruby module.
An example to define ADTs:
module Shape
extend ADT
Point = const
Circle = data :radius
Rectangle = data :width, :height do
def square?
width == height
end
end
interface do
def area
case self
in Point
0
in Circle[radius]
3.14 * radius * radius
in Rectangle[w, h]
w * h
end
end
end
end
rectangle = Shape::Rectangle.new(3, 4)
rectangle.area
# => 12
rectangle.square?
# => false
rectangle.is_a?(Shape)
# => true
An ADT
injected module has const
and data
to define data types.
const
is to define a constant and look like a singleton object.- is similar to
object
in Scala
- is similar to
data :field_a, :field_b, ...
is to define a type that holds given fields- is similar to
case class
in Scala
- is similar to
A combination of const
and data
in ADT
module is similar to the one of object
, case class
, and sealed trait
in Scala.
As the example shows, it allows to define instance methods at the declaration of types and also give a block to interface
.
Using ADT
brings capabilities to manage a set of data types intuitively and type sensitive way as well as Enum
.
Additionally, Rstructural gem provides Option
and Either
based on ADT
. Please have a look if you are interested in.
Wrapping Up
I believe this kind of type abstraction would help you if you are familiar with functional programming way whether or not programming language provides static analysis.