rubymurray 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (9) hide show
  1. data/LICENSE +20 -0
  2. data/README +35 -0
  3. data/Rakefile +379 -0
  4. data/doc/currybook.rdoc +146 -0
  5. data/doc/jamis.rb +591 -0
  6. data/lib/curry.rb +291 -0
  7. data/setup.rb +35 -0
  8. data/test/tc_curry.rb +170 -0
  9. metadata +61 -0
@@ -0,0 +1,291 @@
1
+ #
2
+ # Ruby Murray - Ruby version of Perl's Sub::Curry.
3
+ # (c)2006 Ross Bamford
4
+ #
5
+ # For Ruby Quiz 64. License: Same as Ruby.
6
+ # See +Curry+ for documentation and +TestCurry+ for examples.
7
+ require 'singleton'
8
+
9
+ # *Ruby* *Murray* is a Ruby port of Perl's Sub::Curry library that allows
10
+ # curried blocks and methods to be handled in a pretty flexible way.
11
+ #
12
+ # See http://search.cpan.org/~lodin/Sub-Curry-0.8/lib/Sub/Curry.pm and
13
+ # http://search.cpan.org/~lodin/Sub-Curry-0.8/lib/Sub/Curry/Cookbook.pod
14
+ # for details on the original, and some general background.
15
+ #
16
+ # Simple usage:
17
+ #
18
+ # curry = lambda { |*args| args }.curry(Curry::HOLE, "foo", Curry::HOLE)
19
+ # curry.call(1,3) # => [1, "foo", 3]
20
+ #
21
+ # curry = "string".method(:slice).curry(Curry::HOLE, 2)
22
+ # curry.call(0) # => "st"
23
+ # curry.call(2) # => "ri"
24
+ #
25
+ # The +curry+ methods are provided by the +Curriable+ module, which simply
26
+ # provides convenient wrapping for Curry.new. There are a few variations
27
+ # between the various forms, but mostly they are equivalent and can be
28
+ # used interchangeably.
29
+ #
30
+ # See TestCurry (and click the method signatures) for more usage.
31
+ #
32
+ # Curried procs are immutable once created. If you wish to apply further
33
+ # special spice to a curried method, you may do so either using the
34
+ # instance method +new+ to create a new curried proc by applying new
35
+ # spice to the old spice, or by passing the special spices directly to
36
+ # a +call+.
37
+ #
38
+ # You can download Ruby Murray (with documentation and explanatory
39
+ # comments) from http://roscopeco.co.uk/ruby-quiz-entries/64/curry.rb
40
+ class Curry
41
+ VERSION = '0.1.2'
42
+
43
+ # A whitehole removes the blackhole, but the spice that has been put
44
+ # into the blackhole remains since blackholes themselves don't store
45
+ # anything.
46
+ WHITEHOLE = Object.new
47
+
48
+ # An antihole put in a hole makes the hole disappear. If the spice is
49
+ # 1, <HOLE>, 3, <HOLE>, 4 and 2, <ANTIHOLE>, 5 is applied then the
50
+ # result will become 1, 2, 3, 4, 5.
51
+ ANTIHOLE = Object.new
52
+
53
+ def WHITEHOLE.inspect #:nodoc:
54
+ "<WHITEHOLE>"
55
+ end
56
+ def ANTIHOLE.inspect #:nodoc:
57
+ "<ANTIHOLE>"
58
+ end
59
+
60
+ # Just a base class for 'active' special spices (holes, really).
61
+ # SpiceArgs have a spice_arg method that must return an array.
62
+ # Maybe you can subclass up your own special spices...
63
+ #
64
+ # All the standard subclasses are singletons, the instance of
65
+ # which is assigned to the appropriate constant (HOLE, BLACKHOLE,
66
+ # etc).
67
+ class SpiceArg
68
+ def initialize(name)
69
+ @name = name
70
+ end
71
+
72
+ def spice_arg(args_remain)
73
+ raise NoMethodError, "Abstract method"
74
+ end
75
+
76
+ def inspect
77
+ "<#{@name}>"
78
+ end
79
+ end
80
+
81
+ class HoleArg < SpiceArg #:nodoc: all
82
+ include Singleton
83
+ def initialize; super("HOLE"); end
84
+ def spice_arg(args_remain)
85
+ a = args_remain.shift
86
+ if a == ANTIHOLE
87
+ []
88
+ else
89
+ [a]
90
+ end
91
+ end
92
+ end
93
+
94
+ class BlackHoleArg < SpiceArg #:nodoc: all
95
+ include Singleton
96
+ def initialize; super("BLACKHOLE"); end
97
+ def spice_arg(args_remain)
98
+ if idx = args_remain.index(WHITEHOLE)
99
+ args_remain.slice!(0..idx)[0..-2]
100
+ else
101
+ args_remain.slice!(0..args_remain.length)
102
+ end
103
+ end
104
+ end
105
+
106
+ class AntiSpiceArg < SpiceArg #:nodoc: all
107
+ include Singleton
108
+ def initialize; super("ANTISPICE"); end
109
+ def spice_arg(args_remain)
110
+ args_remain.shift
111
+ []
112
+ end
113
+ end
114
+
115
+
116
+ # Special spice that calls out to a block to get it's argument.
117
+ # The block is called each time an argument is required.
118
+ # Unlike the other spices, this must be instantiated with the
119
+ # block you want lazily evaluated.
120
+ class LazySpice < Curry::SpiceArg
121
+ def initialize( &promise )
122
+ super("LAZYSPICE")
123
+ @promise = promise
124
+ end
125
+
126
+ def spice_arg( args ) # called to provide the missing argument
127
+ [@promise.call(args)]
128
+ end
129
+ end
130
+
131
+ # A hole is what it sounds like: a gap in the argument list. Later,
132
+ # when the subroutine is called the holes are filled in. So if the
133
+ # spice is 1, <HOLE>, 3 and then 2, 4 is applied to the curried proc,
134
+ # the resulting argument list is 1, 2, 3, 4.
135
+ #
136
+ # Holes can be called "scalar inserters" that default to +nil+.
137
+ HOLE = HoleArg.instance
138
+
139
+ # A blackhole is like a hole for lists that never gets full. There's
140
+ # an imaginary untouchable blackhole at the end of the spice. The
141
+ # blackhole thusly inserts the new spice before itself. The blackhole
142
+ # never gets full because nothing is ever stored in a blackhole as it
143
+ # isn't a hole really...
144
+ #
145
+ # Blackholes are used to move the point of insertion from the end to
146
+ # somewhere else, so you can curry the end of the argument list.
147
+ #
148
+ # Blackholes can be called "list inserters" that defaults to the
149
+ # empty list.
150
+ BLACKHOLE = BlackHoleArg.instance
151
+
152
+ # An antispice is like a hole except that when it's filled it disappears.
153
+ # It's like a combination of a hole and an antihole. If the spice is
154
+ # 1, <ANTISPICE>, 3 and 2, 4 is applied, then the result will become
155
+ # 1, 3, 4.
156
+ ANTISPICE = AntiSpiceArg.instance
157
+
158
+ # The raw spice held by this curried proc. May contain special
159
+ # spices.
160
+ attr_reader :spice
161
+
162
+ # The block (+Proc+) for which arguments are curried.
163
+ attr_reader :uncurried
164
+
165
+ # call-seq:
166
+ # Curry.new(*spice) { |*args| ... } -> #&lt;Curry...&gt;
167
+ # Curry.new(callable, *spice) -> #&lt;Curry...&gt;
168
+ #
169
+ # Create a new curry with the specified spice and
170
+ # block or callable object. The second form requires only
171
+ # that the first argument respond_to?(:call)
172
+ def initialize(*spice, &block)
173
+ block = block || (spice.shift if spice.first.respond_to?(:call))
174
+ raise ArgumentError, "No block supplied" unless block
175
+ @spice, @uncurried = spice, block
176
+ end
177
+
178
+ # call-seq:
179
+ # some_curry.call(*args) { |b| ... } -> result
180
+ # some_curry[*args] { |b| ... } -> result
181
+ #
182
+ # Call the curried proc, passing the supplied arguments.
183
+ # This method resolves all special spices and passes the
184
+ # resolved arguments to the block. If a block is passed to
185
+ # +call+ it will be passed on as a block argument to the curried
186
+ # method *only* if this curry was created from a +Method+. Curries
187
+ # created from a block passed to Curry.new (or from Proc#curry)
188
+ # cannot have block arguments passed to them.
189
+ #
190
+ # Unlike Perl's Sub::Curry implementation, special spices *may*
191
+ # be passed in the call arguments, and are applied as with new.
192
+ # This means that whiteholes and antiholes can be passed in to
193
+ # make single-call modifications to the argument spice.
194
+ # This probably isn't as great on performance but it's more fun.
195
+ #
196
+ # see also +new+
197
+ def call(*args, &blk)
198
+ @uncurried.call(*call_spice(args), &blk)
199
+ end
200
+
201
+ # Had some trouble with aliases and doc so it's done this way
202
+ def [](*args) # :nodoc:
203
+ call(*args)
204
+ end
205
+
206
+ # call-seq:
207
+ # some_curry.new(*spice) -> #&lt;Curry...&gt;
208
+ #
209
+ # Create a new curried proc by applying the supplied spice to the
210
+ # current spice in this curried proc. This does not simply append
211
+ # the spices - Arguments in the supplied spice are applied to the
212
+ # curried spice arguments, with black/white hole and antiholes
213
+ # operating as documented.
214
+ #
215
+ # See also +call+.
216
+ def new(*spice)
217
+ Curry.new(*merge_spice(spice), &@uncurried)
218
+ end
219
+
220
+ # call-seq:
221
+ # some_curry.to_proc -> #&lt;Proc...&gt;
222
+ #
223
+ # Convert to a proc
224
+ def to_proc
225
+ # since we're immutable we can keep this
226
+ @extern_proc ||= method(:call).to_proc
227
+ end
228
+
229
+ private
230
+
231
+ # Handles new curry merges
232
+ def merge_spice(spice)
233
+ largs = spice.dup
234
+
235
+ res = @spice.inject([]) do |res, sparg|
236
+ # If we've used all the new spice, don't
237
+ # touch any more of the old spice.
238
+ if sparg.is_a?(SpiceArg) && !largs.empty?
239
+ res + sparg.spice_arg(largs)
240
+ else
241
+ res << sparg
242
+ end
243
+ end
244
+
245
+ res + largs
246
+ end
247
+
248
+ # Merges, then resolves all special spices
249
+ def call_spice(args)
250
+ sp = merge_spice(args)
251
+ sp.inject([]) do |ary, a|
252
+ if a.is_a? SpiceArg
253
+ ary + a.spice_arg([])
254
+ else
255
+ ary << a
256
+ end
257
+ end
258
+ end
259
+ end
260
+
261
+ # Provided for Perl compatibility
262
+ module Sub #:nodoc: all
263
+ Curry = ::Curry
264
+ end
265
+
266
+ # Provides a +curry+ method that can be mixed in to classes that make
267
+ # sense with currying. Depends on +self+ implementing a +call+ method.
268
+ module Curriable
269
+ # call-seq:
270
+ # curriable.curry(*spice) -> #&lt;Curry...&gt;
271
+ #
272
+ # Create a new curried proc from this curriable, using the supplied
273
+ # spice.
274
+ def curry(*spice)
275
+ Curry.new(self, *spice)
276
+ end
277
+ end
278
+
279
+ unless defined? NO_CORE_CURRY
280
+ NO_CORE_CURRY = (ENV['NO_CORE_CURRY'] || $SAFE > 3)
281
+ end
282
+
283
+ unless NO_CORE_CURRY
284
+ class Proc
285
+ include Curriable
286
+ end
287
+
288
+ class Method
289
+ include Curriable
290
+ end
291
+ end
@@ -0,0 +1,35 @@
1
+ require 'rbconfig'
2
+ require 'find'
3
+ require 'ftools'
4
+
5
+ include Config
6
+
7
+ $ruby = CONFIG['ruby_install_name']
8
+ $sitedir = CONFIG["sitelibdir"]
9
+ unless $sitedir
10
+ version = CONFIG["MAJOR"]+"."+CONFIG["MINOR"]
11
+ $libdir = File.join(CONFIG["libdir"], "ruby", version)
12
+ $sitedir = $:.find {|x| x =~ /site_ruby/}
13
+ if !$sitedir
14
+ $sitedir = File.join($libdir, "site_ruby")
15
+ elsif $sitedir !~ Regexp.quote(version)
16
+ $sitedir = File.join($sitedir, version)
17
+ end
18
+ end
19
+
20
+ if (destdir = ENV['DESTDIR'])
21
+ $sitedir = destdir + $sitedir
22
+ File::makedirs($sitedir)
23
+ end
24
+
25
+ # The library files
26
+
27
+ files = Dir.chdir('lib') { Dir['**/*.rb'] }
28
+ for fn in files
29
+ fn_dir = File.dirname(fn)
30
+ target_dir = File.join($sitedir, fn_dir)
31
+ if ! File.exist?(target_dir)
32
+ File.makedirs(target_dir)
33
+ end
34
+ File::install(File.join('lib', fn), File.join($sitedir, fn), 0644, true)
35
+ end
@@ -0,0 +1,170 @@
1
+ require "#{File.dirname(__FILE__)}/../lib/curry"
2
+ require 'test/unit'
3
+
4
+ # Included for example purposes. Click method signatures to open code.
5
+ class TestCurry < Test::Unit::TestCase
6
+ def test_fixed_args
7
+ curry = Curry.new(1,2,3) { |a,b,c| [a,b,c] }
8
+ assert_equal [1,2,3], curry.call
9
+ end
10
+
11
+ def test_fixed_array_args
12
+ curry = Curry.new([1],[2,3]) { |*args| args }
13
+ assert_equal [[1],[2,3]], curry.call
14
+ end
15
+
16
+ def test_hole
17
+ curry = Curry.new(1,Curry::HOLE,3) { |a,b,c| [a,b,c] }
18
+ assert_equal [1,nil,3], curry.call
19
+ assert_equal [1,2,3], curry.call(2)
20
+
21
+ curry = Curry.new(1,Curry::HOLE,3,Curry::HOLE) { |*args| args }
22
+ assert_equal [1,2,3,4], curry.call(2,4)
23
+
24
+ # Make sure extra args go to the end
25
+ assert_equal [1,2,3,4,5,6], curry.call(2,4,5,6)
26
+
27
+ # Make sure array args are handled right.
28
+ # This tests both explicitly holed arrays
29
+ # and extra arrays at the end
30
+ assert_equal [1,[2,'two'],3,[4,0],[[14]]],
31
+ curry.call([2,'two'],[4,0],[[14]])
32
+
33
+ end
34
+
35
+ def test_antihole
36
+ curry = Curry.new(1,Curry::HOLE,3) { |*args| args }
37
+ assert_equal [1,3], curry.call(Curry::ANTIHOLE)
38
+
39
+ curry = Curry.new(1,Curry::HOLE,3,Curry::HOLE,4) { |*args| args }
40
+ assert_equal [1,2,3,4,5], curry.call(2,Curry::ANTIHOLE,5)
41
+ end
42
+
43
+ def test_antispice
44
+ curry = Curry.new(1,Curry::ANTISPICE,3,Curry::HOLE,4) { |*args| args }
45
+ assert_equal [1,3,4,5], curry.call(2,Curry::ANTIHOLE,5)
46
+ end
47
+
48
+ def test_black_hole
49
+ # There's an implicit black-hole at the end
50
+ # so this should just act as normal.
51
+ curry = Curry.new(1,Curry::BLACKHOLE) { |*args| args }
52
+ assert_equal [1,2,3], curry.call(2,3)
53
+
54
+ curry = Curry.new(1,Curry::BLACKHOLE,3,4) { |*args| args }
55
+ assert_equal [1,2,10,3,4], curry.call(2,10)
56
+ end
57
+
58
+ def test_white_hole
59
+ # spice gives 1
60
+ # blackhole gives 2
61
+ # blackhole finished by whitehole
62
+ # spice gives 3
63
+ # hole matches the 7
64
+ # spice gives 5
65
+ # remaining args give 8 and 9
66
+ curry = Curry.new(1,Curry::BLACKHOLE,3,Curry::HOLE,5) { |*args| args }
67
+ assert_equal [1,2,3,7,5,8,9], curry.call(2,Curry::WHITEHOLE,7,8,9)
68
+ # spice gives 1
69
+ # blackhole gives 10 and 20
70
+ # whitehole ends blackhole
71
+ # spice gives 3
72
+ # hole matches nothing, gives nil
73
+ # spice gives 5
74
+ assert_equal [1,10,20,3,nil,5], curry.call(10,20,Curry::WHITEHOLE)
75
+
76
+ # spice gives 1
77
+ # blackhole gives 10, 20, 25
78
+ # whitehole kills black
79
+ # spice gives 3
80
+ # hole matches 4
81
+ # spice gives 5
82
+ assert_equal [1,10,20,25,3,4,5], curry.call(10,20,25,Curry::WHITEHOLE,4)
83
+
84
+ # Multiple blackholes.
85
+ #
86
+ # spice gives 1
87
+ # blackhole 1 gives 10, 20, 25
88
+ # whitehole, blackhole 1 negated
89
+ # spice gives 6
90
+ # hole matches 40
91
+ # spice gives 3, 4
92
+ # blackhole 2 gives 50, 60
93
+ # spice gives 5
94
+ curry = Curry.new(1,Curry::BLACKHOLE,6,Curry::HOLE,3,4,Curry::BLACKHOLE,5) { |*args| args }
95
+ assert_equal [1,10,20,25,6,40,3,4,50,60,5], curry.call(10,20,25,Curry::WHITEHOLE,40,50,60)
96
+ end
97
+
98
+ def test_curry_from_curry
99
+ curry = Curry.new(1,Curry::BLACKHOLE,6,Curry::HOLE,3,4,Curry::BLACKHOLE,5) { |*args| args }
100
+ curry = curry.new(Curry::HOLE,Curry::WHITEHOLE,8,9,10)
101
+ assert_equal [1,Curry::HOLE,6,8,3,4,9,10,5], curry.spice
102
+
103
+ # How to add after that hole?
104
+ curry = curry.new(Curry::HOLE, 4, Curry::BLACKHOLE)
105
+ assert_equal [1,Curry::HOLE,6,8,3,4,9,10,5,4,Curry::BLACKHOLE], curry.spice
106
+
107
+ curry = curry.new(Curry::ANTIHOLE)
108
+ assert_equal [1,6,8,3,4,9,10,5,4,Curry::BLACKHOLE], curry.spice
109
+
110
+ # how to add after that blackhole?
111
+ curry = curry.new(3,Curry::BLACKHOLE,Curry::WHITEHOLE,0)
112
+ assert_equal [1,6,8,3,4,9,10,5,4,3,Curry::BLACKHOLE,0], curry.spice
113
+
114
+ assert_equal [1,6,8,3,4,9,10,5,4,3,2,1,0], curry.call(2,1)
115
+ end
116
+
117
+ def test_cant_block_to_curried_block
118
+ a = Curry.new(1,2) { |*args| args }
119
+
120
+ # block is lost
121
+ assert_equal [1,2,3], a.call(3) { |b| }
122
+ end
123
+
124
+ if (NO_CORE_CURRY if defined? NO_CORE_CURRY)
125
+ warn "Skipping core extension tests"
126
+ else
127
+ def test_curry_proc
128
+ a = [1,2,3,4,5]
129
+ c = Curry.new(*a) { |*args| args * 2 }
130
+ assert_equal [1,2,3,4,5,1,2,3,4,5], c.call
131
+
132
+ c = lambda { |*args| args * 2 }.curry(*a)
133
+ assert_equal [1,2,3,4,5,1,2,3,4,5], c.call
134
+ end
135
+
136
+ def test_curry_method
137
+ a = [1,2,3,4,5]
138
+ injsum = Curry.new(a.method(:inject),0)
139
+ assert_equal 15, injsum.call { |s,i| s + i }
140
+
141
+ injsum = a.method(:inject).curry(0)
142
+ assert_equal 15, injsum.call { |s,i| s + i }
143
+ end
144
+ end
145
+
146
+ def test_curry_to_proc
147
+ curry = Curry.new(Curry::HOLE, Curry::HOLE, 'thou') { |ary,i,msg| ary << "#{i} #{msg}" }
148
+ assert_equal ["1 thou", "2 thou", "3 thou"], [1,2,3].inject([],&curry)
149
+ end
150
+
151
+ def test_alt_bits
152
+ curry = Curry.new(Curry::BLACKHOLE, 'too', 'true') { |one, two, *rest| [one, two, rest] }
153
+ assert_equal [1,2,['too','true']], curry[1,2]
154
+ end
155
+
156
+ def test_perlish
157
+ s = "str"
158
+ s = Sub::Curry.new(s.method(:+), "ing")
159
+ assert_equal "string", s.call
160
+ end
161
+
162
+ def test_lazyspice_at_end_regression
163
+ a = [1,3,5]
164
+ b = [2,4,6]
165
+ l = lambda { |ary,aa,ba| ary + [aa,ba] }.curry(Curry::HOLE,Curry::HOLE,Curry::LazySpice.new { b.shift })
166
+ # bug behaviour: assert_equal [1,nil,3,nil,5,nil]
167
+ assert_equal [1,2,3,4,5,6], a.inject([], &l)
168
+ end
169
+ end
170
+