ick 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +3 -0
- data/Manifest.txt +6 -0
- data/lib/ick.rb +1 -0
- data/lib/ick/advisor.rb +43 -0
- data/lib/ick/base.rb +108 -0
- data/lib/ick/guard.rb +53 -0
- data/lib/ick/sugar.rb +7 -0
- data/lib/ick/tee.rb +35 -0
- data/lib/ick/version.rb +1 -1
- data/lib/ick/wrap.rb +92 -0
- data/test/test_ick.rb +23 -10
- data/website/index.html +13 -94
- data/website/index.txt +12 -77
- metadata +8 -2
data/History.txt
CHANGED
data/Manifest.txt
CHANGED
@@ -6,7 +6,13 @@ Rakefile
|
|
6
6
|
config/hoe.rb
|
7
7
|
config/requirements.rb
|
8
8
|
lib/ick.rb
|
9
|
+
lib/ick/advisor.rb
|
10
|
+
lib/ick/base.rb
|
11
|
+
lib/ick/guard.rb
|
12
|
+
lib/ick/sugar.rb
|
13
|
+
lib/ick/tee.rb
|
9
14
|
lib/ick/version.rb
|
15
|
+
lib/ick/wrap.rb
|
10
16
|
log/debug.log
|
11
17
|
script/destroy
|
12
18
|
script/generate
|
data/lib/ick.rb
CHANGED
data/lib/ick/advisor.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
module Ick
|
2
|
+
class Advisor < Wrapper
|
3
|
+
@@arounds = lambda { |callback, receiver, sym, *args|
|
4
|
+
callback.call()
|
5
|
+
}
|
6
|
+
def self.around target = nil, args = {}, &proc # { |callback, receiver, sym, *args| ... }
|
7
|
+
old_arounds = @@arounds
|
8
|
+
if target.kind_of?(Symbol)
|
9
|
+
proc = lambda { |callback, receiver, sym, *args|
|
10
|
+
self.send(target, sym, *args, &proc)
|
11
|
+
}
|
12
|
+
elsif target.kind_of?(Class)
|
13
|
+
proc = lambda { |callback, receiver, sym, *args|
|
14
|
+
target.filter(receiver, sym, *args, &proc)
|
15
|
+
}
|
16
|
+
end
|
17
|
+
@@arounds = if args[:only]
|
18
|
+
lambda { |callback, receiver, sym, *args|
|
19
|
+
new_callback = lambda { old_arounds.call(callback, receiver, sym, *args) }
|
20
|
+
Array(args[:only]).include(sym) ? proc.call(callback, receiver, sym, *args) : callback.call()
|
21
|
+
}
|
22
|
+
elsif args[:except]
|
23
|
+
lambda { |callback, receiver, sym, *args|
|
24
|
+
new_callback = lambda { old_arounds.call(callback, receiver, sym, *args) }
|
25
|
+
Array(args[:only]).include(sym) ? new_callback.call() : proc.call(new_callback, receiver, sym, *args)
|
26
|
+
}
|
27
|
+
else
|
28
|
+
lambda { |callback, receiver, sym, *args|
|
29
|
+
new_callback = lambda { old_arounds.call(callback, receiver, sym, *args) }
|
30
|
+
proc.call(new_callback, receiver, sym, *args)
|
31
|
+
}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
def __invoke__(sym, *args, &block)
|
35
|
+
@@arounds.call(
|
36
|
+
lambda { @value.__send__(sym, *args, &block) },
|
37
|
+
@value,
|
38
|
+
sym,
|
39
|
+
*args
|
40
|
+
)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/ick/base.rb
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__)
|
2
|
+
|
3
|
+
require 'singleton'
|
4
|
+
|
5
|
+
module Ick
|
6
|
+
|
7
|
+
class Base
|
8
|
+
include Singleton
|
9
|
+
|
10
|
+
def returns(value, result)
|
11
|
+
raise 'implemented by subclass or by calling meta-method'
|
12
|
+
end
|
13
|
+
|
14
|
+
def evaluate(value, proc)
|
15
|
+
raise 'implemented by subclass or by calling meta-method'
|
16
|
+
end
|
17
|
+
|
18
|
+
def invoke(value = nil, &proc)
|
19
|
+
result = evaluate(value, proc)
|
20
|
+
returns(value, result)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.evaluates_in_calling_environment
|
24
|
+
define_method :evaluate do |value, proc|
|
25
|
+
proc.call(value)
|
26
|
+
end
|
27
|
+
true
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.evaluates_in_value_environment
|
31
|
+
define_method :evaluate do |value, proc|
|
32
|
+
value.instance_eval(&proc)
|
33
|
+
end
|
34
|
+
true
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.returns_value
|
38
|
+
define_method :returns do |value, result|
|
39
|
+
value
|
40
|
+
end
|
41
|
+
true
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.returns_result
|
45
|
+
define_method :returns do |value, result|
|
46
|
+
result
|
47
|
+
end
|
48
|
+
true
|
49
|
+
end
|
50
|
+
|
51
|
+
#snarfed from Ruby On Rails
|
52
|
+
def self.underscore(camel_cased_word)
|
53
|
+
camel_cased_word.to_s.gsub(/::/, '/').
|
54
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
55
|
+
tr("-", "_").
|
56
|
+
downcase
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.belongs_to clazz
|
60
|
+
method_name = self.underscore(self.name.split('::')[-1])
|
61
|
+
unless clazz.method_defined?(method_name)
|
62
|
+
clazz.class_eval "
|
63
|
+
def #{method_name}(value=self,&proc)
|
64
|
+
if block_given?
|
65
|
+
#{self.name}.instance.invoke(value, &proc)
|
66
|
+
else
|
67
|
+
Invoker.new(value, #{self.name})
|
68
|
+
end
|
69
|
+
end"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
class Invoker
|
76
|
+
|
77
|
+
instance_methods.reject { |m| m =~ /^__/ }.each { |m| undef_method m }
|
78
|
+
|
79
|
+
def initialize(value, clazz)
|
80
|
+
@value = value
|
81
|
+
@clazz = clazz
|
82
|
+
end
|
83
|
+
|
84
|
+
def method_missing(sym, *args, &block)
|
85
|
+
@clazz.instance.invoke(@value) { |value|
|
86
|
+
value.__send__(sym, *args, &block)
|
87
|
+
}
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
class Let < Base
|
93
|
+
evaluates_in_calling_environment and returns_result
|
94
|
+
end
|
95
|
+
|
96
|
+
class Returning < Base
|
97
|
+
evaluates_in_calling_environment and returns_value
|
98
|
+
end
|
99
|
+
|
100
|
+
class My < Base
|
101
|
+
evaluates_in_value_environment and returns_result
|
102
|
+
end
|
103
|
+
|
104
|
+
class Inside < Base
|
105
|
+
evaluates_in_value_environment and returns_value
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
data/lib/ick/guard.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__)
|
2
|
+
|
3
|
+
require 'singleton'
|
4
|
+
|
5
|
+
module Ick
|
6
|
+
|
7
|
+
class GuardWrapper < IdentityWrapper
|
8
|
+
def initialize(value, default, proc)
|
9
|
+
@proc = proc
|
10
|
+
@default = default
|
11
|
+
super(value)
|
12
|
+
end
|
13
|
+
def __rewrap__(new_value)
|
14
|
+
self.__class.new(new_value, @default, @proc) #contagious
|
15
|
+
end
|
16
|
+
def __invoke__(sym, *args, &block)
|
17
|
+
@proc.call(@value, sym) ? super : @default
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Guard < Wrap
|
22
|
+
include Singleton
|
23
|
+
|
24
|
+
def self.guard_with(&proc)
|
25
|
+
@guard_proc = proc
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.guard
|
29
|
+
@guard_proc
|
30
|
+
end
|
31
|
+
|
32
|
+
def invoke(value, &proc)
|
33
|
+
invoke_wrapped(value, GuardWrapper, nil, self.class.guard, &proc)
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
class Maybe < Guard
|
39
|
+
guard_with { |value, sym| value.nil? == false }
|
40
|
+
evaluates_in_calling_environment and returns_result
|
41
|
+
end
|
42
|
+
|
43
|
+
class Try < Guard
|
44
|
+
guard_with { |value, sym| value.respond_to?(sym) == true }
|
45
|
+
evaluates_in_calling_environment and returns_result
|
46
|
+
end
|
47
|
+
|
48
|
+
class Please < Ick::Guard
|
49
|
+
guard_with { |value, sym| value.respond_to?(sym) == true } # defines your guard condition
|
50
|
+
evaluates_in_value_environment and returns_result # defines its behaviour
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
data/lib/ick/sugar.rb
ADDED
data/lib/ick/tee.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
module Ick
|
2
|
+
|
3
|
+
=begin
|
4
|
+
WARNING: UNTESTED
|
5
|
+
=end
|
6
|
+
class Tee < Wrap
|
7
|
+
|
8
|
+
def invoke(*values, &proc)
|
9
|
+
invoke_wrapped(values, ArrayWrapper, nil, &proc)
|
10
|
+
end
|
11
|
+
|
12
|
+
def unwrap(wrapped, wrapper_class)
|
13
|
+
wrapped.respond_to?(:__value__) ? wrapped.__value__.first : wrapped
|
14
|
+
end
|
15
|
+
|
16
|
+
evaluates_in_calling_environment and returns_result
|
17
|
+
|
18
|
+
def self.belongs_to clazz
|
19
|
+
method_name = self.underscore(self.name.split('::')[-1])
|
20
|
+
unless clazz.method_defined?(method_name)
|
21
|
+
clazz.class_eval "
|
22
|
+
def #{method_name}(*values,&proc)
|
23
|
+
values = [self] if values.empty?
|
24
|
+
if block_given?
|
25
|
+
#{self.name}.instance.invoke(*values, &proc)
|
26
|
+
else
|
27
|
+
Invoker.new(values, #{self.name})
|
28
|
+
end
|
29
|
+
end"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
data/lib/ick/version.rb
CHANGED
data/lib/ick/wrap.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
module Ick
|
2
|
+
|
3
|
+
class Wrapper
|
4
|
+
|
5
|
+
alias :__respond_to? :respond_to?
|
6
|
+
alias :__instance_eval :instance_eval
|
7
|
+
alias :__class :class
|
8
|
+
alias :__inspect :inspect
|
9
|
+
instance_methods.reject { |m| m =~ /^__/ }.each { |m| undef_method m }
|
10
|
+
alias :instance_eval :__instance_eval
|
11
|
+
|
12
|
+
def initialize(value, *additional_attributes)
|
13
|
+
@value = value
|
14
|
+
end
|
15
|
+
|
16
|
+
def __value__
|
17
|
+
@value
|
18
|
+
end
|
19
|
+
def __invocation__
|
20
|
+
@invocation
|
21
|
+
end
|
22
|
+
def __rewrap__(value)
|
23
|
+
raise 'implemented by subclass'
|
24
|
+
end
|
25
|
+
def __invoke__(sym, *args, &block)
|
26
|
+
raise 'implemented by subclass'
|
27
|
+
end
|
28
|
+
def method_missing(sym, *args, &block)
|
29
|
+
__rewrap__(__invoke__(sym, *args, &block))
|
30
|
+
end
|
31
|
+
def respond_to?(sym)
|
32
|
+
@value.respond_to?(sym) || self.__respond_to?(sym)
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.is_contagious
|
36
|
+
define_method(:__rewrap__) { |value|
|
37
|
+
self.__class.new(value)
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.is_not_contagious
|
42
|
+
define_method(:__rewrap__) { |value| value }
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
class IdentityWrapper < Wrapper
|
48
|
+
|
49
|
+
def __invoke__(sym, *args, &block)
|
50
|
+
@value.__send__(sym, *args, &block)
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
class ArrayWrapper < Wrapper
|
56
|
+
|
57
|
+
def initialize(values, *additional_attributes)
|
58
|
+
super(values)
|
59
|
+
end
|
60
|
+
|
61
|
+
def __invoke__(sym, *args, &block)
|
62
|
+
p "#{sym} -> #{@value.inspect}"
|
63
|
+
@value.map { |_| _.__send__(sym, *args, &block) }
|
64
|
+
end
|
65
|
+
|
66
|
+
is_contagious
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
class Wrap < Base
|
71
|
+
|
72
|
+
def wrap(value, wrapper_class, *additional_arguments)
|
73
|
+
wrapper_class.new(value, *additional_arguments)
|
74
|
+
end
|
75
|
+
|
76
|
+
def unwrap(wrapped, wrapper_class)
|
77
|
+
wrapped.respond_to?(:__value__) ? wrapped.__value__ : wrapped
|
78
|
+
end
|
79
|
+
|
80
|
+
def invoke_wrapped(value, wrapper_class, *additional_arguments, &proc)
|
81
|
+
wrapped_value = wrap(value, wrapper_class, *additional_arguments)
|
82
|
+
wrapped_result = evaluate(wrapped_value, proc)
|
83
|
+
returns(unwrap(wrapped_value, wrapper_class), unwrap(wrapped_result, wrapper_class))
|
84
|
+
end
|
85
|
+
|
86
|
+
def invoke
|
87
|
+
raise 'subclass should override and invoke invoke_wrapped'
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
data/test/test_ick.rb
CHANGED
@@ -125,15 +125,28 @@ class TestIck < Test::Unit::TestCase
|
|
125
125
|
)
|
126
126
|
end
|
127
127
|
|
128
|
-
def
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
128
|
+
# def test_tee_basic
|
129
|
+
# box1 = Box.new(1)
|
130
|
+
# box2 = Box.new(2)
|
131
|
+
# tee(box1,box2) { |box| box.value = 7 }
|
132
|
+
# assert_equal(7, box1.value)
|
133
|
+
# assert_equal(7, box2.value)
|
134
|
+
# end
|
135
|
+
|
136
|
+
# def test_tee_semantics
|
137
|
+
# first_log = []
|
138
|
+
# second_log = []
|
139
|
+
# [first_log, second_log].map do |value|
|
140
|
+
# log << "first line of output"
|
141
|
+
# log << "second line of output"
|
142
|
+
# end
|
143
|
+
# assert_equal(, log)
|
144
|
+
# out = []
|
145
|
+
# tee(1, 2, 3) do |value|
|
146
|
+
# out << "a#{value}"
|
147
|
+
# out << "b#{value}"
|
148
|
+
# end
|
149
|
+
# assert_equal(%w(a1 b1 c1 a2 b2 c2), out)
|
150
|
+
# end
|
138
151
|
|
139
152
|
end
|
data/website/index.html
CHANGED
@@ -33,7 +33,7 @@
|
|
33
33
|
<h1>Invocation Construction Kit</h1>
|
34
34
|
<div id="version" class="clickable" onclick='document.location = "http://rubyforge.org/projects/ick"; return false'>
|
35
35
|
<p>Get Version</p>
|
36
|
-
<a href="http://rubyforge.org/projects/ick" class="numbers">0.2.
|
36
|
+
<a href="http://rubyforge.org/projects/ick" class="numbers">0.2.1</a>
|
37
37
|
</div>
|
38
38
|
<h1>→ ‘ick’</h1>
|
39
39
|
|
@@ -66,7 +66,7 @@
|
|
66
66
|
<p>Although Ruby borrows many of its features from Lisp and its syntax from Algol, it does not have block-local variables. In other words, if you declare a variable anywhere inside of a method, that variable is visible everywhere in that method. This is a problem, because it encourages writing methods where the instance variables create lot of dependencies between different expressions. Those methods can be hard to understand and refactor.</p>
|
67
67
|
|
68
68
|
|
69
|
-
<p>Ick solves this problem by providing
|
69
|
+
<p>Ick solves this problem by providing a block structure method: #let. #let takes an expression and binds it to a variable inside of a block. For example, if you want someone’s phone number only if they are a friend:</p>
|
70
70
|
|
71
71
|
|
72
72
|
<p><pre class='syntax'>
|
@@ -74,10 +74,7 @@
|
|
74
74
|
</pre></p>
|
75
75
|
|
76
76
|
|
77
|
-
<p>This code makes it clear that you only need the <code>person</code> 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.
|
78
|
-
|
79
|
-
|
80
|
-
<p>(The four methods were inspired by <a href="http://blog.rubyenrails.nl/articles/2008/02/18/our-daily-method-10-object-r-rs-ds-s">Michiel de Mare’s post on the same subject</a>, 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.)</p>
|
77
|
+
<p>This code makes it clear that you only need the <code>person</code> 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. Ick provides three other block structure methods (#returning, #my, and #inside) that are detailed on the <a href="inside.html">Inside Ick</a> page.</p>
|
81
78
|
|
82
79
|
|
83
80
|
<h2>Guarded Evaluation</h2>
|
@@ -184,112 +181,34 @@
|
|
184
181
|
<p>The point is, <code>try(program) { responsibly }</code>. You choose which classes to open and which methods to add. <a href="http://avdi.org/devblog/2008/02/25/full-disclosure/">All I’m saying is this: before re-opening a class, did you go through the rest of your toolbox first?</a></p>
|
185
182
|
|
186
183
|
|
187
|
-
<h2>
|
188
|
-
|
189
|
-
|
190
|
-
<p>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 <em>self</em> stay the same, or does it become the value in the block?</p>
|
191
|
-
|
192
|
-
|
193
|
-
<p>The methods #try and #maybe are both implemented as <em>evaluates_in_calling_environment</em>, 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:</p>
|
194
|
-
|
195
|
-
|
196
|
-
<p><pre class='syntax'>
|
197
|
-
<span class="keyword">class </span><span class="class">Please</span> <span class="punct"><</span> <span class="constant">Ick</span><span class="punct">::</span><span class="constant">Guard</span>
|
198
|
-
<span class="ident">guard_with</span> <span class="punct">{</span> <span class="punct">|</span><span class="ident">value</span><span class="punct">,</span> <span class="ident">sym</span><span class="punct">|</span> <span class="ident">value</span><span class="punct">.</span><span class="ident">respond_to?</span><span class="punct">(</span><span class="ident">sym</span><span class="punct">)</span> <span class="punct">==</span> <span class="constant">true</span> <span class="punct">}</span>
|
199
|
-
<span class="ident">evaluates_in_value_environment</span> <span class="keyword">and</span> <span class="ident">returns_result</span>
|
200
|
-
<span class="ident">belongs_to</span> <span class="constant">Object</span>
|
201
|
-
<span class="keyword">end</span>
|
202
|
-
|
203
|
-
<span class="ident">please</span><span class="punct">(...)</span> <span class="punct">{</span> <span class="ident">may</span><span class="punct">.</span><span class="ident">i</span><span class="punct">.</span><span class="ident">have</span><span class="punct">.</span><span class="ident">some</span><span class="punct">.</span><span class="ident">more</span> <span class="punct">}</span>
|
204
|
-
</pre></p>
|
205
|
-
|
206
|
-
|
207
|
-
<p>The method #please executes in the value’s environment, and thus it can call methods directly.</p>
|
208
|
-
|
209
|
-
|
210
|
-
<p>You already saw #let, it takes your expression and binds it to a parameter, then it evaluates a block in the calling environment, just as #try evaluates and guards its block in the calling environment. If you want something that behaves like #let but evaluates in the value’s environment just like #please, you can use #my:</p>
|
211
|
-
|
212
|
-
|
213
|
-
<p><pre class='syntax'>
|
214
|
-
<span class="ident">my</span><span class="punct">(</span><span class="constant">Person</span><span class="punct">.</span><span class="ident">find</span><span class="punct">(</span><span class="symbol">:first</span><span class="punct">,</span> <span class="punct">...))</span> <span class="keyword">do</span>
|
215
|
-
<span class="ident">first_name</span> <span class="punct">=</span> <span class="punct">'</span><span class="string">Charles</span><span class="punct">'</span>
|
216
|
-
<span class="ident">last_name</span> <span class="punct">=</span> <span class="punct">'</span><span class="string">Babbage</span><span class="punct">'</span>
|
217
|
-
<span class="ident">friends</span> <span class="punct"><<</span> <span class="punct">'</span><span class="string">Ada Lovelace</span><span class="punct">'</span>
|
218
|
-
<span class="keyword">end</span>
|
219
|
-
</pre></p>
|
220
|
-
|
221
|
-
|
222
|
-
<p>This will return Charles Babbage’s friends. On the surface, <em>evaluates_in_value_environment</em> 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.</p>
|
223
|
-
|
224
|
-
|
225
|
-
<p>So #let and #my both pass an expression to a block and return the result. Given that they both ‘declare’ <em>returns_result</em>, this is not surprising. But there is another choice: <em>returns_value</em> instead of <em>returns_result</em>. Ruby on Rails includes the popular #returning method, and it works the same in Ick:</p>
|
184
|
+
<h2>Is Ick for me?</h2>
|
226
185
|
|
227
186
|
|
228
|
-
<p
|
229
|
-
<span class="ident">returning</span><span class="punct">(</span><span class="constant">Person</span><span class="punct">.</span><span class="ident">find</span><span class="punct">(</span><span class="symbol">:first</span><span class="punct">,</span> <span class="punct">...))</span> <span class="keyword">do</span> <span class="punct">|</span><span class="ident">p</span><span class="punct">|</span>
|
230
|
-
<span class="ident">p</span><span class="punct">.</span><span class="ident">first_name</span> <span class="punct">=</span> <span class="punct">'</span><span class="string">Charles</span><span class="punct">'</span>
|
231
|
-
<span class="ident">p</span><span class="punct">.</span><span class="ident">last_name</span> <span class="punct">=</span> <span class="punct">'</span><span class="string">Babbage</span><span class="punct">'</span>
|
232
|
-
<span class="ident">p</span><span class="punct">.</span><span class="ident">friends</span> <span class="punct"><<</span> <span class="punct">'</span><span class="string">Ada Lovelace</span><span class="punct">'</span>
|
233
|
-
<span class="keyword">end</span>
|
234
|
-
</pre></p>
|
235
|
-
|
236
|
-
|
237
|
-
<p>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?</p>
|
238
|
-
|
239
|
-
|
240
|
-
<p><pre class='syntax'>
|
241
|
-
<span class="ident">inside</span><span class="punct">(</span><span class="constant">Person</span><span class="punct">.</span><span class="ident">find</span><span class="punct">(</span><span class="symbol">:first</span><span class="punct">,</span> <span class="punct">...))</span> <span class="keyword">do</span>
|
242
|
-
<span class="ident">first_name</span> <span class="punct">=</span> <span class="punct">'</span><span class="string">Charles</span><span class="punct">'</span>
|
243
|
-
<span class="ident">last_name</span> <span class="punct">=</span> <span class="punct">'</span><span class="string">Babbage</span><span class="punct">'</span>
|
244
|
-
<span class="ident">friends</span> <span class="punct"><<</span> <span class="punct">'</span><span class="string">Ada Lovelace</span><span class="punct">'</span>
|
245
|
-
<span class="keyword">end</span>
|
246
|
-
</pre></p>
|
247
|
-
|
248
|
-
|
249
|
-
<p>The method #inside returns the value and evaluates the block in the value’s environment.</p>
|
250
|
-
|
251
|
-
|
252
|
-
<h2>Under the Hood</h2>
|
187
|
+
<p>Ick does provide a number of very convenient methods for abstracting evaluation. If you adopt them for your project, your code will be more readable, more succinct, and easier to refactor. That being said, it will not make your teeth whiter or help you sleep if you have a newborn.</p>
|
253
188
|
|
254
189
|
|
255
|
-
<p>
|
190
|
+
<p><em>Every programming problem can be solved with another layer of abstraction, except the problem of too many layers of abstraction</em></p>
|
256
191
|
|
257
192
|
|
258
|
-
<
|
193
|
+
<p>The important caveat about Ick is that its implementation adds abstraction. If all you want are two or three specific methods, writing them as simply and as directly as possible makes for a very simple implementation. Ick uses classes and templates instead of simple methods so that when you are ready to start writing your own abstractions, you can easily use the pieces that Ick has built-in. If Ick was written as a collection of cool methods, you would not be able to extend it without copying and pasting.</p>
|
259
194
|
|
260
195
|
|
261
|
-
<p>Ick
|
262
|
-
|
263
|
-
|
264
|
-
<p><pre class='syntax'>
|
265
|
-
<span class="keyword">class </span><span class="class">Object</span>
|
266
|
-
<span class="keyword">def </span><span class="method">returning</span><span class="punct">(</span><span class="ident">value</span><span class="punct">)</span>
|
267
|
-
<span class="keyword">yield</span><span class="punct">(</span><span class="ident">value</span><span class="punct">)</span>
|
268
|
-
<span class="ident">value</span>
|
269
|
-
<span class="keyword">end</span>
|
270
|
-
<span class="keyword">end</span>
|
271
|
-
</pre></p>
|
272
|
-
|
273
|
-
|
274
|
-
<p>So why bother with Ick? Well, Ick is a construction kit. if you want to make a method just like Object#returning, only <em>X</em> (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.</p>
|
275
|
-
|
276
|
-
|
277
|
-
<p>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.</p>
|
196
|
+
<p>Ick raises how you handle things to the level of first-class objects in Ruby, so you can mix and match and separate concerns as you see fit. Logging, permissions, error handling… These are some of the places you can take Ick. Have fun.</p>
|
278
197
|
|
279
198
|
|
280
|
-
<
|
199
|
+
<h2>Where can I read about what’s going on inside Ick?</h2>
|
281
200
|
|
282
201
|
|
283
|
-
<p
|
202
|
+
<p><a href="inside.html">Inside Ick</a></p>
|
284
203
|
|
285
204
|
|
286
|
-
<
|
205
|
+
<h2>Administrivia</h2>
|
287
206
|
|
288
207
|
|
289
|
-
<
|
208
|
+
<h3>Home Sweet Home</h3>
|
290
209
|
|
291
210
|
|
292
|
-
<
|
211
|
+
<p><a href="http://ick.rubyforge.org">ick.rubyforge.org</a></p>
|
293
212
|
|
294
213
|
|
295
214
|
<h3>How to submit patches</h3>
|
data/website/index.txt
CHANGED
@@ -22,15 +22,13 @@ h2. Block Structured Ruby
|
|
22
22
|
|
23
23
|
Although Ruby borrows many of its features from Lisp and its syntax from Algol, it does not have block-local variables. In other words, if you declare a variable anywhere inside of a method, that variable is visible everywhere in that method. This is a problem, because it encourages writing methods where the instance variables create lot of dependencies between different expressions. Those methods can be hard to understand and refactor.
|
24
24
|
|
25
|
-
Ick solves this problem by providing
|
25
|
+
Ick solves this problem by providing a block structure method: #let. #let takes an expression and binds it to a variable inside of a block. For example, if you want someone's phone number only if they are a friend:
|
26
26
|
|
27
27
|
<pre syntax="ruby">
|
28
28
|
let(Person.find(:first, ...)) { |person| person.phone_number if person.friend? }
|
29
29
|
</pre>
|
30
30
|
|
31
|
-
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.
|
32
|
-
|
33
|
-
(The four methods were inspired by "Michiel de Mare's post on the same subject":http://blog.rubyenrails.nl/articles/2008/02/18/our-daily-method-10-object-r-rs-ds-s, 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.)
|
31
|
+
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. Ick provides three other block structure methods (#returning, #my, and #inside) that are detailed on the "Inside Ick":inside.html page.
|
34
32
|
|
35
33
|
h2. Guarded Evaluation
|
36
34
|
|
@@ -112,88 +110,25 @@ If you simply want everything you see here working exactly as it's shown, simply
|
|
112
110
|
|
113
111
|
The point is, @try(program) { responsibly }@. You choose which classes to open and which methods to add. "All I’m saying is this: before re-opening a class, did you go through the rest of your toolbox first?":http://avdi.org/devblog/2008/02/25/full-disclosure/
|
114
112
|
|
115
|
-
h2.
|
116
|
-
|
117
|
-
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?
|
118
|
-
|
119
|
-
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:
|
120
|
-
|
121
|
-
<pre syntax="ruby">
|
122
|
-
class Please < Ick::Guard
|
123
|
-
guard_with { |value, sym| value.respond_to?(sym) == true }
|
124
|
-
evaluates_in_value_environment and returns_result
|
125
|
-
belongs_to Object
|
126
|
-
end
|
127
|
-
|
128
|
-
please(...) { may.i.have.some.more }
|
129
|
-
</pre>
|
130
|
-
|
131
|
-
The method #please executes in the value's environment, and thus it can call methods directly.
|
113
|
+
h2. Is Ick for me?
|
132
114
|
|
133
|
-
|
115
|
+
Ick does provide a number of very convenient methods for abstracting evaluation. If you adopt them for your project, your code will be more readable, more succinct, and easier to refactor. That being said, it will not make your teeth whiter or help you sleep if you have a newborn.
|
134
116
|
|
135
|
-
|
136
|
-
my(Person.find(:first, ...)) do
|
137
|
-
first_name = 'Charles'
|
138
|
-
last_name = 'Babbage'
|
139
|
-
friends << 'Ada Lovelace'
|
140
|
-
end
|
141
|
-
</pre>
|
142
|
-
|
143
|
-
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.
|
144
|
-
|
145
|
-
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:
|
146
|
-
|
147
|
-
<pre syntax="ruby">
|
148
|
-
returning(Person.find(:first, ...)) do |p|
|
149
|
-
p.first_name = 'Charles'
|
150
|
-
p.last_name = 'Babbage'
|
151
|
-
p.friends << 'Ada Lovelace'
|
152
|
-
end
|
153
|
-
</pre>
|
117
|
+
_Every programming problem can be solved with another layer of abstraction, except the problem of too many layers of abstraction_
|
154
118
|
|
155
|
-
|
119
|
+
The important caveat about Ick is that its implementation adds abstraction. If all you want are two or three specific methods, writing them as simply and as directly as possible makes for a very simple implementation. Ick uses classes and templates instead of simple methods so that when you are ready to start writing your own abstractions, you can easily use the pieces that Ick has built-in. If Ick was written as a collection of cool methods, you would not be able to extend it without copying and pasting.
|
156
120
|
|
157
|
-
|
158
|
-
inside(Person.find(:first, ...)) do
|
159
|
-
first_name = 'Charles'
|
160
|
-
last_name = 'Babbage'
|
161
|
-
friends << 'Ada Lovelace'
|
162
|
-
end
|
163
|
-
</pre>
|
164
|
-
|
165
|
-
The method #inside returns the value and evaluates the block in the value's environment.
|
166
|
-
|
167
|
-
h2. Under the Hood
|
168
|
-
|
169
|
-
Ick is actually 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. It's easy to build your own methods.
|
170
|
-
|
171
|
-
h3. Every programming problem can be solved with another layer of abstraction, except the problem of too many layers of abstraction
|
172
|
-
|
173
|
-
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:
|
174
|
-
|
175
|
-
<pre syntax="ruby">
|
176
|
-
class Object
|
177
|
-
def returning(value)
|
178
|
-
yield(value)
|
179
|
-
value
|
180
|
-
end
|
181
|
-
end
|
182
|
-
</pre>
|
183
|
-
|
184
|
-
So why bother with Ick? Well, Ick is a construction kit. 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.
|
185
|
-
|
186
|
-
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.
|
121
|
+
Ick raises how you handle things to the level of first-class objects in Ruby, so you can mix and match and separate concerns as you see fit. Logging, permissions, error handling... These are some of the places you can take Ick. Have fun.
|
187
122
|
|
188
|
-
|
123
|
+
h2. Where can I read about what's going on inside Ick?
|
189
124
|
|
190
|
-
|
125
|
+
"Inside Ick":inside.html
|
191
126
|
|
192
|
-
|
127
|
+
h2. Administrivia
|
193
128
|
|
194
|
-
|
129
|
+
h3. Home Sweet Home
|
195
130
|
|
196
|
-
|
131
|
+
"ick.rubyforge.org":http://ick.rubyforge.org
|
197
132
|
|
198
133
|
h3. How to submit patches
|
199
134
|
|
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-03-
|
6
|
+
version: 0.2.1
|
7
|
+
date: 2008-03-09 00:00:00 -05:00
|
8
8
|
summary: Invocation Construction Kit
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -38,7 +38,13 @@ files:
|
|
38
38
|
- config/hoe.rb
|
39
39
|
- config/requirements.rb
|
40
40
|
- lib/ick.rb
|
41
|
+
- lib/ick/advisor.rb
|
42
|
+
- lib/ick/base.rb
|
43
|
+
- lib/ick/guard.rb
|
44
|
+
- lib/ick/sugar.rb
|
45
|
+
- lib/ick/tee.rb
|
41
46
|
- lib/ick/version.rb
|
47
|
+
- lib/ick/wrap.rb
|
42
48
|
- log/debug.log
|
43
49
|
- script/destroy
|
44
50
|
- script/generate
|