Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/raganwald/andand
The Maybe Monad in idiomatic Ruby
https://github.com/raganwald/andand
Last synced: 2 days ago
JSON representation
The Maybe Monad in idiomatic Ruby
- Host: GitHub
- URL: https://github.com/raganwald/andand
- Owner: raganwald
- License: other
- Created: 2008-11-01T12:55:42.000Z (about 16 years ago)
- Default Branch: master
- Last Pushed: 2017-02-28T21:25:11.000Z (almost 8 years ago)
- Last Synced: 2024-12-15T22:02:54.342Z (9 days ago)
- Language: Ruby
- Homepage:
- Size: 142 KB
- Stars: 301
- Watchers: 7
- Forks: 30
- Open Issues: 4
-
Metadata Files:
- Readme: README.textile
- Changelog: History.txt
- License: License.txt
Awesome Lists containing this project
README
h1. Object#andand
h2. What
@Object#andand@ lets us write:
And get a _guarded method invocation_ or _safe navigation method_. This snippet performs a @.find@ on the Location class, then sends @.phone@ to the result _if the result is not nil_. If the result is nil, then the expression returns nil without throwing a NoMethodError.
@phone = Location.find(:first, ...elided... ).andand.phoneAs Dejan Simic "put it":http://rors.org/2008/3/18/andand:
Why would you want to write this:
when you can write this:
entry.at('description') && entry.at('description').inner_textWhy indeed! As a bonus, install andand and you will also receive an enhanced Object#tap method, _at no extra charge_!
entry.at('description').andand.inner_texth2. Installing
Update to RubyGems 1.2.0 before proceeding!!
sudo gem sources -a http://gems.github.com (you only have to do this once)
sudo gem install raganwald-andandOr:
git clone git://github.com/raganwald/andand.git
cd andand
rake gem
rake install_gemh2. The basics
h3. Object#andand
Ruby programmers are familiar with the two _guarded assignment_ operators @&&=@ and @||=@. The typical use for them is when you have a variable that might be nil. For example:
You are trimming the first name provided it isn’t nil, and you are assigning ‘612-777-9311’ to the phone if it _is_ nil (or false, but that isn’t important right now). The other day we were discussing the guards and we agreed that we wished there was a _guarded method invocation_ operator. Here’s an example of when you would use it:
first_name &&= @first_name.trim
@phone ||= '612-777-9311'Meaning, search the location table for the first record matching some criteria, and if you find a location, get its phone. If you don’t, get nil. (Groovy provides this exact functionality, although Groovy uses @?.@ instead of @&&.@) However, @&&.@ won’t work because @&&.@ is not a real Ruby operator.
@phone = Location.find(:first, ...elided... )&&.phoneObject#andand let’s us write:
And get the same effect as:
@phone = Location.find(:first, ...elided... ).andand.phoneNote that because you accept any method using Ruby’s method invocation syntax, you can accept methods with parameters and/or blocks:
@phone = ->(loc){ loc && loc.phone }.call(Location.find(:first, ...elided... ))Object#andand emphasizes syntactic regularity: the goal was to make an @&&.@ operation that worked like @&&=@. @&&=@ looks just like normal assignment, you can use any expression on the RHS, only the semantics are different. The andand method also works just like a normal method invocation, only the semantics are modified.
list_of_lists.detect { ...elided... }.andand.inject(42) { ...elided ... }h3. Block-Structured andand
You can use @andand@ with a block instead of a method:
If the receiver is nil, the block is not executed and @andand@ returns @nil@. If the receiver is not nil, it is passed as a parameter to the block and @andand@ returns the value of the block. This makes it possible to perform conditional evaluation and also to make the scope of the variable really obvious.
@phone = Location.find(:first, ...elided... ).andand { |location|
YellowPages.reverse_lookup(location).phone
}h3. Scope
@Object#andand@ only works for one method call. For example, @fu.andand.bar.blitz@ is going to call @nil.blitz@ if @fu@ is @nil@. That's because @fu.andand.bar@ is going to evaluate to @nil@, and then we will call the method @blitz@ on it. In most cases, you want to use @fu.andand.bar.andand.blitz@. If that seems awkward, you might want to reconsider violating the Law of Demeter in an environment where you can't be sure if your receiver is @nil@ or not.
Another example of this (pointed out by Jan Zimmek):
This results in a @NoMethodError@. Again, @x@ is @nil@, therefore @x.andand.length@ is @nil@, therefore we end up with @nil > 3@ which results in a @NoMethodError@. This can be fixed with @x.andand.length.andand > 3@ as above, or perhaps:
x = nil
x.andand.length > 3
x = nil
x.andand { |value| value.length > 3 }h3. Enhanced Object#tap
Ruby 1.9 introduces "Object#tap":http://moonbase.rydia.net/mental/blog/programming/eavesdropping-on-expressions. This library implements Object#tap for Ruby 1.8 *and* enhances it. As in Ruby 1.9, you can call @.tap@ with a block:
But like its sibling @.andand@, you can now call @.tap@ with a method as well:
blah.sort.grep( /foo/ ).tap { |xs| p xs }.map { |x| x.blah }
[1, 2, 3, 4, 5].tap.pop.map { |n| n * 2 }
=> [2, 4, 6, 8]h3. Doctor, it hurts when I do that
_So don't do that!_
The popular use case for Object#tap is poor man's debugging:
Perhaps you want to remove the tap, you can delete it:
blah.sort.grep( /foo/ ).tap { |xs| p xs }.map { |x| x.blah }Or, you can change it to @.dont@:
blah.sort.grep( /foo/ ).map { |x| x.blah }Like @.andand@ and @.tap@, @.dont@ works with arbitrary methods, not just blocks:
blah.sort.grep( /foo/ ).dont { |xs| p xs }.map { |x| x.blah }
(1..10).to_a.reverse!
=> [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
(1..10).to_a.dont.reverse!
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]h2. A little more background
"Object#andand & Object#me in Ruby":http://weblog.raganwald.com/2008/01/objectandand-objectme-in-ruby.html explains the original motivations, as well as providing links to similar implementations you may want to consider. A few people have pointed out that Object#andand is similar to Haskell's Maybe monad. "The Maybe Monad in Ruby":http://blog.pretheory.com/arch/2008/02/the_maybe_monad_in_ruby.php is a good introduction for Ruby programmers.
h2. That's cool, but…
No problem, I get that andand isn't exactly what you need. Have a look at the "Invocation Construction Kit":http://ick.rubyforge.org or "Ick." The Ick gem _generalizes_ #andand and #tap: Ick provides four useful ways to block-structure your code, the methods #let, #returning, #inside, and #my. Ick also includes four quasi-monadic invocations, #maybe, #please, #tee, and #fork.
"Ick":http://ick.rubyforge.org provides abstractions for building your own invocations, so you can branch out and build some of your own abstractions with Ick's building blocks.
h2. How to submit patches
Read the "8 steps for fixing other people's code":http://drnicwilliams.com/2007/06/01/8-steps-for-fixing-other-peoples-code/.
The public clone url is @git://github.com/raganwald/andand.git@. Fork you very much.
h2. License
This code is free to use under the terms of the "MIT license":http://en.wikipedia.org/wiki/MIT_License.
h2. Shout Out
"Mobile Commons":http://mcommons.com/. Huge.
Also interesting: "Wicked":http://github.com/wideopenspaces/wicked
h2. Contact
Comments are welcome. Send an email to "Reginald Braithwaite":mailto:[email protected].