Skip to content
This repository
Newer
Older
100644 255 lines (151 sloc) 18.532 kb
4775e3ff »
2009-09-23 Added "Anaphora in Ruby"
1 Anaphora in Ruby
2 ===
3
4 > In natural language, an anaphor is an expression which refers back in the conversation. The most common anaphor in English is probably "it," as in "Get the wrench and put it on the table." Anaphora are a great convenience in everyday language--imagine trying to get along without them--but they don't appear much in programming languages. For the most part, this is good. Anaphoric expressions are often genuinely ambiguous, and present-day programming languages are not designed to handle ambiguity. --Paul Graham, [On Lisp](http://www.paulgraham.com/onlisp.html "On Lisp")
5
89d1c246 »
2009-09-29 updated do again
6 **Block anaphora**
4775e3ff »
2009-09-23 Added "Anaphora in Ruby"
7
3292cdee »
2011-12-29 better links on each page
8 Oliver Steele wrote a nice little JavaScript library called [Functional JavaScript](http://osteele.com/sources/javascript/functional/ "Functional JavaScript"). JavaScript is a particularly verbose language descended from Lisp. It's syntax for writing anonymous functions is awkward, especially for the kind of short functions that are passed to higher-level functions like `map` or `select`. Oliver decided that if you wanted to write a lot of anonymous functions, you'd better have a more succinct way to write them. So he added "String Lambdas" to JavaScript, a succinct alternate syntax for anonymous functions.
4775e3ff »
2009-09-23 Added "Anaphora in Ruby"
9
10 [String#to\_proc](http://github.com/raganwald/homoiconic/blob/master/2008-11-28/you_cant_be_serious.md "You can't be serious!?") is a port of Oliver's String Lambdas to Ruby. One of the things you can do with String#to\_proc is define a block (or a proc) that takes one parameter with an expression containing an underscore instead of explicitly naming a parameter.
11
50715bf8 »
2009-09-26 more cowbell
12 For example, instead of `(1..100).map { |x| (1/x)+1 }`, you can write `(1..100).map(&'(1/_)+1')` using String#to\_proc. The underscore is an anaphor, it refers back to the block's parameter just as the word "it" in this sentence refers back to the word "anaphor." The win is brevity: You don't have to define a parameter just to use it once.
4775e3ff »
2009-09-23 Added "Anaphora in Ruby"
13
89d1c246 »
2009-09-29 updated do again
14 String#to\_proc does a lot more than just provide anaphora for single parameters in blocks, of course. But it does provide this specific anaphor in Ruby.
4775e3ff »
2009-09-23 Added "Anaphora in Ruby"
15
89d1c246 »
2009-09-29 updated do again
16 **Methodphitamine: Another implementation of the block anaphor**
4775e3ff »
2009-09-23 Added "Anaphora in Ruby"
17
89d1c246 »
2009-09-29 updated do again
18 [Methodphitamine](http://jicksta.com/posts/the-methodphitamine "The Methodphitamine at Adhearsion Blog by Jay Phillips") provides another implementation of block anaphora, this one inspired by the Groovy language.
4775e3ff »
2009-09-23 Added "Anaphora in Ruby"
19
20 Symbol#to\_proc is the standard way to abbreviate blocks that consist of a single method invocation, typically without parameters. For example if you want the first name of a collection of people records, you might use `Person.all(...).map(&:first_name)`.
21
50715bf8 »
2009-09-26 more cowbell
22 If you want to do more, such as invoke a method with a parameter, or if you want to chain several methods, you are out of luck. Symbol#to\_proc does not allow you to write `Person.all(...).map(&:first_name[0..3])`. With Methodphitamine you can write:
23
24 Person.all(...).map(&it.first_name[0..3])
25
26 Likewise with Symbol#to\_proc you can't write `Person.all(...).map(&:first_name.titlecase)`. You have to write `Person.all(...).map(&:first_name).map(&:titlecase)`. With Methodphitamine you can write:
27
28 Person.all(...).map(&it.first_name.titlecase)
29
30 This is easy to read and does what you expect for simple cases. Methodphitamine uses a proxy object to create the illusion of an anaphor, allowing you to invoke method with parameters and to chain more than one method. Here's some code illustrating the technique:
4775e3ff »
2009-09-23 Added "Anaphora in Ruby"
31
f6b3d4df »
2009-09-23 That should be a blank slate
32 class AnaphorProxy < BlankSlate
4775e3ff »
2009-09-23 Added "Anaphora in Ruby"
33
34 def initialize(proc = lambda { |x| x })
35 @proc = proc
36 end
37
38 def to_proc
39 @proc
40 end
41
42 def method_missing(symbol, *arguments, &block)
43 AnaphorProxy.new(
44 lambda { |x| self.to_proc.call(x).send(symbol, *arguments, &block) }
45 )
46 end
47
48 end
49
50 class Object
51
52 def it
53 AnaphorProxy.new
54 end
55
56 end
57
58 (1..10).map(&it * 2 + 1) # => [3, 5, 7, 9, 11, 13, 15, 17, 19, 21]
59
60 What happens is that "it" is a method that returns an AnaphorProxy. The default proxy is an object that answers the Identity function in response to #to\_proc. Think about out how `(1..10).map(&it) => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]` works: "it" is a method that returns the default AnaphorProxy; using &it calls AnaphorProxy#to\_proc and receives `lambda { |x| x }` in return; #map now applies this to `1..10` and you get `[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]`.
61
62 If you send messages to an AnaphorProxy, you get another AnaphorProxy that "records" the messages you send. So `it * 2 + 1` evaluates to an AnaphorProxy that returns `lambda { |x| lambda { |x| lambda { |x| x }.call(x) * 2 }.call(x) + 1 }`. This is equivalent to `lambda { |x| x * 2 + 1}` but more expensive to compute and dragging with it some closed over variables.
63
50715bf8 »
2009-09-26 more cowbell
64 As you might expect from a hack along these lines, there are all sorts of things to trip us up. `(1..10).map(&it * 2 + 1)` works, however what would you expect from:
65
66 (1..10).map(&1 + it * 2) # no!
67
68 This does not work with Methodphitamine, and neither does something like:
69
70 Person.all(...).select(&it.first_name == it.last_name) # no!
71
72 Also, unexpected things happen if you try to "record" an invocation of #to\_proc:
73
74 [:foo, :bar, :blitz].map(&it.to_proc.call(some_object)) # no!
4775e3ff »
2009-09-23 Added "Anaphora in Ruby"
75
50715bf8 »
2009-09-26 more cowbell
76 So while String#to\_proc allows you to write things like `(1..10).map(&'1 + it * 2')` or `Person.all(...).select(&'_.first_name == _.last_name')`, this approach does not. (The implementation above has been simplified to illustrate the idea. Consult the actual [methodphitamine gem source](http://github.com/jicksta/methodphitamine "jicksta's methodphitamine at master - GitHub") for details on how it is actually implemented: There are performance optimizations as well as a lightweight Maybe Monad hiding under the covers.)
4775e3ff »
2009-09-23 Added "Anaphora in Ruby"
77
89d1c246 »
2009-09-29 updated do again
78 **UPDATE: Block anaphora in rewrite_rails**
8ad2f2a0 »
2009-09-29 updated to mention rewrite_rails
79
582168bb »
2012-07-23 update deprecated projects
80 [rewrite_rails](http://github.com/raganwald-deprecated/rewrite_rails "raganwald's rewrite_rails at master - GitHub") supports `it`, `its`, or `_` as block anaphora for blocks taking one argument. Similarly to Methodphitamine, you can write:
8ad2f2a0 »
2009-09-29 updated to mention rewrite_rails
81
82 (1..10).map{ it * 2 + 1 } # => [3, 5, 7, 9, 11, 13, 15, 17, 19, 21]
83
84 You can also write all of the following:
85
86 (1..10).map { 1 + it * 2 }
87 Person.all(...).select { its.first_name == its.last_name } # and,
88 [:foo, :bar, :blitz].map { it.to_proc.call(some_object) }
89 (1..100).map { (1/_)+1 }
89d1c246 »
2009-09-29 updated do again
90
91 In comparison to String#to\_proc, block anaphora do less (String#to\_proc also supports point-free blocks and named parameters). However, block anaphora looks a little cleaner, you don't have code inside a string, you just have code.
8ad2f2a0 »
2009-09-29 updated to mention rewrite_rails
92
4775e3ff »
2009-09-23 Added "Anaphora in Ruby"
93 **Anaphors for conditionals**
94
d97626b8 »
2009-09-23 tightened up
95 Many people are familiar with the [andand gem](http://github.com/raganwald/andand "raganwald's andand at master - GitHub"). Say you want to write some code like this:
4775e3ff »
2009-09-23 Added "Anaphora in Ruby"
96
97 big_long_calculation() && big_long_calculation().foo
98
99 Most of the time you ought to "cache" the big long calculation in a temporary variable like this:
100
101 (it = big_long_calculation()) && it.foo
102
103 That's such a common idiom, #andand gives you a much more succinct way to write it:
104
105 big_long_calculation().andand.foo
106
d97626b8 »
2009-09-23 tightened up
107 So the idea behind #andand is to express a test for nil and doing something with the result if it is not nil in a very compact way. This is not a new idea. Paul Graham gives this very example when describing the rationale for [anaphoric macros](http://www.bookshelf.jp/texi/onlisp/onlisp_15.html "Onlisp: Anaphoric Macros"):
4775e3ff »
2009-09-23 Added "Anaphora in Ruby"
108
109 > It's not uncommon in a Lisp program to want to test whether an expression returns a non-nil value, and if so, to do something with the value. If the expression is costly to evaluate, then one must normally do something like this:
110
111 (let ((result (big-long-calculation)))
112 (if result
113 (foo result)))
114
115 > Wouldn't it be easier if we could just say, as we would in English:
116
117 (if (big-long-calculation)
118 (foo it))
119
120 > In natural language, an anaphor is an expression which refers back in the conversation. The most common anaphor in English is probably "it," as in "Get the wrench and put it on the table." Anaphora are a great convenience in everyday language--imagine trying to get along without them--but they don't appear much in programming languages. For the most part, this is good. Anaphoric expressions are often genuinely ambiguous, and present-day programming languages are not designed to handle ambiguity.
121
122 WIth an anaphoric macro, the anaphor "it" is bound to the result of the if expression's test clause, so you can express "test for nil and do something with the result if it is not nil" in a compact way.
123
124 **Anaphors for conditionals in Ruby?**
125
89d1c246 »
2009-09-29 updated do again
126 Reading about Lisp's anaphoric macros made me wonder whether anaphora for conditionals would work in Ruby. I find `(it = big_long_calculation()) && it.foo` cluttered and ugly, but perhaps I could live without #andand if I could write things like:
4775e3ff »
2009-09-23 Added "Anaphora in Ruby"
127
128 if big_long_calculation(): it.foo end
129
582168bb »
2012-07-23 update deprecated projects
130 This is relatively easy to accomplish using [rewrite_rails](http://github.com/raganwald-deprecated/rewrite_rails "raganwald's rewrite_rails at master - GitHub"). In the most naïve case, you want to rewrite all of your if statements such that:
4775e3ff »
2009-09-23 Added "Anaphora in Ruby"
131
3a43f11a »
2009-09-23 a little less "then"
132 if big_long_calculation()
4775e3ff »
2009-09-23 Added "Anaphora in Ruby"
133 it.foo
134 end
135
136 Becomes:
137
3a43f11a »
2009-09-23 a little less "then"
138 if (it = big_long_calculation())
4775e3ff »
2009-09-23 Added "Anaphora in Ruby"
139 it.foo
140 end
141
142 You can embellish such a hypothetical rewriter with optimizations such as not assigning `it` unless there is a variable reference somewhere in the consequent or alternate clauses and so forth, but the basic implementation is straightforward.
143
89d1c246 »
2009-09-29 updated do again
144 The trouble with this idea is that in Ruby, *There Is More Than One Way To Do It* (for any value of "it"). If we implement anaphora for conditionals, we ought to implement them for all of the ways a Ruby programmer might write a conditional. As discussed, we must support:
4775e3ff »
2009-09-23 Added "Anaphora in Ruby"
145
3a43f11a »
2009-09-23 a little less "then"
146 if big_long_calculation()
4775e3ff »
2009-09-23 Added "Anaphora in Ruby"
147 it.foo
148 end
149
150 Luckily, that's the exact same thing as:
151
152 if big_long_calculation(): it.foo end
153
154 They both are parsed into the exact same abstract syntax tree expression. Good. Now what about this case:
155
156 it.foo if big_long_calculation()
157
89d1c246 »
2009-09-29 updated do again
158 That doesn't read properly. The anaphor should follow the subject, not precede it. If we want our anaphora to read sensibly, we really want to write:
4775e3ff »
2009-09-23 Added "Anaphora in Ruby"
159
160 big_long_calculation().foo if it # or
161 big_long_calculation().foo unless it.nil?
162
163 These read more naturally, but supporting these expressions would invite Yellow Edge Case Cranial Headache or "YECCH." Behind the scenes, Ruby parses both of the following expressions identically:
164
165 big_long_calculation().foo unless it.nil? # and
166 unless it.nil?
167 big_long_calculation().foo
168 end
169
170 So you would have to have a rule that if the anaphor appears in the test expression, it refers to something from the consequent expression, not from any preceding test expression. But if you tried that rule, how would you handle this code?
171
172 if calculation_that_might_return_a_foobar()
173 if it.kind_of?(:Foobar)
174 number_of_foobars += 1
175 end
176 end
177
89d1c246 »
2009-09-29 updated do again
178 This doesn't work as expected because the anaphor would refer forward to its consequent expression `number_of_foobars += 1` rather than backwards to the enclosing test expression `calculation_that_might_return_a_foobar()`. You can try to construct some rules for disambiguating things, but you're going to end up asking programmers to memorize the implementation of how things actually work rather than relying on familiarity with how anaphora work in English.
4775e3ff »
2009-09-23 Added "Anaphora in Ruby"
179
180 Another problem with supporting `big_long_calculation().foo unless it.nil?` is that we now need some rules to figure out that the anaphor refers to `big_long_calculation()` and not to `big_long_calculation().foo`. Whatever arbitrary rules we pick are going to introduce ambiguity. What shall we do about:
181
182 big_long_calculation().foo unless it.nil?
183 big_long_calculation().foo.bar unless it.nil?
184 big_long_calculation() + 3 unless it.nil?
185 3 + big_long_calculation() unless it.nil?
186 big_long_calculation(3) unless it.nil?
187 big_long_calculation(foo()) unless it.nil?
188 big_long_calculation(foo(bar())) unless it.nil?
189
190 In my opinion, if we can't find clean and easy to understand support for writing conditionals as suffixes, we aren't supporting Ruby conditionals. To underscore the difficulty, let's also remember that Ruby programmers idiomatically use operators to express conditional expressions. Given:
191
192 big_long_calculation() && big_long_calculation().foo
193
194 We want to write:
195
196 big_long_calculation() && it.foo
197
198 This is near and dear to my heart: The name "andand" comes from this exact formulation. #andand doesn't enhance an if expression, it enhances the double ampersand operator. One can see at a glance that implementing support for `big_long_calculation() && it.foo` is fraught with perils. What about `big_long_calculation() + it.foo`? What about `big_long_calculation().bar && it.foo`?
199
89d1c246 »
2009-09-29 updated do again
200 It seems that it is much harder to support anaphora for conditionals in Ruby than it is to support anaphora for conditionals in Lisp. This isn't surprising. Lisp has an extremely regular lack of syntax, so we don't have to concern ourselves with as many cases as we do in Ruby.
4775e3ff »
2009-09-23 Added "Anaphora in Ruby"
201
202 **Old school anaphora**
203
204 Anaphora have actually been baked into Ruby from its earliest days. Thanks to its Perl heritage, a number of global variables act like anaphora. For example, `$&` is a global variable containing the last successful regular expression match, or nil if the last attempt to match failed. So instead of writing something like:
205
d97626b8 »
2009-09-23 tightened up
206 if match_data = /reg(inald)?/.match(full_name) then puts match_data[0] end
4775e3ff »
2009-09-23 Added "Anaphora in Ruby"
207
208 You can use $& as an anaphor and avoid creating another explicit temporary variable, just like the anaphor in a conditional:
209
d97626b8 »
2009-09-23 tightened up
210 if /reg(inald)?/.match(full_name) then puts $& end
4775e3ff »
2009-09-23 Added "Anaphora in Ruby"
211
212 These 'anaphoric' global variables have a couple of advantages. Since they are tied to the use of things like regular expression matching rather than a specific syntactic construct like an if expression, they are more flexible and can be used in more ways. Their behaviour is very well defined.
213
214 The disadvantage is that there is a complete hodge-podge of them. Some are read only, some read-write, and none have descriptive names. They look like line noise to the typical programmer, and as a result many people (myself included) simply don't use them outside of writing extremely short shell scripts in Ruby.
215
216 Anaphors like the underscore or a special variable called "it" have the advantage of providing a smaller surface area for understanding. Consider Lisp's anaphoric macro where "it" refers to the value of the test expression and nothing more (we ignore the special cases and other ways Ruby expresses conditionals). Compare:
217
218 if /reg(inald)?/.match(full_name) then puts $& end
219
220 To:
221
222 if /reg(inald)?/.match(full_name) then puts it[0] end
223
58403723 »
2009-10-08 reorganized and now "feature complete"
224 To my eyes, "it" is easier to understand because it is a very general, well-understood anaphor. "It" always matches the test expression. We don't have to worry about whether `$&` is the result of a match or all the text to the left of a match or the command line parameters or what-have-you.
4775e3ff »
2009-09-23 Added "Anaphora in Ruby"
225
226 **Summing up**
227
228 Anaphora allow us to abbreviate code, hiding parameters and temporary variables for certain special cases. This can be a win for readability for short code snippets where the extra verbiage is almost as long as what you're trying to express. That being said, implementing anaphora in Ruby is a hard design problem, in part because There Is More Than One Way To Do It, and trying to provide complete support leads to ambiguities, inconsistencies, and conflicts. And old school anaphora? They are clearly an acquired taste.
229
244c5b51 »
2009-09-24 and more
230 **More**
231
9b1c9fe1 »
2013-02-17 no more weblog.raganwald.com!
232 * [String#to\_proc](http://github.com/raganwald/homoiconic/blob/master/2008-11-28/you_cant_be_serious.md "You can't be serious!?") and its original [blog post](http://raganwald.com/2007/10/stringtoproc.html "String#to_proc").
244c5b51 »
2009-09-24 and more
233 * [Methodphitamine](http://github.com/jicksta/methodphitamine "jicksta's methodphitamine at master - GitHub") and its original [blog post](http://jicksta.com/posts/the-methodphitamine "The Methodphitamine at Adhearsion Blog by Jay Phillips")
234 * [Anaphoric macros](http://www.bookshelf.jp/texi/onlisp/onlisp_15.html "Onlisp: Anaphoric Macros")
582168bb »
2012-07-23 update deprecated projects
235 * [rewrite_rails](http://github.com/raganwald-deprecated/rewrite_rails "raganwald's rewrite_rails at master - GitHub") contains an improved implementation of String#to\_proc.
244c5b51 »
2009-09-24 and more
236 * A [usenet discussion](http://groups.google.com/group/ruby-talk-google/browse_thread/thread/26445dcef22f5a5/1772d0c487d4c570?hl=en&amp;lnk=ol&amp; "Introducing the &quot;it&quot; keyword") about anaphora in Ruby.
fe91782b »
2009-09-30 shout out to Robert Fischer
237 * [@RobertFischer](http://twitter.com/RobertFischer "Robert Fischer") pointed out that Groovy implements Block Anaphora using exactly the same syntax as rewrite\_rails, as well as mentioning that Groovy provides a special operator, `?.`, for the Maybe Monad.
238 * Perl has some [anaphora of its own](http://www.wellho.net/mouth/969_Perl-and-.html "Perl - $_ and @_").
244c5b51 »
2009-09-24 and more
239
7d4a695d »
2011-11-16 dasherize the mores
240 ---
f69ce703 »
2011-11-12 Link to the book
241
8c4fb377 »
2013-01-03 My recent work
242 My recent work:
243
f9d44876 »
2013-01-04 allongé
244 ![](http://i.minus.com/iL337yTdgFj7.png)[![JavaScript Allongé](http://i.minus.com/iW2E1A8M5UWe6.jpeg)](http://leanpub.com/javascript-allonge "JavaScript Allongé")![](http://i.minus.com/iL337yTdgFj7.png)[![CoffeeScript Ristretto](http://i.minus.com/iMmGxzIZkHSLD.jpeg)](http://leanpub.com/coffeescript-ristretto "CoffeeScript Ristretto")![](http://i.minus.com/iL337yTdgFj7.png)[![Kestrels, Quirky Birds, and Hopeless Egocentricity](http://i.minus.com/ibw1f1ARQ4bhi1.jpeg)](http://leanpub.com/combinators "Kestrels, Quirky Birds, and Hopeless Egocentricity")
44799868 »
2011-12-08 More recent work!
245
f9d44876 »
2013-01-04 allongé
246 * [JavaScript Allongé](http://leanpub.com/javascript-allonge), [CoffeeScript Ristretto](http://leanpub.com/coffeescript-ristretto), and my [other books](http://leanpub.com/u/raganwald).
21fcc00b »
2013-01-12 redirect to allong.es
247 * [allong.es](http://allong.es), practical function combinators and decorators for JavaScript.
17be3259 »
2012-12-22 revised footers
248 * [Method Combinators](https://github.com/raganwald/method-combinators), a CoffeeScript/JavaScript library for writing method decorators, simply and easily.
4f7cce6b »
2013-01-03 githiub
249 * [jQuery Combinators](http://github.com/raganwald/jquery-combinators), what else? A jQuery plugin for writing your own fluent, jQuery-like code.
f69ce703 »
2011-11-12 Link to the book
250
16fa4bb5 »
2012-01-11 separated .sig
251 ---
252
b2b5c4ad »
2013-02-03 shuffle
253 (Spot a bug or a spelling mistake? This is a Github repo, fork it and send me a pull request!)
254
7e71700c »
2012-04-12 updated links
255 [Reg Braithwaite](http://braythwayt.com) | [@raganwald](http://twitter.com/raganwald)
Something went wrong with that request. Please try again.