mug 2.0.1 → 2.1.0
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.
- checksums.yaml +4 -4
- data/lib/mug/apply.rb +33 -11
- data/lib/mug/functional.rb +191 -0
- data/lib/mug.rb +1 -0
- data/test/test-functional.rb +242 -0
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fb09c157ead059eeb09c4b9f0686ec84636d08cbb46755e838e561a21645aa1e
|
|
4
|
+
data.tar.gz: 06c8ee0f76b4763199876bdd76bf6a28ed987e588248c8683108c53409943e77
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4a3e1a610087afa31dca23c0b4f72f3fe4959fd13f45e39c17ea86f058293926a97da12beceea748601a615bb1f10d5bcb0f6f6b3dc7b1c00a2b50702cc5a2b2
|
|
7
|
+
data.tar.gz: 169f4dba3371163df8f793ae66c0182fd29de7bff65546324e121466837d28d5d718277240eb859b54aefecc7063ff5c349009b85ad8f721028a5faad2b7dd5d
|
data/lib/mug/apply.rb
CHANGED
|
@@ -1,15 +1,37 @@
|
|
|
1
|
-
|
|
2
1
|
class Proc
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
2
|
+
# TruffleRuby's Proc#curry does not perform partial application for
|
|
3
|
+
# non-lambda procs; it invokes the proc immediately regardless of the
|
|
4
|
+
# number of arguments supplied.
|
|
5
|
+
if RUBY_ENGINE == 'truffleruby'
|
|
6
|
+
#
|
|
7
|
+
# Curries this Proc and partially applies parameters.
|
|
8
|
+
# If a sufficient number of arguments are supplied, it passes the
|
|
9
|
+
# supplied arguments to the original proc and returns the result.
|
|
10
|
+
# Otherwise, returns another curried proc that takes the rest of
|
|
11
|
+
# arguments.
|
|
12
|
+
#
|
|
13
|
+
def apply(*args)
|
|
14
|
+
n = arity < 0 ? -arity - 1 : arity
|
|
15
|
+
if lambda?
|
|
16
|
+
curry(n).call(*args)
|
|
17
|
+
elsif args.length >= n
|
|
18
|
+
call(*args)
|
|
19
|
+
else
|
|
20
|
+
proc {|*more| call(*args, *more) }
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
else
|
|
24
|
+
#
|
|
25
|
+
# Curries this Proc and partially applies parameters.
|
|
26
|
+
# If a sufficient number of arguments are supplied, it passes the
|
|
27
|
+
# supplied arguments to the original proc and returns the result.
|
|
28
|
+
# Otherwise, returns another curried proc that takes the rest of
|
|
29
|
+
# arguments.
|
|
30
|
+
#
|
|
31
|
+
def apply(*args)
|
|
32
|
+
n = arity < 0 ? -arity - 1 : arity
|
|
33
|
+
curry(n).call(*args)
|
|
34
|
+
end
|
|
13
35
|
end
|
|
14
36
|
end
|
|
15
37
|
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
|
|
2
|
+
class Proc
|
|
3
|
+
|
|
4
|
+
#
|
|
5
|
+
# Composes a sequence of functions.
|
|
6
|
+
#
|
|
7
|
+
# A function is anything that responds to #to_proc, so
|
|
8
|
+
# symbols are allowed.
|
|
9
|
+
#
|
|
10
|
+
# This proc is prepended at the start of the composition.
|
|
11
|
+
#
|
|
12
|
+
def compose *funcs
|
|
13
|
+
return self if funcs.empty?
|
|
14
|
+
self >> funcs.map(&:to_proc).reduce(:>>)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
#
|
|
18
|
+
# Composes a sequence of functions.
|
|
19
|
+
#
|
|
20
|
+
# A function is anything that responds to #to_proc, so
|
|
21
|
+
# symbols are allowed.
|
|
22
|
+
#
|
|
23
|
+
# This proc is appended at the end of the composition.
|
|
24
|
+
#
|
|
25
|
+
def precompose *funcs
|
|
26
|
+
return self if funcs.empty?
|
|
27
|
+
self << funcs.map(&:to_proc).reduce(:>>)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
#
|
|
31
|
+
# Applies this function to each element of +args+ in order.
|
|
32
|
+
#
|
|
33
|
+
# `proc.mapply(*args)` is equivalent to `args.map(&proc)`
|
|
34
|
+
#
|
|
35
|
+
def mapply *args
|
|
36
|
+
args.map {|*a| self.call(*a) }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
#
|
|
40
|
+
# Generates a function that memoizes this one. For a given
|
|
41
|
+
# set of parameters, this proc is only invoked once; the
|
|
42
|
+
# result is remembered for subsequent invocations.
|
|
43
|
+
#
|
|
44
|
+
def memoize
|
|
45
|
+
cache = {}
|
|
46
|
+
lambda do |*args|
|
|
47
|
+
cache.fetch(args) {|_| cache[args] = self.call(*args) }
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
alias memoise memoize
|
|
51
|
+
|
|
52
|
+
#
|
|
53
|
+
# Generates a function that reorders its arguments according
|
|
54
|
+
# to +indices+ and calls this function on the resulting
|
|
55
|
+
# list.
|
|
56
|
+
#
|
|
57
|
+
# The +arity+ parameter controls how mismatches in length
|
|
58
|
+
# between the arguments and indices are handled:
|
|
59
|
+
# :min - cap at the minimum of args and indices (default)
|
|
60
|
+
# :max - use the maximum; nil-fill if args are short,
|
|
61
|
+
# pass-through if args are long
|
|
62
|
+
# :indices - always use indices.size; nil for out-of-bounds
|
|
63
|
+
# :arguments - always use args.size; excess positions pass
|
|
64
|
+
# through at their original index
|
|
65
|
+
#
|
|
66
|
+
def trans *indices, arity: :min
|
|
67
|
+
case arity
|
|
68
|
+
when :min
|
|
69
|
+
lambda do |*a|
|
|
70
|
+
n = [a.size, indices.size].min
|
|
71
|
+
list = (0...n).map {|i| a[indices[i]] }
|
|
72
|
+
self.call(*list)
|
|
73
|
+
end
|
|
74
|
+
when :indices
|
|
75
|
+
lambda do |*a|
|
|
76
|
+
list = (0...indices.size).map {|i| a[indices[i]] }
|
|
77
|
+
self.call(*list)
|
|
78
|
+
end
|
|
79
|
+
when :arguments
|
|
80
|
+
lambda do |*a|
|
|
81
|
+
list = (0...a.size).map do |i|
|
|
82
|
+
i < indices.size ? a[indices[i]] : a[i]
|
|
83
|
+
end
|
|
84
|
+
self.call(*list)
|
|
85
|
+
end
|
|
86
|
+
when :max
|
|
87
|
+
lambda do |*a|
|
|
88
|
+
n = [a.size, indices.size].max
|
|
89
|
+
list = (0...n).map do |i|
|
|
90
|
+
i < indices.size ? a[indices[i]] : a[i]
|
|
91
|
+
end
|
|
92
|
+
self.call(*list)
|
|
93
|
+
end
|
|
94
|
+
else
|
|
95
|
+
raise ArgumentError, "unknown arity mode: #{arity.inspect}"
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
#
|
|
100
|
+
# Generates a function that maps its arguments to each of
|
|
101
|
+
# +funcs+ in order.
|
|
102
|
+
#
|
|
103
|
+
def zipmap *funcs
|
|
104
|
+
lambda do |*args|
|
|
105
|
+
n = [args.size, funcs.size].min
|
|
106
|
+
mapped = (0...n).map do |i|
|
|
107
|
+
func = funcs[i]
|
|
108
|
+
arg = args[i]
|
|
109
|
+
|
|
110
|
+
if func.nil?
|
|
111
|
+
arg
|
|
112
|
+
elsif func.respond_to? :call
|
|
113
|
+
func.call arg
|
|
114
|
+
elsif func.is_a? Symbol
|
|
115
|
+
arg.__send__ func
|
|
116
|
+
else
|
|
117
|
+
raise TypeError, "expected callable, Symbol, or nil; got #{func.class}"
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
self.call(*mapped)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
class << self
|
|
125
|
+
|
|
126
|
+
#
|
|
127
|
+
# Generates a function that maps its arguments to the
|
|
128
|
+
# given +funcs+ list in order.
|
|
129
|
+
#
|
|
130
|
+
def juxt *funcs
|
|
131
|
+
lambda do |*args|
|
|
132
|
+
funcs.map {|f| f.to_proc.call(*args) }
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
#
|
|
137
|
+
# Generates an identity function that always returns its argument exactly.
|
|
138
|
+
#
|
|
139
|
+
def identity
|
|
140
|
+
lambda {|x| x }
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
#
|
|
144
|
+
# Generates a constant function that always returns +c+.
|
|
145
|
+
#
|
|
146
|
+
# Note that it always returns the same object, so mutations will stick
|
|
147
|
+
# from invocation to invocation.
|
|
148
|
+
#
|
|
149
|
+
def const c
|
|
150
|
+
lambda {|*| c }
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
class Enumerator
|
|
157
|
+
class << self
|
|
158
|
+
|
|
159
|
+
#
|
|
160
|
+
# Creates an Enumerator that can unfold a sequence from a given seed.
|
|
161
|
+
#
|
|
162
|
+
def unfold(seed, &blk)
|
|
163
|
+
raise ArgumentError, 'no block given' unless block_given?
|
|
164
|
+
Enumerator.new do |y|
|
|
165
|
+
loop do
|
|
166
|
+
result = blk.call(seed)
|
|
167
|
+
break if result.nil?
|
|
168
|
+
value, seed = result
|
|
169
|
+
y << value
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
=begin
|
|
178
|
+
Copyright (c) 2014-2026, Matthew Kerwin <matthew@kerwin.net.au>
|
|
179
|
+
|
|
180
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
181
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
182
|
+
copyright notice and this permission notice appear in all copies.
|
|
183
|
+
|
|
184
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
185
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
186
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
187
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
188
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
189
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
190
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
191
|
+
=end
|
data/lib/mug.rb
CHANGED
|
@@ -17,6 +17,7 @@ require_relative 'mug/enumerable/chain'
|
|
|
17
17
|
require_relative 'mug/enumerable/counts'
|
|
18
18
|
require_relative 'mug/enumerable/hash-like'
|
|
19
19
|
require_relative 'mug/fragile-method-chain'
|
|
20
|
+
require_relative 'mug/functional'
|
|
20
21
|
require_relative 'mug/hash/fetch-assign'
|
|
21
22
|
require_relative 'mug/hash/map'
|
|
22
23
|
require_relative 'mug/hash/merge'
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
require 'test/unit'
|
|
2
|
+
$VERBOSE = true
|
|
3
|
+
|
|
4
|
+
require_relative '../lib/mug/functional'
|
|
5
|
+
|
|
6
|
+
class Test_functional_compose < Test::Unit::TestCase
|
|
7
|
+
def test_compose__readme_example
|
|
8
|
+
prc = lambda {|x| x.inspect }
|
|
9
|
+
prc2 = prc.compose(:to_s, :length)
|
|
10
|
+
assert_equal( 3, prc2[123] )
|
|
11
|
+
assert_equal( 5, prc2['abc'] )
|
|
12
|
+
end
|
|
13
|
+
def test_compose__empty
|
|
14
|
+
prc = lambda {|x| x }
|
|
15
|
+
assert_same( prc, prc.compose )
|
|
16
|
+
end
|
|
17
|
+
def test_compose__single
|
|
18
|
+
prc = lambda {|x| x + 1 }
|
|
19
|
+
prc2 = prc.compose(:abs)
|
|
20
|
+
assert_equal( 4, prc2[-5] )
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class Test_functional_precompose < Test::Unit::TestCase
|
|
25
|
+
def test_precompose__readme_example
|
|
26
|
+
prc = lambda {|x| x.inspect }
|
|
27
|
+
prc2 = prc.precompose(:to_s, :length)
|
|
28
|
+
assert_equal( '3', prc2[123] )
|
|
29
|
+
assert_equal( '3', prc2['abc'] )
|
|
30
|
+
end
|
|
31
|
+
def test_precompose__empty
|
|
32
|
+
prc = lambda {|x| x }
|
|
33
|
+
assert_same( prc, prc.precompose )
|
|
34
|
+
end
|
|
35
|
+
def test_precompose__single
|
|
36
|
+
prc = lambda {|x| x * 2 }
|
|
37
|
+
prc2 = prc.precompose(:to_i)
|
|
38
|
+
assert_equal( 246, prc2['123'] )
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
class Test_functional_mapply < Test::Unit::TestCase
|
|
43
|
+
def test_mapply__basic
|
|
44
|
+
prc = lambda {|x| x.to_s }
|
|
45
|
+
assert_equal( ['1', '2', '3'], prc.mapply(1, 2, 3) )
|
|
46
|
+
end
|
|
47
|
+
def test_mapply__equivalent_to_map
|
|
48
|
+
prc = lambda {|x| x * 2 }
|
|
49
|
+
args = [1, 2, 3, 4]
|
|
50
|
+
assert_equal( args.map(&prc), prc.mapply(*args) )
|
|
51
|
+
end
|
|
52
|
+
def test_mapply__empty
|
|
53
|
+
prc = lambda {|x| x }
|
|
54
|
+
assert_equal( [], prc.mapply )
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
class Test_functional_memoize < Test::Unit::TestCase
|
|
59
|
+
def test_memoize__caches_result
|
|
60
|
+
call_count = 0
|
|
61
|
+
prc = lambda do |x|
|
|
62
|
+
call_count += 1
|
|
63
|
+
x * 2
|
|
64
|
+
end
|
|
65
|
+
memo = prc.memoize
|
|
66
|
+
assert_equal( 4, memo.call(2) )
|
|
67
|
+
assert_equal( 1, call_count )
|
|
68
|
+
assert_equal( 4, memo.call(2) )
|
|
69
|
+
assert_equal( 1, call_count )
|
|
70
|
+
end
|
|
71
|
+
def test_memoize__different_args
|
|
72
|
+
call_count = 0
|
|
73
|
+
prc = lambda do |x|
|
|
74
|
+
call_count += 1
|
|
75
|
+
x * 2
|
|
76
|
+
end
|
|
77
|
+
memo = prc.memoize
|
|
78
|
+
assert_equal( 4, memo.call(2) )
|
|
79
|
+
assert_equal( 6, memo.call(3) )
|
|
80
|
+
assert_equal( 2, call_count )
|
|
81
|
+
end
|
|
82
|
+
def test_memoise__alias
|
|
83
|
+
prc = lambda {|x| x }
|
|
84
|
+
assert_equal( prc.method(:memoize), prc.method(:memoise) )
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
class Test_functional_trans < Test::Unit::TestCase
|
|
89
|
+
def test_trans__reorder
|
|
90
|
+
prc = lambda {|a, b, c| [a, b, c] }
|
|
91
|
+
prc2 = prc.trans(2, 0, 1)
|
|
92
|
+
assert_equal( [:c, :a, :b], prc2.call(:a, :b, :c) )
|
|
93
|
+
end
|
|
94
|
+
def test_trans__duplicate_index
|
|
95
|
+
prc = lambda {|a, b| [a, b] }
|
|
96
|
+
prc2 = prc.trans(0, 0)
|
|
97
|
+
assert_equal( [:x, :x], prc2.call(:x, :y) )
|
|
98
|
+
end
|
|
99
|
+
def test_trans__capped_by_args
|
|
100
|
+
prc = lambda {|*a| a }
|
|
101
|
+
prc2 = prc.trans(2, 1, 0)
|
|
102
|
+
assert_equal( [nil, :b], prc2.call(:a, :b) )
|
|
103
|
+
end
|
|
104
|
+
def test_trans__capped_by_indices
|
|
105
|
+
prc = lambda {|*a| a }
|
|
106
|
+
prc2 = prc.trans(1, 0)
|
|
107
|
+
assert_equal( [:b, :a], prc2.call(:a, :b, :c) )
|
|
108
|
+
end
|
|
109
|
+
# arity: :min (default, same as above tests)
|
|
110
|
+
def test_trans__arity_min_explicit
|
|
111
|
+
prc = lambda {|*a| a }
|
|
112
|
+
prc2 = prc.trans(1, 0, arity: :min)
|
|
113
|
+
assert_equal( [:b, :a], prc2.call(:a, :b, :c) )
|
|
114
|
+
end
|
|
115
|
+
# arity: :indices
|
|
116
|
+
def test_trans__arity_indices__args_short
|
|
117
|
+
prc = lambda {|*a| a }
|
|
118
|
+
prc2 = prc.trans(2, 0, 1, arity: :indices)
|
|
119
|
+
assert_equal( [nil, :a, :b], prc2.call(:a, :b) )
|
|
120
|
+
end
|
|
121
|
+
def test_trans__arity_indices__args_long
|
|
122
|
+
prc = lambda {|*a| a }
|
|
123
|
+
prc2 = prc.trans(1, 0, arity: :indices)
|
|
124
|
+
assert_equal( [:b, :a], prc2.call(:a, :b, :c) )
|
|
125
|
+
end
|
|
126
|
+
# arity: :arguments
|
|
127
|
+
def test_trans__arity_arguments__args_short
|
|
128
|
+
prc = lambda {|*a| a }
|
|
129
|
+
prc2 = prc.trans(2, 0, 1, arity: :arguments)
|
|
130
|
+
assert_equal( [nil, :a], prc2.call(:a, :b) )
|
|
131
|
+
end
|
|
132
|
+
def test_trans__arity_arguments__args_long
|
|
133
|
+
prc = lambda {|*a| a }
|
|
134
|
+
prc2 = prc.trans(1, 0, arity: :arguments)
|
|
135
|
+
assert_equal( [:b, :a, :c], prc2.call(:a, :b, :c) )
|
|
136
|
+
end
|
|
137
|
+
# arity: :max
|
|
138
|
+
def test_trans__arity_max__args_short
|
|
139
|
+
prc = lambda {|*a| a }
|
|
140
|
+
prc2 = prc.trans(2, 0, 1, arity: :max)
|
|
141
|
+
assert_equal( [nil, :a, :b], prc2.call(:a, :b) )
|
|
142
|
+
end
|
|
143
|
+
def test_trans__arity_max__args_long
|
|
144
|
+
prc = lambda {|*a| a }
|
|
145
|
+
prc2 = prc.trans(1, 0, arity: :max)
|
|
146
|
+
assert_equal( [:b, :a, :c], prc2.call(:a, :b, :c) )
|
|
147
|
+
end
|
|
148
|
+
def test_trans__arity_max__equal_length
|
|
149
|
+
prc = lambda {|*a| a }
|
|
150
|
+
prc2 = prc.trans(2, 0, 1, arity: :max)
|
|
151
|
+
assert_equal( [:c, :a, :b], prc2.call(:a, :b, :c) )
|
|
152
|
+
end
|
|
153
|
+
# invalid arity
|
|
154
|
+
def test_trans__arity_invalid
|
|
155
|
+
prc = lambda {|*a| a }
|
|
156
|
+
assert_raise( ArgumentError ) { prc.trans(0, 1, arity: :bogus) }
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
class Test_functional_zipmap < Test::Unit::TestCase
|
|
161
|
+
def test_zipmap__readme_example
|
|
162
|
+
printer = lambda {|*x| x }
|
|
163
|
+
mapped = printer.zipmap(:upcase, :downcase, :to_sym)
|
|
164
|
+
assert_equal( ['HELLO', 'there', :Everyone], mapped.call('Hello', 'There', 'Everyone') )
|
|
165
|
+
end
|
|
166
|
+
def test_zipmap__nil_passthrough
|
|
167
|
+
prc = lambda {|a, b| [a, b] }
|
|
168
|
+
prc2 = prc.zipmap(nil, :to_s)
|
|
169
|
+
assert_equal( [42, '42'], prc2.call(42, 42) )
|
|
170
|
+
end
|
|
171
|
+
def test_zipmap__callable
|
|
172
|
+
doubler = lambda {|x| x * 2 }
|
|
173
|
+
prc = lambda {|a, b| [a, b] }
|
|
174
|
+
prc2 = prc.zipmap(doubler, nil)
|
|
175
|
+
assert_equal( [10, 5], prc2.call(5, 5) )
|
|
176
|
+
end
|
|
177
|
+
def test_zipmap__type_error
|
|
178
|
+
prc = lambda {|a| a }
|
|
179
|
+
prc2 = prc.zipmap(123)
|
|
180
|
+
assert_raise( TypeError ) { prc2.call('x') }
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
class Test_functional_juxt < Test::Unit::TestCase
|
|
185
|
+
def test_juxt__readme_example
|
|
186
|
+
func = Proc.juxt :upcase, :downcase, :length
|
|
187
|
+
assert_equal( ['ABC', 'abc', 3], func.call('AbC') )
|
|
188
|
+
end
|
|
189
|
+
def test_juxt__with_procs
|
|
190
|
+
doubler = lambda {|x| x * 2 }
|
|
191
|
+
negator = lambda {|x| -x }
|
|
192
|
+
func = Proc.juxt(doubler, negator)
|
|
193
|
+
assert_equal( [10, -5], func.call(5) )
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
class Test_functional_identity < Test::Unit::TestCase
|
|
198
|
+
def test_identity__returns_same_object
|
|
199
|
+
func = Proc.identity
|
|
200
|
+
obj = Object.new
|
|
201
|
+
assert_same( obj, func.call(obj) )
|
|
202
|
+
end
|
|
203
|
+
def test_identity__with_values
|
|
204
|
+
func = Proc.identity
|
|
205
|
+
assert_equal( 42, func.call(42) )
|
|
206
|
+
assert_equal( 'hello', func.call('hello') )
|
|
207
|
+
assert_nil( func.call(nil) )
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
class Test_functional_const < Test::Unit::TestCase
|
|
212
|
+
def test_const__returns_constant
|
|
213
|
+
obj = Object.new
|
|
214
|
+
func = Proc.const(obj)
|
|
215
|
+
assert_same( obj, func.call )
|
|
216
|
+
assert_same( obj, func.call(1, 2, 3) )
|
|
217
|
+
end
|
|
218
|
+
def test_const__same_object_mutations_stick
|
|
219
|
+
ary = [1, 2, 3]
|
|
220
|
+
func = Proc.const(ary)
|
|
221
|
+
func.call.push(4)
|
|
222
|
+
assert_equal( [1, 2, 3, 4], func.call )
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
class Test_functional_unfold < Test::Unit::TestCase
|
|
227
|
+
def test_unfold__counting
|
|
228
|
+
enum = Enumerator.unfold(1) {|n| [n, n + 1] }
|
|
229
|
+
assert_equal( [1, 2, 3, 4, 5], enum.take(5) )
|
|
230
|
+
end
|
|
231
|
+
def test_unfold__fibonacci
|
|
232
|
+
enum = Enumerator.unfold([0, 1]) {|(a, b)| [a, [b, a + b]] }
|
|
233
|
+
assert_equal( [0, 1, 1, 2, 3], enum.take(5) )
|
|
234
|
+
end
|
|
235
|
+
def test_unfold__termination
|
|
236
|
+
enum = Enumerator.unfold(1) {|n| n <= 3 ? [n, n + 1] : nil }
|
|
237
|
+
assert_equal( [1, 2, 3], enum.to_a )
|
|
238
|
+
end
|
|
239
|
+
def test_unfold__no_block
|
|
240
|
+
assert_raise( ArgumentError ) { Enumerator.unfold(1) }
|
|
241
|
+
end
|
|
242
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mug
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.0
|
|
4
|
+
version: 2.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Matthew Kerwin
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-05-01 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: |
|
|
14
14
|
== MUG: Matty's Ultimate Gem
|
|
@@ -45,6 +45,7 @@ files:
|
|
|
45
45
|
- lib/mug/enumerable/counts.rb
|
|
46
46
|
- lib/mug/enumerable/hash-like.rb
|
|
47
47
|
- lib/mug/fragile-method-chain.rb
|
|
48
|
+
- lib/mug/functional.rb
|
|
48
49
|
- lib/mug/hash.rb
|
|
49
50
|
- lib/mug/hash/fetch-assign.rb
|
|
50
51
|
- lib/mug/hash/map.rb
|
|
@@ -92,6 +93,7 @@ files:
|
|
|
92
93
|
- test/test-enumerable-chain.rb
|
|
93
94
|
- test/test-enumerable-hash-like.rb
|
|
94
95
|
- test/test-fragile-method-chain.rb
|
|
96
|
+
- test/test-functional.rb
|
|
95
97
|
- test/test-hashfetchassign.rb
|
|
96
98
|
- test/test-hashmap.rb
|
|
97
99
|
- test/test-hashmerge.rb
|
|
@@ -157,6 +159,7 @@ test_files:
|
|
|
157
159
|
- test/test-enumerable-chain.rb
|
|
158
160
|
- test/test-enumerable-hash-like.rb
|
|
159
161
|
- test/test-fragile-method-chain.rb
|
|
162
|
+
- test/test-functional.rb
|
|
160
163
|
- test/test-hashfetchassign.rb
|
|
161
164
|
- test/test-hashmap.rb
|
|
162
165
|
- test/test-hashmerge.rb
|