Skip to content
This repository
Browse code

try some syntax hilighting

  • Loading branch information...
commit 8ed541edc7f7cb5d1b3e0e3d34b740bd8cadabe9 1 parent e6d31df
Reg Braithwaite authored May 22, 2012

Showing 1 changed file with 82 additions and 50 deletions. Show diff stats Hide diff stats

  1. 132  2012/05/anaphora.md
132  2012/05/anaphora.md
Source Rendered
@@ -8,11 +8,15 @@
8 8
 
9 9
 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:
10 10
 
11  
-    if match_data = /reg(inald)?/.match(full_name) then puts match_data[0] end
12  
-    
  11
+```ruby
  12
+if match_data = /reg(inald)?/.match(full_name) then puts match_data[0] end
  13
+```
  14
+
13 15
 You can use $& as an anaphor and avoid creating another explicit temporary variable, just like the anaphor in a conditional:
14 16
 
15  
-    if /reg(inald)?/.match(full_name) then puts $& end 
  17
+```ruby
  18
+if /reg(inald)?/.match(full_name) then puts $& end 
  19
+```
16 20
     
17 21
 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.
18 22
 
@@ -20,13 +24,19 @@ The disadvantage is that there is a complete hodge-podge of them. Some are read
20 24
 
21 25
 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:
22 26
 
23  
-    if /reg(inald)?/.match(full_name) then puts $& end
24  
-    
  27
+```ruby
  28
+if /reg(inald)?/.match(full_name) then puts $& end
  29
+```
  30
+
25 31
 To:
26 32
 
27  
-    if /reg(inald)?/.match(full_name) then puts it[0] end
28  
-    
29  
-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.
  33
+```ruby
  34
+if /reg(inald)?/.match(full_name) then puts it[0] end
  35
+```
  36
+
  37
+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. Of course, "it" isn't an anaphor in Ruby. It is (forgive the expression) in other languages like Groovy.
  38
+
  39
+Could anaphors be added to Ruby where none previously existed? Yes. Sort of.
30 40
 
31 41
 ## New School Block Anaphora
32 42
 
@@ -38,41 +48,47 @@ Some languages provide a special meta-variable that can be used in a similar way
38 48
 
39 49
 Jay Phillips implemented a simple block anaphor called [Methodphitamine](http://jicksta.com/posts/the-methodphitamine "The Methodphitamine at Adhearsion Blog by Jay Phillips"). `it` doesn't seem like much of a win when you just want to send a message without parameters. But 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:
40 50
 
41  
-    Person.all(...).map(&it.first_name[0..3])
  51
+```ruby
  52
+Person.all(...).map(&it.first_name[0..3])
  53
+```
42 54
     
43 55
 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:
44 56
 
45  
-    Person.all(...).map(&it.first_name.titlecase)
  57
+```ruby
  58
+Person.all(...).map(&it.first_name.titlecase)
  59
+```
46 60
     
47 61
 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:
48 62
 
49  
-    class AnaphorProxy < BlankSlate
50  
-  
51  
-      def initialize(proc = lambda { |x| x })
52  
-        @proc = proc
53  
-      end
54  
-  
55  
-      def to_proc
56  
-        @proc 
57  
-      end
58  
-  
59  
-      def method_missing(symbol, *arguments, &block)
60  
-        AnaphorProxy.new(
61  
-          lambda { |x| self.to_proc.call(x).send(symbol, *arguments, &block) }
62  
-        )
63  
-      end
64  
-  
65  
-    end
  63
+```ruby
  64
+class AnaphorProxy < BlankSlate
66 65
 
67  
-    class Object
68  
-  
69  
-      def it
70  
-        AnaphorProxy.new
71  
-      end
72  
-  
73  
-    end
  66
+  def initialize(proc = lambda { |x| x })
  67
+    @proc = proc
  68
+  end
  69
+
  70
+  def to_proc
  71
+    @proc 
  72
+  end
  73
+
  74
+  def method_missing(symbol, *arguments, &block)
  75
+    AnaphorProxy.new(
  76
+      lambda { |x| self.to_proc.call(x).send(symbol, *arguments, &block) }
  77
+    )
  78
+  end
  79
+
  80
+end
  81
+
  82
+class Object
  83
+
  84
+  def it
  85
+    AnaphorProxy.new
  86
+  end
  87
+
  88
+end
74 89
 
75  
-    (1..10).map(&it * 2 + 1) # => [3, 5, 7, 9, 11, 13, 15, 17, 19, 21]
  90
+(1..10).map(&it * 2 + 1) # => [3, 5, 7, 9, 11, 13, 15, 17, 19, 21]
  91
+```
76 92
     
77 93
 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]`.
78 94
 
@@ -80,15 +96,21 @@ If you send messages to an AnaphorProxy, you get another AnaphorProxy that "reco
80 96
 
81 97
 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:
82 98
 
83  
-    (1..10).map(&1 + it * 2) # no!
  99
+```ruby
  100
+(1..10).map(&1 + it * 2) # no!
  101
+```
84 102
     
85 103
 This does not work with Methodphitamine, and neither does something like:
86 104
 
87  
-    Person.all(...).select(&it.first_name == it.last_name) # no!
  105
+```ruby
  106
+Person.all(...).select(&it.first_name == it.last_name) # no!
  107
+```
88 108
     
89 109
 Also, unexpected things happen if you try to "record" an invocation of #to\_proc:
90 110
 
91  
-    [:foo, :bar, :blitz].map(&it.to_proc.call(some_object)) # no!
  111
+```ruby
  112
+[:foo, :bar, :blitz].map(&it.to_proc.call(some_object)) # no!
  113
+```
92 114
 
93 115
 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.)
94 116
 
@@ -96,9 +118,11 @@ So while String#to\_proc allows you to write things like `(1..10).map(&'1 + it *
96 118
 
97 119
 [Ampex](https://github.com/rapportive-oss/ampex) is a new block anaphora library. Instead of `it`, Ampex uses `X`:
98 120
 
99  
-    ["a", "b", "c"].map &(X * 2)
100  
-      # => ["aa", "bb", "cc"]
101  
-      
  121
+```ruby
  122
+["a", "b", "c"].map &(X * 2)
  123
+  # => ["aa", "bb", "cc"]
  124
+```
  125
+   
102 126
 As Conrad Irwin explains in a [blog post](http://cirw.in/blog/ampex) announcing Ampex:
103 127
 
104 128
 > The ampex library is distributed as a rubygem, so to use it, you can either install it one-off or add it to your Gemfile. We've been using ampex in production for over a year now, and beacuse it's written in pure Ruby, it works on Ruby 1.8.7, Ruby 1.9 and JRuby out of the box.
@@ -107,26 +131,34 @@ As Conrad Irwin explains in a [blog post](http://cirw.in/blog/ampex) announcing
107 131
 
108 132
 Using proxy objects (as methodphitimine and ampex do) runs you into that curious problem of trying to implement symmetrical behaviour in object-oriented languages where everything is inherently *asymmetrical*. Block anaphora implemented by proxy objects only work properly when they're a receiver in a block. You cannot, for example, use methodphitimine or ampex to write:
109 133
 
110  
-    (1..10).map { 1 + it * 2 }
111  
-    (1..10).map { 1 + X * 2 }
  134
+```ruby
  135
+(1..10).map { 1 + it * 2 }
  136
+(1..10).map { 1 + X * 2 }
  137
+```
112 138
     
113 139
 You also have certain issues with respect to when arguments are evaluated:
114 140
 
115  
-    i = 1
116  
-    (1..10).map { it.frobbish(i += 1) }
  141
+```ruby
  142
+i = 1
  143
+(1..10).map { &it.frobbish(i += 1) }
  144
+```
117 145
     
118 146
 `i +=1` is only evaluated once, not for each iteration. To "fix" either of these problems, you need to parse Ruby directly. No sane person would do this just for the convenience of using block anaphora in there code, however Github archeologists report that a now-extinct society of programmers did this very thing:
119 147
 
120 148
 The abandonware gem [rewrite_rails](http://github.com/raganwald/rewrite_rails "raganwald's rewrite_rails at master - GitHub") supported `it`, `its`, or `_` as block anaphora for blocks taking one argument. Similarly to Methodphitamine, you could write:
121 149
 
122  
-    (1..10).map{ it * 2 + 1 } # => [3, 5, 7, 9, 11, 13, 15, 17, 19, 21]
  150
+```ruby
  151
+(1..10).map{ it * 2 + 1 } # => [3, 5, 7, 9, 11, 13, 15, 17, 19, 21]
  152
+```
123 153
     
124 154
 You could also write all of the following:
125 155
 
126  
-    (1..10).map { 1 + it * 2 }
127  
-    Person.all(...).select { its.first_name == its.last_name } # and,
128  
-    [:foo, :bar, :blitz].map { it.to_proc.call(some_object) }
129  
-    (1..100).map { (1/_)+1 }
  156
+```ruby
  157
+(1..10).map { 1 + it * 2 }
  158
+Person.all(...).select { its.first_name == its.last_name } # and,
  159
+[:foo, :bar, :blitz].map { it.to_proc.call(some_object) }
  160
+(1..100).map { (1/_)+1 }
  161
+```
130 162
     
131 163
 `rewrte_rails` does its magic by parsing the block and rewriting it.
132 164
 

0 notes on commit 8ed541e

Please sign in to comment.
Something went wrong with that request. Please try again.