Skip to content

Instantly share code, notes, and snippets.

@britishtea
Last active December 25, 2015 12:49
Show Gist options
  • Save britishtea/6979731 to your computer and use it in GitHub Desktop.
Save britishtea/6979731 to your computer and use it in GitHub Desktop.
Simple and possibly broken pattern matching in Ruby. Proof of Concept.
require "pattern_matching"
# Let's abuse case statements and the case equality method (#===) to implement
# pattern matching in Ruby.
#
# Note that this'll only work in Ruby 2.1.0-dev (2.0.0-p247 and lower does not
# honour refined #=== in case statements).
using PatternMatching
def map(f, list)
case [f, list]
when [Proc, []] then []
when [Proc, Array] then [f[list.first]] + map(f, list[1..-1])
else raise "Invalid arguments"
end
end
func = -> (string) { string.upcase }
map func, ["a", "b", "c"] # => ["A", "B", "C"]
require "pattern_matching"
# Let's add some sugar now!
module Functional
module Helpers
def head(list)
list.first
end
def tail(list)
list[1..-1]
end
def cons(head, tail)
[head] + tail.dup
end
end
using PatternMatching
include Helpers
def fun(name, &body)
if method_defined? name
warn "#{self}##{name} is already defined."
end
singleton_class.send :define_method, name do |*args|
begin
@func_args = args
catch(:__got_a_match__) do
body.call args
raise TypeError
end
ensure
@func_args = nil
end
end
singleton_class.send :public, name
end
def on(*patterns, &block)
if @func_args === patterns
throw :__got_a_match__, block.call
end
end
end
# The interesting stuff begins here.
module Test
extend Functional
fun :map do |f, xs|
on(Proc, []) { [] }
on(Proc, Array) { cons f[head xs], map(f, tail(xs)) }
end
fun :append do |xs, ys|
on([], Array) { ys }
on(Array, Array) { cons head(xs), append(tail(xs), ys) }
end
fun :one do
on(_) { "one" } # _ is a wildcard
end
fun :boom do
"this will blow up"
end
end
p Test.map -> (x) { x.upcase }, ["a", "b", "c"] # => ["A", "B", "C"]
p Test.append [1, 2, 3], [4, 5, 6] # => [1, 2, 3, 4, 5, 6]
p Test.one # => "one"
p Test.boom # => TypeError (TypeError)
module PatternMatching
def self.match?(one, two)
return true if two == :__wildcard__
return true if one.is_a?(Class) && two.is_a?(one) # so Procs aren't called
return true if two === one
return true if two == one
return false
end
refine Object do
def _
:__wildcard__
end
end
refine Array do
def ===(other)
zip(other).all? { |us, them| PatternMatching.match? us, them }
end
end
end
require "minitest"
require "pattern_matching"
require "minitest/autorun"
using PatternMatching
describe "pattern matching" do
it "matches elements" do
assert ["a", "b"] === ["a", "b"]
refute ["a", "b"] === ["a", "c"]
end
it "matches classes" do
assert ["a", "b"] === [String, String ]
refute ["a", "b"] === [String, Integer]
end
# Proc#=== executes a Proc, we just want to check if they're the same type.
it "doesn't execute Procs" do
[-> (x) { $proc_called = true }] === [Proc]
refute $proc_called
end
it "matches regular expressions" do
assert ["a", "b"] === [/^.$/, /^.$/]
refute ["a", "b"] === [/^..$/, /^b$/]
end
it "matches wildcards" do
assert ["a", "b"] === [:__wildcard__, :__wildcard__]
assert ["a", "b"] === [_, _]
end
it "rocks in case statements" do
map = lambda do |f, list|
case [f, list]
when [Proc, []] then []
when [Proc, Array] then [f[list.first]] + map.(f, list[1..-1])
else raise TypeError
end
end
assert_equal map.call(-> s { s.upcase }, ["a", "b"]), ["A", "B"]
end
end
Run options: --seed 56569
# Running:
......
Finished in 0.001812s, 3311.2583 runs/s, 5518.7638 assertions/s.
6 runs, 10 assertions, 0 failures, 0 errors, 0 skips
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment