Last active
December 25, 2015 12:49
-
-
Save britishtea/6979731 to your computer and use it in GitHub Desktop.
Simple and possibly broken pattern matching in Ruby. Proof of Concept.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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