In order to better understand the various monads available in Haskell, I've been re-implementing them in Ruby. Thus far I've done Identity, List (well Array), Maybe and State. Today I'm going to show you the Identity monad. A monad is a framework of sorts for applying rules to a series of computations. A monad has at least two operations, bind and return. return takes a non monadic value and converts it to a monadic one, it has type:
(Monad m) => a -> m a
(I'm using Haskell type notation here because ruby doesn't have type notation ;) )
Bind takes a monadic value and de-monadifies it to feed it into a function that returns a monadic value. It has the type:
(Monad m) => m a -> (a -> m b) -> m b
Bind is where the magic happens. Haskell uses it's type system to ensure that every sequence of computations in a given monad goes through bind. Bind therefore lets the writer of the monad decide the rules for the little monadic world within a given program. (This is how Haskell deals with side-effects (IO) ).
So now without further ado, I present the Identity monad:
class Identity
def initialize( val )
@val = val
end
def self.m_return( a )
Identity.new( a )
end
def self.m_bind(id_a, &f)
f.call( id_a.val )
end
def val
@val
end
end
Short and sweet. All you can really do with an Identity monad is force evaluation order. Ruby is imperative so that doesn't really matter.
Here's some code using Identity:
Identity.m_bind( Identity.m_return( 1 ) ) do |x| # x is 1, we've sucked it out of the Monad.
Identity.m_return( x + 1 )
end
This is quite verbose. In Haskell it would be return 1 >>= (\x -> return x + 1), where >>= is bind and (\x -> ... ) is analogous to lambda { |x| ... }. Haskell also has some syntactic sugar for monadic expressions like this. Using the the syntactic sugar it would look like:
let i = return 1 in
do x <- i
return x + 1
The real difference of course is the type checking. If I wrote return 1 >>= (\x -> x + 1) in Haskell it would not compile where ruby cares not a whit if we want to escape our monads. That combined with the fact that unlike Haskell we have no syntactic sugar for monads means it's going to be difficult to debug our ruby implementations. Hopefully I've whet your appetite, next time we'll tackle the Maybe monad, that allows us to handle computations that might fail.
3 comments:
I've done a similar thing in Java, but not just as an exercise - I use Maybe and Either in production code.
Here's the functional stuff - http://code.google.com/p/functionalpeas/
In Java, the lack of dynamic dispatch (and closures) makes for more verbosity.
Your blog keeps getting better and better! Your older articles are not as good as newer ones you have a lot more creativity and originality now keep it up!
Hi Logan,
Why don't you check out the Rumonade gem:
https://github.com/ms-ati/rumonade
It implements Monad as a Ruby mix-in, and uses it to extend Array in (what I hope is) a natural way. It also provides Option and Either classes modeled after Scala.
Like a lot of folks, I enjoy working in Ruby but also have professional exposure to Scala and FunctionalJava. After using monads in those other languages, and it just seems silly to use Ruby without these sorts of abstractions.
Post a Comment