ick 0.2.3 → 0.2.4
Sign up to get free protection for your applications and to get access to all the features.
- data/Manifest.txt +8 -1
- data/lib/ick.rb +1 -0
- data/lib/ick/advisor.rb +4 -4
- data/lib/ick/bizarro.rb +41 -0
- data/lib/ick/version.rb +1 -1
- data/test/test_180_seconds.rb +100 -0
- data/test/test_not.rb +5 -0
- data/website/180seconds.html +239 -0
- data/website/180seconds.txt +159 -0
- data/website/index.html +14 -3
- data/website/index.txt +7 -1
- data/website/inside.html +325 -0
- data/website/inside.txt +215 -0
- data/website/template.rhtml +2 -0
- metadata +14 -3
data/website/inside.txt
ADDED
@@ -0,0 +1,215 @@
|
|
1
|
+
h1. Inside the Invocation Construction Kit
|
2
|
+
|
3
|
+
h2. More about the four block structures
|
4
|
+
|
5
|
+
"Ick":index.html provides #let, a method for block-structuring Ruby code. As already shown, if you want someone's phone number only if they are a friend: @let(Person.find(:first, ...)) { |person| person.phone_number if person.friend? }@
|
6
|
+
|
7
|
+
This code makes it clear that you only need the @person@ variable inside the block. If you want to refactor this code, you know that the entire expression can move without breaking another piece of code.
|
8
|
+
|
9
|
+
h3. #let, #returning, #my, and #inside
|
10
|
+
|
11
|
+
As shown above, #let does the obvious thing: it passes a value to a block, it evaluates the block in the caller's environment, and it returns the result of the block. Hmmm...
|
12
|
+
|
13
|
+
There are two binary decisions to be made about every block: First, do you want to evaluate the block in the calling environment (which is how almost every block is evaluated in Ruby), or do you want to evaluate the block in the value's context. In other words, does _self_ stay the same, or does it become the value in the block?
|
14
|
+
|
15
|
+
If you want something that behaves like #let but evaluates in the value's environment just like #please, you can use #my:
|
16
|
+
|
17
|
+
<pre syntax="ruby">
|
18
|
+
my(Person.find(:first, ...)) do
|
19
|
+
first_name = 'Charles'
|
20
|
+
last_name = 'Babbage'
|
21
|
+
friends << 'Ada Lovelace'
|
22
|
+
end
|
23
|
+
</pre>
|
24
|
+
|
25
|
+
This will return Charles Babbage's friends. On the surface, _evaluates_in_value_environment_ is about the syntactic sugar of dropping an instance variable. But with a little thought, you can come up with some really cool way to (mis)use this capability.
|
26
|
+
|
27
|
+
So #let and #my both pass an expression to a block and return the result. Given that they both 'declare' _returns_result_, this is not surprising. But there is another choice: _returns_value_ instead of _returns_result_. Ruby on Rails includes the popular #returning method, and it works the same in Ick:
|
28
|
+
|
29
|
+
<pre syntax="ruby">
|
30
|
+
returning(Person.find(:first, ...)) do |p|
|
31
|
+
p.first_name = 'Charles'
|
32
|
+
p.last_name = 'Babbage'
|
33
|
+
p.friends << 'Ada Lovelace'
|
34
|
+
end
|
35
|
+
</pre>
|
36
|
+
|
37
|
+
This returns the person record, not the list of friends. The block is evaluated strictly for side effects. And what happens if we want to return the value and also evaluate in the value's environment?
|
38
|
+
|
39
|
+
<pre syntax="ruby">
|
40
|
+
inside(Person.find(:first, ...)) do
|
41
|
+
first_name = 'Charles'
|
42
|
+
last_name = 'Babbage'
|
43
|
+
friends << 'Ada Lovelace'
|
44
|
+
end
|
45
|
+
</pre>
|
46
|
+
|
47
|
+
The method #inside returns the value and evaluates the block in the value's environment.
|
48
|
+
|
49
|
+
(The four methods were inspired by "Michiel de Mare's":http://blog.rubyenrails.nl/articles/2008/02/18/our-daily-method-10-object-r-rs-ds-s post on the same subject, although Ick’s nomenclature is not compatible with Michiel’s. Michiel’s #rsss, #rrss, #rsds, and #rrds are called #returning, #let, #inside, and #my in Ick.)
|
50
|
+
|
51
|
+
h3. What about #try and #maybe?
|
52
|
+
|
53
|
+
The methods #try and #maybe are both implemented as _evaluates_in_calling_environment_, because that is least surprising. But when you're rolling your own, you might want to change that to make things more sugary. For example, here is a different version of #try:
|
54
|
+
|
55
|
+
<pre syntax="ruby">
|
56
|
+
class Please < Ick::Guard
|
57
|
+
guard_with { |value, sym| value.respond_to?(sym) == true }
|
58
|
+
evaluates_in_value_environment and returns_result
|
59
|
+
belongs_to Object
|
60
|
+
end
|
61
|
+
|
62
|
+
please(...) { may.i.have.some.more }
|
63
|
+
</pre>
|
64
|
+
|
65
|
+
The method #please executes in the value's environment, and thus it can call methods directly. Here is the key point about Ick as compared to rolling your own methods directly: you can combine and recombine the parts to make new kinds of methods. As you just saw, we can make #try and #please out of the same building blocks. This is why Ick is a "contruction kit," not just a collection of handy syntactic short cuts.
|
66
|
+
|
67
|
+
h2. Wrap Music
|
68
|
+
|
69
|
+
The four methods #let, #returning, #my, and #inside are fairly simple. There's some finagling with what they return and their evaluation environment, but evaluation within the environment is 100% standard Ruby. But things get really interesting when we want to actually change Ruby's evaluation behaviour. Specifically, when we want to change what it means to call a method on the value we provide.
|
70
|
+
|
71
|
+
That's what #maybe, #please, and #try all do: when a method is called on the value, they intercept it and decide whether to call the method or prematurely return _nil_. Ick can't actually change the behaviour of the Ruby interpreter, so what it does is as close to evil metaprogramming as possible. Ick doesn't do any method redefinition or method chain manipulation, instead it performs its juju by wrapping the value in a delegator. Ick's delegators are called _Wrappers_.
|
72
|
+
|
73
|
+
Ick's built-in methods that use wrappers inherit from @Ick::Wrap@. When the result is returned from the block, it is unwrapped if it is wrapped.
|
74
|
+
|
75
|
+
h3. Hand Rolling
|
76
|
+
|
77
|
+
You can use Ick::Wrap along with your own wrappers by calling #invoke_wrapped:
|
78
|
+
|
79
|
+
<pre syntax="Ruby">
|
80
|
+
class MyWrapper < Ick::Wrapper
|
81
|
+
def initialize(value)
|
82
|
+
# ...
|
83
|
+
end
|
84
|
+
# ...
|
85
|
+
end
|
86
|
+
|
87
|
+
Ick::Wrap.instance.invoke_wrapped(Person.find(:first, ...), MyWrapper)
|
88
|
+
</pre>
|
89
|
+
|
90
|
+
If you need additional parameters for your wrapper, you can declare them in the initializer and pass them to #invoke_wrapped:
|
91
|
+
|
92
|
+
<pre syntax="Ruby">
|
93
|
+
class MyWrapper < Ick::Wrapper
|
94
|
+
def initialize(value, extra1, extra2)
|
95
|
+
# ...
|
96
|
+
end
|
97
|
+
# ...
|
98
|
+
end
|
99
|
+
|
100
|
+
Ick::Wrap.instance.invoke_wrapped(Person.find(:first, ...), MyWrapper, :foo, :bar)
|
101
|
+
</pre>
|
102
|
+
|
103
|
+
The methods #maybe, #please, and #try are all implemented as subclasses of Ick:Guard. Have a look at guard.rb to see how to hide the extra wrapping syntax that #invoke_wrapped requires.
|
104
|
+
|
105
|
+
h3. ArrayWrapper
|
106
|
+
|
107
|
+
Ick::ArrayWrapper wraps a collection of receivers. Any message sent to the wrapper is dispatched to each of the receivers in the collection. Here's a simplified implementation:
|
108
|
+
|
109
|
+
<pre syntax="ruby">
|
110
|
+
class ArrayWrapper < Wrapper
|
111
|
+
|
112
|
+
def __invoke__(sym, *args, &block)
|
113
|
+
@value.map { |_| _.__send__(sym, *args, &block) }
|
114
|
+
end
|
115
|
+
|
116
|
+
is_contagious
|
117
|
+
|
118
|
+
end
|
119
|
+
</pre>
|
120
|
+
|
121
|
+
What on Earth is it for? Specifically, what's wrong with just using Enumerable#map if we want to send the same message(s) to each receiver in a collection?
|
122
|
+
|
123
|
+
The answer is that when we use Enumerable#map, it executes the entire block once for each receiver. So if there are side effects in the block, they are executed once for each receiver as well. Whereas ArrayWrapper dispatches the messages to each receiver without executing side effects more than once.
|
124
|
+
|
125
|
+
Ick::Tee provides a method for using an ArrayWrapper with _return_value_ semantics. When you use #tee, it evaluates the block once, but dispatches methods to each of the values you pass in. It returns the first value you pass in. This can be handy for things like sending logging information to more than one log.
|
126
|
+
|
127
|
+
Here's one of the test cases you can find in the gem:
|
128
|
+
|
129
|
+
<pre syntax="ruby">
|
130
|
+
def test_tee_semantics
|
131
|
+
array_logger_one = []
|
132
|
+
array_logger_two = []
|
133
|
+
line_number = 0
|
134
|
+
[array_logger_one, array_logger_two].map do |log|
|
135
|
+
line_number += 1
|
136
|
+
log << "A#{line_number}"
|
137
|
+
line_number += 1
|
138
|
+
log << "B#{line_number}"
|
139
|
+
end
|
140
|
+
assert_equal(%w(A1 B2), array_logger_one)
|
141
|
+
assert_equal(%w(A3 B4), array_logger_two)
|
142
|
+
array_logger_one = []
|
143
|
+
array_logger_two = []
|
144
|
+
line_number = 0
|
145
|
+
tee(array_logger_one, array_logger_two) do |log|
|
146
|
+
line_number += 1
|
147
|
+
log << "A#{line_number}"
|
148
|
+
line_number += 1
|
149
|
+
log << "B#{line_number}"
|
150
|
+
end
|
151
|
+
assert_equal(%w(A1 B2), array_logger_one)
|
152
|
+
assert_equal(%w(A1 B2), array_logger_two)
|
153
|
+
end
|
154
|
+
</pre>
|
155
|
+
|
156
|
+
When we used Enumerable#map, the side effect @line_number += 1@ is evaluated four times, twice for each receiver. However when we use #tee, the side effect @line_number += 1@ is only evaluated twice, even though we have two receivers. Now that you know about the four execution possibilities encapsulated by #let, #returning, #my, and #inside, you can probably guess what #fork does when I tell you that its implementation _returns_result_.
|
157
|
+
|
158
|
+
h3. Beware!
|
159
|
+
|
160
|
+
Because Ick::Wrap is passing a wrapper into the block instead of the original value, you can get unexpected results under certain circumstances. For example, if you assign the value something else within the block such as an instance variable, when the block exits the value will still have whatever behaviour the wrapper creates.
|
161
|
+
|
162
|
+
Therefore, wrappers are _unidirectional_: they only work when you are calling methods on your value, not when you are passing the wrapped value as a parameter to any other method. This is hideous: why should there be any difference between @{ |value| value + 1 }@ and @{ |value| 1 + value }@?
|
163
|
+
|
164
|
+
Also, wrappers are _external_, they only affect messages sent to the wrapped value in your block. For example, if you are using #try to avoid NoMethodErrors, any messages you send to the wrapped value will handled by the wrapper. But what happens if the wrapped value sends itself a message that it doesn't handle? Aha, that will raise a NoMethodError!
|
165
|
+
|
166
|
+
Wrappers are a very leaky abstraction, which is why I try to name things a little closer to what they actually do than what they purport to do but don't really do. For example, we write @try { something.or.other }@ to emphasize that we are only trying the things you see in front of you.
|
167
|
+
|
168
|
+
h2. Why Ick?
|
169
|
+
|
170
|
+
As just mentioned, Ick is a construction kit. By all means install the gem and go wild with #let, #returning, #my, #inside, #try, and #maybe. But have a look under the hood. Ick uses classes and template methods to replicate what can be done in a few lines of explicit code. For example, Object#returning is implemented in Rails as:
|
171
|
+
|
172
|
+
<pre syntax="ruby">
|
173
|
+
class Object
|
174
|
+
def returning(value)
|
175
|
+
yield(value)
|
176
|
+
value
|
177
|
+
end
|
178
|
+
end
|
179
|
+
</pre>
|
180
|
+
|
181
|
+
So why bother with Ick? Well, if you want to make a method just like Object#returning, only _X_ (for some value of X), you can't do that without copying, pasting, and modifying. Ick's classes are included specifically so you can subclass things and make your own new kinds of methods that are variations of the existing methods.
|
182
|
+
|
183
|
+
Thus, the extra abstraction is appropriate if you want to use the built-in methods as a starting point for your own exploratory programming. And if you don't care, you just want the methods, by all means install the gem and just use them. Don't worry about the implementation unless you identify it as a performance problem.
|
184
|
+
|
185
|
+
h3. Where do you want to go today?
|
186
|
+
|
187
|
+
The point behind abstracting invocation and evaluation is that you can _separate concerns_. For example, which methods to chain is one concern. How to handle nil or an object that does not respond to a method is a separate concern. Should you raise and handle and exception? Return nil? log an error? Why should error handling and logging be intermingled with your code?
|
188
|
+
|
189
|
+
With Ick, you can separate the two issues. You can even make the handling pluggable. For example, if instead of calling #let you call your own method, you could sometimes invoke @Ick::Let@ with a block and other times invoke your own handler, perhaps one that logs every method called.
|
190
|
+
|
191
|
+
Have fun!
|
192
|
+
|
193
|
+
h2. Administrivia
|
194
|
+
|
195
|
+
h3. Home Sweet Home
|
196
|
+
|
197
|
+
"ick.rubyforge.org":http://ick.rubyforge.org
|
198
|
+
|
199
|
+
h3. How to submit patches
|
200
|
+
|
201
|
+
Read the "8 steps for fixing other people's code":http://drnicwilliams.com/2007/06/01/8-steps-for-fixing-other-peoples-code/.
|
202
|
+
|
203
|
+
The trunk repository is @svn://rubyforge.org/var/svn/ick/trunk@ for anonymous access.
|
204
|
+
|
205
|
+
h3. License
|
206
|
+
|
207
|
+
This code is free to use under the terms of the "MIT license":http://en.wikipedia.org/wiki/MIT_License.
|
208
|
+
|
209
|
+
h3. Shout Out
|
210
|
+
|
211
|
+
"Mobile Commons":http://mcommons.com/. Still Huge After All These Years.
|
212
|
+
|
213
|
+
h3. Contact
|
214
|
+
|
215
|
+
Comments are welcome. Send an email to "Reginald Braithwaite":mailto:raganwald+rubyforge@gmail.com. And you can always visit "weblog.raganwald.com":http://weblog.raganwald.com/ to see what's cooking.
|
data/website/template.rhtml
CHANGED
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.0
|
|
3
3
|
specification_version: 1
|
4
4
|
name: ick
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.2.
|
7
|
-
date: 2008-
|
6
|
+
version: 0.2.4
|
7
|
+
date: 2008-04-28 00:00:00 -04:00
|
8
8
|
summary: Invocation Construction Kit
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -40,6 +40,7 @@ files:
|
|
40
40
|
- lib/ick.rb
|
41
41
|
- lib/ick/advisor.rb
|
42
42
|
- lib/ick/base.rb
|
43
|
+
- lib/ick/bizarro.rb
|
43
44
|
- lib/ick/guard.rb
|
44
45
|
- lib/ick/sugar.rb
|
45
46
|
- lib/ick/tee.rb
|
@@ -53,18 +54,26 @@ files:
|
|
53
54
|
- tasks/deployment.rake
|
54
55
|
- tasks/environment.rake
|
55
56
|
- tasks/website.rake
|
57
|
+
- test/test_180_seconds.rb
|
58
|
+
- test/test_advisor.rb
|
56
59
|
- test/test_helper.rb
|
57
60
|
- test/test_ick.rb
|
58
|
-
- test/
|
61
|
+
- test/test_not.rb
|
62
|
+
- website/180seconds.html
|
63
|
+
- website/180seconds.txt
|
59
64
|
- website/index.html
|
60
65
|
- website/index.txt
|
66
|
+
- website/inside.html
|
67
|
+
- website/inside.txt
|
61
68
|
- website/javascripts/rounded_corners_lite.inc.js
|
62
69
|
- website/stylesheets/screen.css
|
63
70
|
- website/template.rhtml
|
64
71
|
test_files:
|
72
|
+
- test/test_180_seconds.rb
|
65
73
|
- test/test_advisor.rb
|
66
74
|
- test/test_helper.rb
|
67
75
|
- test/test_ick.rb
|
76
|
+
- test/test_not.rb
|
68
77
|
rdoc_options:
|
69
78
|
- --main
|
70
79
|
- README.txt
|
@@ -73,7 +82,9 @@ extra_rdoc_files:
|
|
73
82
|
- License.txt
|
74
83
|
- Manifest.txt
|
75
84
|
- README.txt
|
85
|
+
- website/180seconds.txt
|
76
86
|
- website/index.txt
|
87
|
+
- website/inside.txt
|
77
88
|
executables: []
|
78
89
|
|
79
90
|
extensions: []
|