12 ms·
Refactoring Ruby with Monads
- vinceguidry 12y agoPlease 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.
- tibbon 12y agoStill, perhaps a good idea to help learn Monads if Ruby is your native programming language. Its helped me, as Haskell is still weird to my eyes
- colbyh 12y agoI think the point is that most projects don't need/can't be functional "all the way through". Like any sufficiently complex application many of us work on systems (or at least parts of systems) that could benefit from a functional approach. If the application happens to be written in Ruby, learning how to use and manipulate abstract data types can be rather useful. 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.
- vinceguidry 12y agoIt's a style and design problem. Ruby is designed to make it easier on the developer when he's trying to do stuff. Part of that design is being able to read the code. If I went into a codebase and saw classes being used for functional constructs instead of repositories for holding state, I'd quietly close the project down and find another gem. 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.
- colbyh 12y agoI'm sorry, but I really don't follow at all. You call this approach heavy handed when in fact it's relatively small amount of source code that makes for much more readable developer code down the line. To me, Maybe seems like a much lighter touch than monkey patching Object. 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.
- vinceguidry 12y agoI went through HtDP and loved it, it helped me gain a data-centric view on programming, and the lessons carried very well into my later Ruby career. I looked at Arc and liked the idea. 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.
- disbelief 12y agoFrom the conclusion: > 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.
- vinceguidry 12y agoYes, the author agrees with me that it's an intellectual exercise. I just wanted to underline that. 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-Facets/dp/1941222129/ref=dp_ob_title_bk http://www.amazon.com/Metaprogramming-Ruby-Program-Like-Face...
- actsasbuffoon 12y agoMonads 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.
- techdebt5112 12y agoonly in languages with first class functions.
- dragonwriter 12y ago> 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 12y agoNo. #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.
- deleted 12y ago[deleted]
- theflubba 12y ago
- V-2 12y agoJust because a language isn't functional doesn't mean it can't benefit from some goodies of the FP paradigm. C# is very successful with adapting bits and pieces from "other worlds" (such as FP, or dynamic typing etc.)
- imanaccount247 12y agoWhy? What do you think is so wrong with ruby that using techniques from FP there is terrible? Virtually every other language handles it fine, and I can't seem to find anything about ruby that would make it a problem.
- techdebt5112 12y agohow badly does this destroy your stack traces?
- tcopeland 12y agoI 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.
- dragonwriter 12y agoThis 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 12y ago`#and_then` is the monadic bind operation `>>=`; applicatives don't have this operation in general.
- aftbit 12y agoFor 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
- prezjordan 12y agoBecause you may want to chain weather_for with some other methods and have this same "if nil don't break!" strategy. We would end up putting `rescue NoMethodError` everywhere and end up right where we started. Instead, 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.
- nshepperd 12y agoIf 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.
- jmcgough 12y agofor 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
- hywel 12y agoThis 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.
- zimbatm 12y agoRecently 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 12y ago>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.
- samnardoni 12y agoI think he/she meant the GC pauses were never longer than 100ms.
- imanaccount247 12y agoYes, and getting anywhere close to 100ms is not normal at all. Warp benchmarks show total request latency never goes above 50ms or so, a 100ms gc pause would be very apparent.
- zimbatm 12y agoIt's from memory, it's possible that the major GC where around 50ms. Minor GC where around 5ms for sure.
- zimbatm 12y ago> 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.
- jhund 12y agoAlternatively one could use NullObjects for the Optional use case: http://devblog.avdi.org/2011/05/30/null-objects-and-falsiness/ http://devblog.avdi.org/2011/05/30/null-objects-and-falsines...