Refactoring Ruby with Monads
codon.com
codon.com
[1] Comparing the listed Ruby to Haskell, .from_value is "pure", #within is "fmap", and there's no real equivalent of <*>.
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.
The idea is that 90% of allocations are very short-lived, so allocating a ton doesn't matter as long as you can throw it away cheaply. Suppose we have an inner loop over an array, summing values. We may allocate at 1.6 GB/s doing the computation, but everything except the last value is garbage.
When we GC we simply copy this last value from the current heap and reset the "top of heap pointer". Yet another cheap operation.
There's a lot more going on (generational GC, nursery's and what not), but GHC's GC heavily abuse some facts that, for example, CRuby cannot, such as the fact that older generations can never reference data in newer generations, so GCing the current generation never has to perform "major GC" (i.e. checking the older data whether it still needs the current generation).
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.
> 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.And I'm sure it's possible to tune GHC. The argument is that if you're going to write immutable data structures in ruby then there is going to be more GC pressure. I don't think it's possible to optimise the ruby GC anywhere near the GHC one because of the mutable nature of the language.
def weather_for(project)
begin
project.creator.address.
country.capital.weather
rescue NoMethodError
nil
end
endInstead, he later changes weather_for to accept and return this new `Optional` construct, making it chainable, and dealing with this "if nil don't break!" in one spot and one spot only.
The only valid complaint I see against taking a functional approach to Ruby is Ruby's shitty, memory intensive object system. Fortunately it's improving with every release and hardware just keeps on getting cheaper, so at the end of the day it's not a very strong complaint.
Ruby has a way to do immutability. It has methods to do common stuff like nil-checking if you pull in ActiveSupport, something I now do on all my projects. You just don't need to inflict such a heavy-handed approach for the meagre gains it will bring. If you find yourself implementing another language in the one you're using, then it's either the entire point, as in something like Opal, or there's no point at all and you should just go use that other language you're implementing.
It's not a question of performance, it's a question of interfacing and code style. The biggest bottleneck is programmer attention, and you're doing a real disservice to the next guy down the line if you mutate Ruby's conventions this way, especially if you don't have clean interfaces written.
And I don't understand your insistence that you're "writing another language" in Ruby by using a few functional constructs. For a language that uses .each as its base iterator and encourages you to pass blocks around it seems quite fitting, in fact.
Anyways, probably going to have to agree to disagree here. I come from the "every good language is a shitty lisp" school of thought so my personal lens might just not line up with yours.
I like Ruby better than Lisp. It's hard to explain why. One of the reasons why I like to argue about this sort of thing on the Internet is because it helps me isolate the subtler qualities of different styles of programming.
I like Ruby because you can build a community around it, in a way that I don't think you can build one around Lisp, at least not like the one you find in Ruby, with the massive quantities of libraries and such.
Lisp is too free-form. You almost never need the power it gives you, and it encourages people to build their own language features where the problem being solved could probably do with a more conventional approach.
Ruby hits a sweet spot, enough dynamism to where you don't feel constrained, not so much that you can't easily reason about code. Code as data is a neat idea, but code needs to be read by humans and that's the harder task.
> Now, to be clear, I’m not saying you should immediately refactor all your Rails applications to use monads; I’m just using Ruby as a tool to explain an interesting idea about programming, not giving you a best practice for all your Ruby code. However, it’s good to know about techniques like this! And in some cases, by applying the power of monads wisely, we can untangle nested callbacks, make parts of our code more reusable, and generally make our programs better.
Rubyists are a clever bunch, if you find yourself writing the same code over and over again, chances are somebody's already figured out how to refactor it appropriately, tucking away the details behind an intention-revealing module.
A better way to refactor code is in using Ruby's built-in metaprogramming abilities. Metaprogramming Ruby is the best book to show you how.
http://www.amazon.com/Metaprogramming-Ruby-Program-Like-Face...
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.
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.
Example of a real monad: http://www.haskell.org/haskellwiki/Monad
C# is very successful with adapting bits and pieces from "other worlds" (such as FP, or dynamic typing etc.)