rubymurray 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+