Last active
December 22, 2015 20:28
-
-
Save mjhoy/6526434 to your computer and use it in GitHub Desktop.
an attempt at the maybe 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
module Maybe | |
# Convenience methods | |
# | |
def Just(val) | |
Just.new(val) | |
end | |
def Nothing | |
Nothing.new | |
end | |
def just &proc | |
RunMaybe.new(just: proc) | |
end | |
def nothing &proc | |
RunMaybe.new(nothing: proc) | |
end | |
def maybe &block | |
Bind.new(block) | |
end | |
class Just | |
def initialize val | |
@val = val | |
end | |
def val | |
@val | |
end | |
def == other | |
case other | |
when Just | |
other.val == val | |
else | |
false | |
end | |
end | |
def run_maybe runner | |
runner.run_just(val) | |
end | |
def >> bind | |
bind.run_bind self.val | |
end | |
def guard *methods | |
if methods.all? { |m| val.public_send(m) } | |
self | |
else | |
Nothing.new | |
end | |
end | |
end | |
class Nothing | |
def run_maybe runner | |
runner.run_nothing | |
end | |
def == other | |
case other | |
when Nothing | |
true | |
else | |
false | |
end | |
end | |
def >> bind | |
self | |
end | |
end | |
class RunMaybe | |
def initialize opts={} | |
@just = opts[:just] | |
@nothing = opts[:nothing] | |
end | |
def just &proc | |
if @just then raise ArgumentError, "Just case already set" end | |
@just = proc | |
self | |
end | |
def nothing &proc | |
if @nothing then raise ArgumentError, "Nothing case already set" end | |
@nothing = proc | |
self | |
end | |
def all_set | |
@just && @nothing | |
end | |
def run_just val | |
if !all_set then raise ArgumentError, "Both Just and Nothing cases must be set" end | |
@just.call(val) | |
end | |
def run_nothing | |
if !all_set then raise ArgumentError, "Both Just and Nothing cases must be set" end | |
@nothing.call | |
end | |
end | |
class Bind | |
def initialize proc | |
@proc = proc | |
end | |
def proc | |
@proc | |
end | |
def >> next_bind | |
first = @proc | |
m = ->(x) do | |
case val = first.call(x) | |
when Just | |
next_bind.run_bind(val.val) | |
when Nothing | |
val | |
end | |
end | |
@proc = m | |
self | |
end | |
def run_bind val | |
value = @proc.call val | |
value | |
end | |
end | |
end | |
require 'minitest/autorun' | |
class User | |
extend Maybe | |
USERS = { | |
1 => "Rowan", | |
2 => "Elize" | |
} | |
attr_accessor :name | |
def self.find id | |
(name = USERS[id]) ? Just(User.new(name)) : | |
Nothing() | |
end | |
def initialize name | |
@name = name | |
end | |
end | |
class MaybeTest < Minitest::Unit::TestCase | |
include Maybe | |
def users_with_swapped_names uid_1, uid_2 | |
User.find(uid_1) >> | |
maybe { |user_1| | |
User.find(uid_2) >> | |
maybe { |user_2| | |
temp = user_1.name | |
user_1.name = user_2.name | |
user_2.name = temp | |
Just [user_1, user_2] | |
}} | |
end | |
def test_swap_user_valid_users | |
user1, user2 = users_with_swapped_names(1, 2). | |
run_maybe( | |
just { |users| users }. | |
nothing { [nil, nil] } | |
) | |
assert_equal User::USERS[2], user1.name | |
assert_equal User::USERS[1], user2.name | |
end | |
def test_swap_user_one_invalid_users | |
return_value = users_with_swapped_names(1, 3). | |
run_maybe( | |
just { false }. | |
nothing { true } | |
) | |
assert return_value, "Nothing expected" | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment