Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding scala-style matching #4

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 77 additions & 1 deletion lib/option.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,88 @@
module OptionHelpers
class OptionMatcher
attr_reader :return_value

def initialize(option)
@option = option
@return_value = nil
end

def case(type)
if type.is_a?(OptionType)
if @option.present?
option_val = @option.get
if option_val.is_a?(type.type)
@return_value = yield(option_val)
end
end
elsif type == SomeClass
if @option.present?
@return_value = yield(@option.get)
end
elsif type.is_a?(NoneClass)
if [email protected]?
@return_value = yield(@option)
end
else
raise TypeError, "can't match an Option against a #{type.to_s}"
end
end

def else
@return_value = yield(@option)
end
end

class OptionType
attr_reader :type

def initialize(type)
@type = type
end

class << self
def for_class(klass)
case klass
when Class
option_type_cache[klass] ||= OptionType.new(klass)
else
raise TypeError, "Must be a Class"
end
end

private

def option_type_cache
@option_type_cache ||= {}
end
end
end
end

class OptionClass

def or_nil
end

class << self
def [](type)
OptionHelpers::OptionType.for_class(type)
end
end

def ==(that)
case that
when OptionClass then or_nil == that.or_nil
else or_nil == that
end
end

def match
matcher = OptionHelpers::OptionMatcher.new(self)
yield matcher
matcher.return_value
end

private

def assert_option(result)
Expand Down Expand Up @@ -102,6 +175,9 @@ def error(*argv)
end

class NoneClass < OptionClass
def [](type)
raise TypeError, "can't specify a type of NoneClass"
end

def dup
raise TypeError, "can't dup NoneClass"
Expand Down Expand Up @@ -187,6 +263,6 @@ def Some(value)
SomeClass.new(value)
end

def Option(value)
def Option(value=nil)
value.nil? ? None : Some(value)
end
53 changes: 53 additions & 0 deletions spec/option_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@
it "should assemble an Error from the arguments passed in" do
lambda { None.error(StandardError, "this is a problem") }.must_raise StandardError, "this is a problem"
end

it "cannot be typed" do
lambda { None[String] }.must_raise TypeError, "can't specify a type of NoneClass"
end
end

describe SomeClass do
Expand Down Expand Up @@ -212,6 +216,16 @@
value = !!(Some(value).error("error") rescue false)
value.must_equal true
end

it "can be typed" do
opt = Some[String]
opt.class.must_equal OptionHelpers::OptionType
opt.type.must_equal String
end

it "must be typed using a class, not a value" do
lambda { Some[5] }.must_raise TypeError, "Must be a Class"
end
end

describe OptionClass do
Expand All @@ -227,4 +241,43 @@
it "should do equality checks against the boxed value" do
Option(value).must_equal(value)
end

it "allows matching typed options" do
Option(5).match do |matcher|
matcher.case(Some[Fixnum]) { |val| val * 2 }
matcher.case(Some[String]) { |val| "bar" }
end.must_equal 10

Option("foo").match do |matcher|
matcher.case(Some[Fixnum]) { |val| val * 2 }
matcher.case(Some[String]) { |val| "bar" }
end.must_equal "bar"
end

it "allows matching untyped options" do
Option(5).match do |matcher|
matcher.case(Some) { |val| val * 2 }
end.must_equal 10
end

it "allows matching on none" do
None.match do |matcher|
matcher.case(None) { "none" }
end.must_equal "none"
end

it "allows matching with an else block" do
Option(5).match do |matcher|
matcher.case(Some[String]) { |val| "bar" }
matcher.else { |val| val.get * 2}
end.must_equal 10
end

it "does not allow matching against non-option types" do
lambda do
Option(5).match do |matcher|
matcher.case(Fixnum) { 1 }
end
end.must_raise TypeError, "can't match an Option against a Fixnum"
end
end