Skip to content

Instantly share code, notes, and snippets.

@avibryant
Last active December 16, 2015 22:19
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save avibryant/5506177 to your computer and use it in GitHub Desktop.
Save avibryant/5506177 to your computer and use it in GitHub Desktop.
Totally impractical and absurd monad syntactic sugar for Ruby, roughly modeled on Scala's for-loop syntax.
require 'continuation'
module Monad
def -@
MonadContext.bind(self)
end
def wrap(val)
raise NotImplementedError
end
def bind(&b)
raise NotImplementedError
end
end
class Maybe
include Monad
end
class Just < Maybe
def get
@val
end
def initialize(val)
@val = val
end
def wrap(val)
Just.new(val)
end
def bind(&b)
b.call(@val)
end
def to_s
"Just(#{@val})"
end
end
class None < Maybe
@@inst = self.new
def wrap(val)
@@inst
end
def bind(&b)
@@inst
end
def to_s
"None"
end
end
module Enumerable
#for some reason we have to copy this here instead of including Monad
def -@
MonadContext.bind(self)
end
def wrap(val)
take(1).map{|ignore| val}
end
def bind(&b)
flat_map(&b)
end
end
def for!(&b)
MonadContext.new.run(&b)
end
class MonadContext
def run(&b)
self.class.stack << self
begin
callcc do |cc|
@cc_stack = [cc]
@coll_stack = []
res = b.call
wrapped = @coll_stack[-1].wrap(res)
@cc_stack.pop.call(wrapped)
end
ensure
self.class.stack.pop
end
end
def bind(coll)
@coll_stack << coll
callcc do |cc|
res = coll.bind do |el|
callcc do |cc2|
@cc_stack << cc2
cc.call(el)
end
end
@coll_stack.pop
@cc_stack.pop.call(res)
end
end
def self.bind(coll)
stack[-1].bind(coll)
end
def self.stack
Thread.current[:MonadContext] ||= []
end
end
if __FILE__ == $0
res = for! do
x =- (1..5)
y =- (x..5)
z =- (y % 2 == 0 ? None.new : Just.new(1))
y + z
end
p res
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment