• Stars
    star
    302
  • Rank 138,030 (Top 3 %)
  • Language
    Ruby
  • License
    Other
  • Created about 16 years ago
  • Updated over 7 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

The Maybe Monad in idiomatic Ruby

Object#andand

What

Object#andand lets us write:

@phone = Location.find(:first, ...elided... ).andand.phone

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.

As Dejan Simic put it:

Why would you want to write this:

entry.at('description') && entry.at('description').inner_text

when you can write this:

entry.at('description').andand.inner_text

Why indeed! As a bonus, install andand and you will also receive an enhanced Object#tap method, at no extra charge!

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-andand

Or:

git clone git://github.com/raganwald/andand.git
cd andand
rake gem
rake install_gem

The basics

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:

first_name &&= @first_name.trim
@phone ||= '612-777-9311'

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:

@phone = Location.find(:first, ...elided... )&&.phone

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.

Object#andand letā€™s us write:

@phone = Location.find(:first, ...elided... ).andand.phone

And get the same effect as:

@phone = ->(loc){ loc && loc.phone }.call(Location.find(:first, ...elided... ))

Note that because you accept any method using Rubyā€™s method invocation syntax, you can accept methods with parameters and/or blocks:

list_of_lists.detect { ...elided... }.andand.inject(42) { ...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.

Block-Structured andand

You can use andand with a block instead of a method:

@phone =  Location.find(:first, ...elided... ).andand { |location|
  YellowPages.reverse_lookup(location).phone
}

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.

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):

x = nil
x.andand.length > 3

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 { |value| value.length > 3 }

Enhanced Object#tap

Ruby 1.9 introduces Object#tap. This library implements Object#tap for Ruby 1.8 and enhances it. As in Ruby 1.9, you can call .tap with a block:

	blah.sort.grep( /foo/ ).tap { |xs| p xs }.map { |x| x.blah }
But like its sibling .andand, you can now call .tap with a method as well:
	[1, 2, 3, 4, 5].tap.pop.map { |n| n * 2 }
    	=> [2, 4, 6, 8]

Doctor, it hurts when I do that

So donā€™t do that!

The popular use case for Object#tap is poor manā€™s debugging:

	blah.sort.grep( /foo/ ).tap { |xs| p xs }.map { |x| x.blah }

Perhaps you want to remove the tap, you can delete it:

	blah.sort.grep( /foo/ ).map { |x| x.blah }

Or, you can change it to .dont:

	blah.sort.grep( /foo/ ).dont { |xs| p xs }.map { |x| x.blah }

Like .andand and .tap, .dont works with arbitrary methods, not just blocks:

	(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]

A little more background

Object#andand & Object#me in Ruby 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 is a good introduction for Ruby programmers.

Thatā€™s cool, butā€¦

No problem, I get that andand isnā€™t exactly what you need. Have a look at the Invocation Construction Kit 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 provides abstractions for building your own invocations, so you can branch out and build some of your own abstractions with Ickā€™s building blocks.

How to submit patches

Read the 8 steps for fixing other peopleā€™s code.

The public clone url is git://github.com/raganwald/andand.git. Fork you very much.

License

This code is free to use under the terms of the MIT license.

Shout Out

Mobile Commons. Huge.

Also interesting: Wicked

Contact

Comments are welcome. Send an email to Reginald Braithwaite.

More Repositories

1

javascript-allonge

Markdown source for the book "JavaScript AllongƩ"
JavaScript
758
star
2

presentations

Conference Talks and Proposals
751
star
3

allong.es

JavaScript
486
star
4

JQuery-Combinators

The jQuery plugin with the academic name and the pragmatic methods
JavaScript
213
star
5

YouAreDaChef

Coffeescript/Javascript method combinations for Underscore projects
CoffeeScript
183
star
6

Katy

CoffeeScript and JavaScript Combinators
CoffeeScript
172
star
7

method-combinators

CoffeeScript
120
star
8

javascript-allonge-six

https://leanpub.com/javascriptallongesix
JavaScript
104
star
9

hashlife

JavaScript
58
star
10

oscin.es

n. pl. 1. (Zool.) Singing birds; a group of the Passeres, having numerous syringeal muscles, conferring musical ability
JavaScript
55
star
11

javascript-spessore

Manuscript for JavaScript Spessore
JavaScript
49
star
12

Underscore-Matchers-for-Jasmine

CoffeeScript
39
star
13

raganwald.github.com

raganwald.com jekyll source
HTML
31
star
14

cafeaulife

Gosperā€™s HashLife in CoffeeScript
CoffeeScript
30
star
15

wood_and_stones

Use your iPad as a Goban
JavaScript
29
star
16

combinators.info

JavaScript
23
star
17

jQuery-Predicates

.exists() and does_not_exist() for jQuery
JavaScript
16
star
18

ristrettolo.gy

jekyll source for http://ristrettolo.gy
JavaScript
10
star
19

FRACTRAN

Code to accompany ā€œ Remembering John Conway's FRACTRAN, a ridiculous, yet surprisingly deep languageā€
JavaScript
10
star
20

supervis.es

CoffeeScript
9
star
21

string-lambdas

String lambdas for JavaScript and CoffeeScript
CoffeeScript
8
star
22

braythwayt.com

Reg Braithwaite's old weblog plus the odd non-technical blow hardiness
HTML
7
star
23

the-little-elixir-book

Fun with "The Little Elixir and OTP Guidebook"
Elixir
6
star
24

presentation_decks

Presentation decks from a few speaking engagements
6
star
25

raganwald.posterous.com

The content from my posterous blog, somewhat
5
star
26

mazerunner

Builds a maze in ES6 using Eller's Algorithm as described by Jamis Buck.
JavaScript
5
star
27

AMA

A lightweight ā€œAsk me anythingā€ repository inspired by @holman and @r00k
5
star
28

still-failing-still-learning-still-optimistic

3
star
29

stata

Read and write support for the Stata binary format
C
2
star
30

MacOSMendel

Java
1
star
31

shunting-yard

The collected shunting-yard code from raganwald.com
JavaScript
1
star
32

shunting-yard-dot-rb

A toy shunting yard implementation in Ruby
Ruby
1
star
33

kato

CoffeeScript-style OO for JavaScript
1
star