Skip to content

Instantly share code, notes, and snippets.

@zobar
Last active December 24, 2015 04:09
Show Gist options
  • Save zobar/6741900 to your computer and use it in GitHub Desktop.
Save zobar/6741900 to your computer and use it in GitHub Desktop.
Option monad in Ruby
.rbenv-gemsets
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
#!/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