
Refactoring Ruby with Monads - wasd
http://codon.com/refactoring-ruby-with-monads
======
dragonwriter
This should be "Refactoring Ruby with Applicative Functors". The common
interface for the so-called "monads" here is a subset of that of an
applicative functor [1]; the fact that the types discussed could also support
monad definitions (and that in some other languages, the applicative functor
definition would piggy back on the monad definition) isn't really relevant.

[1] Comparing the listed Ruby to Haskell, .from_value is "pure", #within is
"fmap", and there's no real equivalent of <*>.

~~~
rntz
`#and_then` is the monadic bind operation `>>=`; applicatives don't have this
operation in general.

------
zimbatm
Recently I benchmarked a small network server written in Haskell. During the
load testing the GHC runtime would allocate at rates of 1.6GB/s which is
highly unusual from my experience but is apparently normal for pure functional
languages. The impressive bit is that during the operations the heap was never
bigger than 50MB and GC pauses longer than 100ms.

If we where to re-write all our ruby code to be purely functional I don't
think that CRuby GC would keep up very long with that kind of memory pressure.

~~~
imanaccount247
>but is apparently normal for pure functional languages

Where did you hear that?

>and GC pauses longer than 100ms.

Yikes! That is not normal at all. You wouldn't be able to write a decent
webserver in haskell if that were normal.

~~~
zimbatm

        > but is apparently normal for pure functional languages
        Where did you hear that?
    

It's certainly true for Clojure as well. My guess is that it applies to any
language that uses immutable data structures. I remember Rich Hickey talking
about issues with the JVM due to clojure's unusual allocation model in one of
his talks.

~~~
imanaccount247
It may well be true for clojure, but it is not for haskell. I know both
clojure and scala have issues due to assumptions in the JVM execution model
that don't play well with functional programming, but ghc was written
explicitly just for haskell.

------
tcopeland
I don't plan on rewriting my Rails apps, but the talk is certainly food for
thought. At the very least this is a nice explanation of Ruby metaprogramming
techniques; pair it with Paolo Perrotta's "Metaprogramming Ruby" and you'll
have a good handle on the subject.

------
aftbit
For the author's toy example, I don't see how this is better than using
begin... rescue.

    
    
      def weather_for(project)
        begin
          project.creator.address.
            country.capital.weather
        rescue NoMethodError
          nil
        end
      end

~~~
nshepperd
If that does what it looks like, then Optional is better than that in one
respect: begin/rescue will catch _all_ NoMethodErrors, including unrelated
ones. But you probably _do_ want an error to be thrown if you typo and write
"project.creator.adress" by mistake.

------
jhund
Alternatively one could use NullObjects for the Optional use case:
[http://devblog.avdi.org/2011/05/30/null-objects-and-
falsines...](http://devblog.avdi.org/2011/05/30/null-objects-and-falsiness/)

------
techdebt5112
how badly does this destroy your stack traces?

------
hywel
This is an excellent article but one of its main achievements is to make Ruby
work like PHP (swallowing nil errors). The difference between try and
Eventually isn't just that try is on every Object, but that the calling code
gets to decide when a nil is unexpected. Personally, I'd rather get errors
from nils which I can fix, than swallowed nil values that I'll probably never
know about.

------
vinceguidry
Please don't actually do this. Ruby is not a functional language, if your
project really needs to be functional all the way through just use Haskell or
whatever. Otherwise just pull out the parts of your code would benefit from
being expressed in functional style into a module and work on implementing a
proper DSL.

~~~
actsasbuffoon
Monads are useful even in imperative languages. They're just a context with a
way of specifying how actions get chained together. Scala, Swift, and a number
of other imperative languages have adopted monads (like Maybe) to great
effect.

Conversely, the state monad (for example) has seen less adoption. Probably
because it's a little difficult to grasp, and imperative languages just rely
on mutable state for a similar effect. The state monad provides some nifty
extras above and beyond variables as they appear in imperative languages, but
you can't argue with the low learning curve of throwing a value into a
variable and mutating it.

As for the article, I really like the API presented here. I'd worry about the
performance penalty of sprinkling method_missing throughout my code, but the
code clarity is phenomenal.

~~~
dragonwriter
> Scala, Swift, and a number of other imperative languages have adopted monads
> (like Maybe) to great effect.

Much of the use of "monads" in many other languages -- and particularly all
the examples here -- really rely only on that subset of monad functionality
that defines applicative functors.

> Conversely, the state monad (for example) has seen less adoption.

The use of the state monad really relies on it being a _monad_ and not a mere
applicative functor; I'm not sure if that's related, but I think that
applicative functors are an easier thing to wrap your head around than monads.

~~~
nshepperd
No. #and_then here is monadic bind (>>=), and is necessary for all three use
cases he described - nested nil checks, nested iteration, nested IO
actions/callbacks.

------
jmcgough
for those who haven't seen the talk, he says during it that he's not actually
advocating that people implement this in their codebase :p

~~~
SEMW
As someone currently maintaining a Ruby codebase originally written by the
author, I winced at that ;)

~~~
tomstuart
I apologise for that codebase! Let’s just say I’ve learned a lot about
building Rails apps since 2007. ;)

