Skip to content

Instantly share code, notes, and snippets.

@mjhoy
Last active December 22, 2015 20:28
Show Gist options
  • Save mjhoy/6526434 to your computer and use it in GitHub Desktop.
Save mjhoy/6526434 to your computer and use it in GitHub Desktop.
an attempt at the maybe monad in Ruby
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