Last active
December 24, 2015 04:09
-
-
Save zobar/6741900 to your computer and use it in GitHub Desktop.
Option monad in Ruby
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
.rbenv-gemsets |
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
2.0.0-p247 |
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 'singleton' | |
def Option(value) | |
value.nil? ? None() : Some(value) | |
end | |
def None | |
None.instance | |
end | |
def Some(value) | |
Some.new value | |
end | |
class Option | |
include Enumerable | |
class NoSuchElementException < StandardError; end | |
def defined? | |
!empty? | |
end | |
def or_nil | |
get_or_else { nil } | |
end | |
end | |
class None < Option | |
include Singleton | |
def ==(other) | |
other.inject(true) { false } | |
end | |
def all? | |
true | |
end | |
def empty? | |
true | |
end | |
def any? | |
false | |
end | |
def collect | |
self | |
end | |
def collect_concat | |
self | |
end | |
def each | |
self | |
end | |
def find_all | |
self | |
end | |
alias_method :flat_map, :collect_concat | |
def flatten | |
self | |
end | |
def get | |
raise NoSuchElementException, 'None#get' | |
end | |
def get_or_else | |
yield | |
end | |
def include?(obj) | |
false | |
end | |
def inject(initial=nil, sym=nil) | |
initial if sym.nil? || block_given? | |
end | |
alias_method :map, :collect | |
alias_method :member?, :include? | |
def or_else | |
yield | |
end | |
alias_method :reduce, :inject | |
def reject | |
self | |
end | |
alias_method :select, :find_all | |
def to_a | |
[] | |
end | |
end | |
class Some < Option | |
attr_reader :get | |
def ==(other) | |
other.include? get | |
end | |
def all? | |
yield get | |
end | |
def any? | |
yield get | |
end | |
def collect | |
Some(yield get) | |
end | |
def collect_concat | |
yield get | |
end | |
def each | |
yield get | |
self | |
end | |
def empty? | |
false | |
end | |
def find_all | |
(yield get) ? self : None() | |
end | |
alias_method :flat_map, :collect_concat | |
def flatten | |
get | |
end | |
def get_or_else | |
get | |
end | |
def include?(obj) | |
obj == get | |
end | |
def initialize(value) | |
@get = value | |
end | |
def inject(initial=nil, sym=nil) | |
if sym.nil? | |
initial.nil? || !block_given? ? get : (yield initial, get) | |
else | |
initial.send sym, get | |
end | |
end | |
alias_method :map, :collect | |
alias_method :member?, :include? | |
def or_else | |
self | |
end | |
alias_method :reduce, :inject | |
def reject | |
(yield get) ? None() : self | |
end | |
alias_method :select, :find_all | |
def to_a | |
[get] | |
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
#!/usr/bin/env ruby -w | |
require 'minitest/autorun' | |
require 'minitest/spec' | |
require_relative 'option.shard' | |
describe Option do | |
subject { Option(value) } | |
let(:mapped_value) { Object.new } | |
let(:must_not_call) { proc { raise 'The block should not have been called.' } } | |
let(:null_function) { -> { mapped_value } } | |
let(:option_map_function) do | |
->(obj) do | |
obj.must_equal subject | |
Option(mapped_value) | |
end | |
end | |
let(:map_function) do | |
->(obj) do | |
obj.must_equal value | |
mapped_value | |
end | |
end | |
describe None do | |
let(:value) { nil } | |
describe '#==' do | |
it 'equals itself' do | |
subject.must_equal None() | |
end | |
it 'does not equal Some' do | |
subject.wont_equal Some(value) | |
end | |
end | |
describe '#all?' do | |
it 'returns true' do | |
subject.all?(&must_not_call).must_equal true | |
end | |
end | |
describe '#any?' do | |
it 'returns false' do | |
subject.any?(&must_not_call).must_equal false | |
end | |
end | |
describe '#collect' do | |
it 'is None' do | |
subject.collect(&must_not_call).must_equal None() | |
end | |
end | |
describe '#collect_concat' do | |
it 'is None' do | |
subject.collect_concat(&must_not_call).must_equal None() | |
end | |
end | |
describe '#defined?' do | |
it 'is not defined' do | |
subject.defined?.must_equal false | |
end | |
end | |
describe '#each' do | |
it 'does not yield' do | |
subject.each(&must_not_call) | |
end | |
end | |
describe '#empty?' do | |
it 'is empty' do | |
subject.must_be_empty | |
end | |
end | |
describe '#find_all' do | |
it 'is None' do | |
subject.find_all(&must_not_call).must_equal None() | |
end | |
end | |
describe '#flatten' do | |
it 'returns none' do | |
subject.flatten.must_equal None() | |
end | |
end | |
describe '#get_or_else' do | |
it 'returns the result of the block' do | |
subject.get_or_else(&null_function).must_equal mapped_value | |
end | |
end | |
describe '#include?' do | |
it 'returns false' do | |
subject.wont_include value | |
end | |
end | |
describe '#or_else' do | |
it 'returns the result of the block' do | |
subject.or_else(&null_function).must_equal mapped_value | |
end | |
end | |
describe '#or_nil' do | |
it 'returns nil' do | |
subject.or_nil.must_equal nil | |
end | |
end | |
describe '#reject' do | |
it 'is None' do | |
subject.reject(&must_not_call).must_equal None() | |
end | |
end | |
describe '#to_a' do | |
it 'is an empty array' do | |
subject.to_a.must_equal [] | |
end | |
end | |
end | |
describe Some do | |
let(:value) { Object.new } | |
describe '#==' do | |
it 'equals an equivalent Some' do | |
subject.must_equal Some(value) | |
end | |
it 'does not equal a different Some' do | |
subject.wont_equal Some(mapped_value) | |
end | |
end | |
describe '#all?' do | |
it 'returns the block result' do | |
(!!subject.all?(&map_function)).must_equal !!mapped_value | |
end | |
end | |
describe '#any?' do | |
it 'returns the block result' do | |
(!!subject.any?(&map_function)).must_equal !!mapped_value | |
end | |
end | |
describe '#collect' do | |
it 'is an option of the block result' do | |
subject.collect(&map_function).must_equal Option(mapped_value) | |
end | |
end | |
describe '#collect_concat' do | |
it 'is the block result' do | |
Option(subject).collect_concat(&option_map_function).must_equal Option(mapped_value) | |
end | |
end | |
describe '#defined?' do | |
it 'is defined' do | |
subject.defined?.must_equal true | |
end | |
end | |
describe '#each' do | |
it 'yields' do | |
yielded = nil | |
subject.each { |obj| yielded = obj } | |
yielded.must_equal value | |
end | |
end | |
describe '#empty?' do | |
it 'is not empty' do | |
subject.wont_be_empty | |
end | |
end | |
describe '#find_all' do | |
it 'is None if block returns false' do | |
subject.find_all { false }.must_equal None() | |
end | |
it 'is Some if block returns true' do | |
subject.find_all { true }.must_equal Option(value) | |
end | |
end | |
describe '#flatten' do | |
it 'returns the value' do | |
Option(subject).flatten.must_equal subject | |
end | |
end | |
describe '#get_or_else' do | |
it 'returns the value' do | |
subject.get_or_else(&must_not_call).must_equal value | |
end | |
end | |
describe '#include?' do | |
it 'returns true if the parameter is the option value' do | |
subject.must_include value | |
end | |
it 'returns false if the parameter is not the option value' do | |
subject.wont_include mapped_value | |
end | |
end | |
describe '#or_else' do | |
it 'returns the value' do | |
subject.or_else(&must_not_call).must_equal Option(value) | |
end | |
end | |
describe '#or_nil' do | |
it 'returns the value' do | |
subject.or_nil.must_equal value | |
end | |
end | |
describe '#reject' do | |
it 'is Some if block returns false' do | |
subject.reject { false }.must_equal Option(value) | |
end | |
it 'is None if block returns true' do | |
subject.reject { true }.must_equal None() | |
end | |
end | |
describe '#to_a' do | |
it 'contains only the option value' do | |
subject.to_a.must_equal [value] | |
end | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment