y_support 1.0.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 +7 -0
- data/.gitignore +20 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +29 -0
- data/Rakefile +2 -0
- data/lib/y_support/all.rb +40 -0
- data/lib/y_support/core_ext/array/misc.rb +45 -0
- data/lib/y_support/core_ext/array.rb +1 -0
- data/lib/y_support/core_ext/enumerable/misc.rb +32 -0
- data/lib/y_support/core_ext/enumerable.rb +1 -0
- data/lib/y_support/core_ext/hash/misc.rb +90 -0
- data/lib/y_support/core_ext/hash.rb +1 -0
- data/lib/y_support/core_ext/module/misc.rb +43 -0
- data/lib/y_support/core_ext/module.rb +2 -0
- data/lib/y_support/core_ext/numeric/misc.rb +13 -0
- data/lib/y_support/core_ext/numeric.rb +1 -0
- data/lib/y_support/core_ext/object/misc.rb +31 -0
- data/lib/y_support/core_ext/object.rb +1 -0
- data/lib/y_support/core_ext/string/misc.rb +80 -0
- data/lib/y_support/core_ext/string.rb +1 -0
- data/lib/y_support/core_ext/symbol/misc.rb +19 -0
- data/lib/y_support/core_ext/symbol.rb +1 -0
- data/lib/y_support/core_ext.rb +5 -0
- data/lib/y_support/inert_recorder.rb +51 -0
- data/lib/y_support/local_object.rb +39 -0
- data/lib/y_support/misc.rb +28 -0
- data/lib/y_support/name_magic.rb +373 -0
- data/lib/y_support/null_object.rb +96 -0
- data/lib/y_support/respond_to.rb +32 -0
- data/lib/y_support/stdlib_ext/matrix/misc.rb +134 -0
- data/lib/y_support/stdlib_ext/matrix.rb +2 -0
- data/lib/y_support/stdlib_ext.rb +3 -0
- data/lib/y_support/typing/array/typing.rb +17 -0
- data/lib/y_support/typing/array.rb +1 -0
- data/lib/y_support/typing/enumerable/typing.rb +75 -0
- data/lib/y_support/typing/enumerable.rb +1 -0
- data/lib/y_support/typing/hash/typing.rb +76 -0
- data/lib/y_support/typing/hash.rb +1 -0
- data/lib/y_support/typing/module/typing.rb +42 -0
- data/lib/y_support/typing/module.rb +1 -0
- data/lib/y_support/typing/object/typing.rb +178 -0
- data/lib/y_support/typing/object.rb +1 -0
- data/lib/y_support/typing.rb +43 -0
- data/lib/y_support/unicode.rb +76 -0
- data/lib/y_support/version.rb +3 -0
- data/lib/y_support.rb +33 -0
- data/test/inert_recorder_test.rb +34 -0
- data/test/local_object_test.rb +37 -0
- data/test/misc_test/test_module/fixture_class.rb +8 -0
- data/test/misc_test.rb +289 -0
- data/test/name_magic_test.rb +57 -0
- data/test/null_object_test.rb +50 -0
- data/test/respond_to_test.rb +46 -0
- data/test/typing_test.rb +213 -0
- data/test/unicode_test.rb +39 -0
- data/y_support.gemspec +22 -0
- metadata +137 -0
@@ -0,0 +1,373 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
require 'y_support'
|
3
|
+
|
4
|
+
# A mixin imitating Ruby constant magic, plus automation of :name alias :ɴ
|
5
|
+
# named argument. This allows to write:
|
6
|
+
#
|
7
|
+
# <tt>SomeName = SomeClass.new</tt>
|
8
|
+
#
|
9
|
+
# and the resulting object will know its #name:
|
10
|
+
#
|
11
|
+
# <tt>SomeName.name = "SomeName"</tt>
|
12
|
+
#
|
13
|
+
# This is done by searching the whole Ruby namespace for constants to which
|
14
|
+
# the object is assigned. The search is performed by calling #const_magic.
|
15
|
+
# This is only done until the name is found - once the object is named, its
|
16
|
+
# subsequent assignment to constants is without effect.
|
17
|
+
#
|
18
|
+
# Alternatively, a named object can be created by using :name alias :ɴ
|
19
|
+
# named argument:
|
20
|
+
#
|
21
|
+
# SomeName.new arg1, arg2, ..., name: "SomeName", named_arg1: val1, ...
|
22
|
+
#
|
23
|
+
# Hook is provided for when the name magic is performed.
|
24
|
+
#
|
25
|
+
module NameMagic
|
26
|
+
DEBUG = false
|
27
|
+
PROBLEM_MODULES = [ 'Gem', 'Rack', 'ActiveSupport' ]
|
28
|
+
|
29
|
+
def self.included target
|
30
|
+
case target
|
31
|
+
when Class then
|
32
|
+
class << target
|
33
|
+
# Make space for the decorator #new:
|
34
|
+
alias :original_method_new :new
|
35
|
+
end
|
36
|
+
# Attach the decorators etc.
|
37
|
+
target.extend ::NameMagic::ClassMethods
|
38
|
+
target.extend ::NameMagic::NamespaceMethods
|
39
|
+
# Attach namespace methods to also to the namespace, if given.
|
40
|
+
begin
|
41
|
+
unless target == target.namespace
|
42
|
+
target.namespace.extend ::NameMagic::NamespaceMethods
|
43
|
+
end
|
44
|
+
rescue NoMethodError
|
45
|
+
end
|
46
|
+
else # it is a Module; we'll infect it with this #included method
|
47
|
+
included_of_the_target = target.method( :included )
|
48
|
+
included_of_self = self.method( :included )
|
49
|
+
pre_included_of_the_target = begin
|
50
|
+
target.method( :pre_included )
|
51
|
+
rescue NameError
|
52
|
+
end
|
53
|
+
if pre_included_of_the_target then
|
54
|
+
target.define_singleton_method :included do |ç|
|
55
|
+
pre_included_of_the_target.( ç )
|
56
|
+
included_of_self.call( ç )
|
57
|
+
included_of_the_target.call( ç )
|
58
|
+
end
|
59
|
+
else
|
60
|
+
target.define_singleton_method :included do |ç|
|
61
|
+
included_of_self.( ç )
|
62
|
+
included_of_the_target.( ç )
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end # self.included
|
67
|
+
|
68
|
+
# Retrieves an instance name (demodulized).
|
69
|
+
#
|
70
|
+
def name
|
71
|
+
self.class.const_magic
|
72
|
+
ɴ = self.class.__instances__[ self ]
|
73
|
+
if ɴ then
|
74
|
+
name_get_closure = self.class.instance_variable_get :@name_get_closure
|
75
|
+
return name_get_closure ? name_get_closure.( ɴ ) : ɴ
|
76
|
+
else
|
77
|
+
return nil
|
78
|
+
end
|
79
|
+
end
|
80
|
+
alias ɴ name
|
81
|
+
|
82
|
+
# Names an instance, cautiously (ie. no overwriting of existing names).
|
83
|
+
#
|
84
|
+
def name=( ɴ )
|
85
|
+
# get previous name of this instance, if any
|
86
|
+
old_ɴ = self.class.__instances__[ self ]
|
87
|
+
# honor the hook
|
88
|
+
name_set_closure = self.class.instance_variable_get :@name_set_closure
|
89
|
+
ɴ = name_set_closure.call( ɴ, self, old_ɴ ) if name_set_closure
|
90
|
+
ɴ = self.class.send( :validate_capitalization, ɴ ).to_sym
|
91
|
+
return if old_ɴ == ɴ # already named as required; nothing to do
|
92
|
+
# otherwise, be cautious about name collision
|
93
|
+
raise NameError, "Name '#{ɴ}' already exists in " +
|
94
|
+
"#{self.class} namespace!" if self.class.__instances__.rassoc( ɴ )
|
95
|
+
# since everything's ok...
|
96
|
+
self.class.namespace.const_set ɴ, self # write a constant
|
97
|
+
self.class.__instances__[ self ] = ɴ # write __instances__
|
98
|
+
self.class.__forget__ old_ɴ # forget the old name of self
|
99
|
+
end
|
100
|
+
|
101
|
+
# Names an instance, aggresively (overwrites existing names).
|
102
|
+
#
|
103
|
+
def name!( ɴ )
|
104
|
+
# get previous name of this instance, if any
|
105
|
+
old_ɴ = self.class.__instances__[ self ]
|
106
|
+
# honor the hook
|
107
|
+
name_set_closure = self.class.instance_variable_get :@name_set_closure
|
108
|
+
ɴ = name_set_closure.( ɴ, self, old_ɴ ) if name_set_closure
|
109
|
+
ɴ = self.class.send( :validate_capitalization, ɴ ).to_sym
|
110
|
+
return false if old_ɴ == ɴ # already named as required; nothing to do
|
111
|
+
# otherwise, rudely remove the collider, if any
|
112
|
+
pair = self.class.__instances__.rassoc( ɴ )
|
113
|
+
self.class.__forget__( pair[0] ) if pair
|
114
|
+
# and add add self to the namespace
|
115
|
+
self.class.namespace.const_set ɴ, self # write a constant
|
116
|
+
self.class.__instances__[ self ] = ɴ # write to __instances__
|
117
|
+
self.class.__forget__ old_ɴ # forget the old name of self
|
118
|
+
return true
|
119
|
+
end
|
120
|
+
|
121
|
+
module NamespaceMethods
|
122
|
+
# Presents class-owned @instances hash of { instance => name } pairs.
|
123
|
+
#
|
124
|
+
def instances
|
125
|
+
const_magic
|
126
|
+
__instances__.keys
|
127
|
+
end
|
128
|
+
|
129
|
+
# Presents an array of all the instance names (disregarding anonymous
|
130
|
+
# instances).
|
131
|
+
#
|
132
|
+
def instance_names
|
133
|
+
instances.map( &:name ).compact
|
134
|
+
end
|
135
|
+
|
136
|
+
# Presents class-owned @instances without const_magic.
|
137
|
+
#
|
138
|
+
def __instances__
|
139
|
+
namespace.instance_variable_get( :@instances ) or
|
140
|
+
namespace.instance_variable_set( :@instances, {} )
|
141
|
+
end
|
142
|
+
|
143
|
+
# Presents class-owned @avid_instances (no const_magic).
|
144
|
+
#
|
145
|
+
def __avid_instances__
|
146
|
+
namespace.instance_variable_get( :@avid_instances ) or
|
147
|
+
namespace.instance_variable_set( :@avid_instances, [] )
|
148
|
+
end
|
149
|
+
|
150
|
+
# Presents class-owned namespace. Normally, this is the class itself,
|
151
|
+
# but can be overriden so as to define constants holding the instances
|
152
|
+
# in some other module.
|
153
|
+
#
|
154
|
+
def namespace
|
155
|
+
self
|
156
|
+
end
|
157
|
+
|
158
|
+
# Returns the instance of the class using NameMagic, specified by the
|
159
|
+
# argument. NameError is raised, if the argument does not represent a valid
|
160
|
+
# instance name, or if the argument itself is not a valid instance (in
|
161
|
+
# which case it is returned unchanged).
|
162
|
+
#
|
163
|
+
def instance arg
|
164
|
+
const_magic
|
165
|
+
# if the argument is an actual instance, just return it
|
166
|
+
return arg if __instances__.keys.include? arg
|
167
|
+
# otherwise, treat it as name
|
168
|
+
r = begin
|
169
|
+
__instances__.rassoc( arg ) || __instances__.rassoc( arg.to_sym )
|
170
|
+
rescue NoMethodError
|
171
|
+
end or
|
172
|
+
raise NameError, "No instance #{arg} in #{namespace}."
|
173
|
+
return r[0]
|
174
|
+
end
|
175
|
+
|
176
|
+
|
177
|
+
# The method will search all the modules in the the object space for
|
178
|
+
# receiver class objects assigned to constants, and name these instances
|
179
|
+
# accordingly. Number of the remaining nameless instances is returned.
|
180
|
+
#
|
181
|
+
def const_magic
|
182
|
+
return 0 if nameless_instances.size == 0
|
183
|
+
serve_all_modules
|
184
|
+
return nameless_instances.size
|
185
|
+
end # def const_magic
|
186
|
+
|
187
|
+
# Returns those instances, which are nameless (@instances hash value is nil).
|
188
|
+
#
|
189
|
+
def nameless_instances
|
190
|
+
__instances__.select { |key, val| val.nil? }.keys
|
191
|
+
end
|
192
|
+
|
193
|
+
# Clears class-owned references to a specified instance.
|
194
|
+
#
|
195
|
+
def forget( which_instance )
|
196
|
+
inst = begin
|
197
|
+
instance( which_instance )
|
198
|
+
rescue ArgumentError
|
199
|
+
return nil # nothing to forget
|
200
|
+
end
|
201
|
+
ɴ = inst.nil? ? nil : inst.name
|
202
|
+
namespace.send :remove_const, ɴ if ɴ # clear constant assignment
|
203
|
+
__instances__.delete( inst ) # remove @instances entry
|
204
|
+
__avid_instances__.delete( inst ) # remove if any
|
205
|
+
return inst # return forgotten instance
|
206
|
+
end
|
207
|
+
|
208
|
+
# Clears class-owned references to a specified instance without performing
|
209
|
+
# #const_magic first. The argument must be an instance of the target class.
|
210
|
+
#
|
211
|
+
def __forget__( instance )
|
212
|
+
name = __instances__.delete instance # remove @instances entry
|
213
|
+
__avid_instances__.delete( instance ) # remove if any
|
214
|
+
namespace.send :remove_const, name if name
|
215
|
+
return instance
|
216
|
+
end
|
217
|
+
|
218
|
+
# Clears class-owned references anonymous instances.
|
219
|
+
#
|
220
|
+
def forget_anonymous_instances
|
221
|
+
nameless_instances.each { |inst, ɴ|
|
222
|
+
__instances__.delete inst
|
223
|
+
__avid_instances__.delete inst
|
224
|
+
}
|
225
|
+
end
|
226
|
+
alias :forget_nameless_instances :forget_anonymous_instances
|
227
|
+
|
228
|
+
# Clears class-owned references to all the instances.
|
229
|
+
#
|
230
|
+
def forget_all_instances
|
231
|
+
__instances__.clear # clears @instances
|
232
|
+
constants( false ).each { |ß| # clear constants in the namespace
|
233
|
+
namespace.send :remove_const, ß if const_get( ß ).is_a? self
|
234
|
+
}
|
235
|
+
end
|
236
|
+
|
237
|
+
# Registers a hook to execute whenever name magic creates a new instance
|
238
|
+
# of the class including NameMagic. The block should take one argument
|
239
|
+
# (the new instance that was created) and is called in #new method right
|
240
|
+
# after instantiation, but before naming.
|
241
|
+
#
|
242
|
+
def new_instance_closure █ @new_instance_closure = block end
|
243
|
+
|
244
|
+
# Registers a hook to execute whenever name setting is performed on an
|
245
|
+
# instance. The block should take three arguments (instance, name, old_name).
|
246
|
+
# The output value of the block is the name to be actually used – the hook
|
247
|
+
# thus allows to define transformations on the name when naming. It is the
|
248
|
+
# responsibility of the block to output a suitable symbol (capitalized,
|
249
|
+
# usable as a constant name etc.)
|
250
|
+
#
|
251
|
+
def name_set_closure █ @name_set_closure = block end
|
252
|
+
|
253
|
+
# Registers a hook to execute whenever the instance is asked about its
|
254
|
+
# name. The name object contained in __instances__[self] is subjected
|
255
|
+
# to the name_get_closure before being returned as instance name.
|
256
|
+
#
|
257
|
+
def name_get_closure █ @name_get_closure = block end
|
258
|
+
|
259
|
+
private
|
260
|
+
|
261
|
+
# Checks all the constants in some module's namespace, recursively.
|
262
|
+
#
|
263
|
+
def serve_all_modules
|
264
|
+
incriminated_ids = ( nameless_instances + __avid_instances__ )
|
265
|
+
.map( &:object_id ).uniq
|
266
|
+
ObjectSpace.each_object Module do |ɱ|
|
267
|
+
# hack against bugs when getting constants from URI
|
268
|
+
next if ::NameMagic::PROBLEM_MODULES.any? { |problem_ς|
|
269
|
+
begin
|
270
|
+
ɱ.name.to_s.start_with? problem_ς
|
271
|
+
rescue NoMethodError, ArgumentError
|
272
|
+
end
|
273
|
+
}
|
274
|
+
puts ɱ if ::NameMagic::DEBUG
|
275
|
+
# check all the module constants:
|
276
|
+
ɱ.constants( false ).each do |const_ß|
|
277
|
+
begin # insurance against buggy dynamic loading of constants
|
278
|
+
◉ = ɱ.const_get( const_ß )
|
279
|
+
rescue
|
280
|
+
next
|
281
|
+
end
|
282
|
+
# is it a wanted object?
|
283
|
+
if incriminated_ids.include? ◉.object_id then
|
284
|
+
if __avid_instances__.map( &:object_id ).include? ◉.object_id then
|
285
|
+
# name avidly
|
286
|
+
__avid_instances__.delete_if { |instance| # make not avid first
|
287
|
+
instance.object_id == ◉.object_id
|
288
|
+
}
|
289
|
+
◉.name! const_ß # and then name it rudely
|
290
|
+
else # name this anonymous instance cautiously
|
291
|
+
# honor name_set_closure
|
292
|
+
ɴ = if @name_set_closure then
|
293
|
+
@name_set_closure.( const_ß, ◉, nil )
|
294
|
+
else const_ß end
|
295
|
+
ɴ = validate_capitalization( ɴ ).to_sym
|
296
|
+
conflicter = begin # be cautious
|
297
|
+
namespace.const_get( ɴ )
|
298
|
+
rescue NameError
|
299
|
+
end
|
300
|
+
if conflicter then
|
301
|
+
raise NameError, "Another #{self} named '#{ɴ}' already " +
|
302
|
+
"exists!" unless conflicter == ◉
|
303
|
+
else
|
304
|
+
# add the instance to the namespace
|
305
|
+
__instances__[ ◉ ] = ɴ
|
306
|
+
namespace.const_set ɴ, ◉
|
307
|
+
end
|
308
|
+
end
|
309
|
+
# and stop working in case there are no more unnamed instances
|
310
|
+
incriminated_ids.delete ◉.object_id
|
311
|
+
break if incriminated_ids.empty?
|
312
|
+
end
|
313
|
+
end # each
|
314
|
+
end # each_object Module
|
315
|
+
end # def serve_all_modules
|
316
|
+
|
317
|
+
# Checks whether a name starts with a capital letter.
|
318
|
+
#
|
319
|
+
def validate_capitalization( name )
|
320
|
+
ɴ = name.to_s
|
321
|
+
# check whether the name starts with 'A'..'Z'
|
322
|
+
raise NameError, "#{self.class} name must start with a capital " +
|
323
|
+
" letter 'A'..'Z' ('#{ɴ}' was given)!" unless ( ?A..?Z ) === ɴ[0]
|
324
|
+
return ɴ
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
module ClassMethods
|
329
|
+
# In addition to its ability to assign name to the target instance when
|
330
|
+
# the instance is assigned to a constant (aka. constant magic), NameMagic
|
331
|
+
# redefines #new class method to consume named parameter :name, alias :ɴ,
|
332
|
+
# thus providing another option for naming of the target instance.
|
333
|
+
#
|
334
|
+
def new *args, &block
|
335
|
+
oo = args[-1].is_a?( Hash ) ? args.pop : {} # extract hash
|
336
|
+
# consume :name named argument if it was supplied
|
337
|
+
ɴß = if oo[:name] then oo.delete :name
|
338
|
+
elsif oo[:ɴ] then oo.delete :ɴ
|
339
|
+
else nil end
|
340
|
+
# Expecting true/false, if :name_avid option is given
|
341
|
+
avid = oo[:name_avid] ? oo.delete( :name_avid ) : false
|
342
|
+
# Avoid name collisions unless avid
|
343
|
+
raise NameError, "#{self} instance #{ɴß} already exists!" if
|
344
|
+
__instances__.keys.include? ɴß unless avid
|
345
|
+
# instantiate
|
346
|
+
args << oo unless oo.empty? # fuse hash
|
347
|
+
new_inst = original_method_new *args, &block
|
348
|
+
# treat the instance as unnamed at first
|
349
|
+
__instances__.merge! new_inst => nil
|
350
|
+
# honor the hook
|
351
|
+
@new_instance_closure.call( new_inst ) if @new_instance_closure
|
352
|
+
# and then name it if name was supplied, or make it avid
|
353
|
+
# (avid instances will steal names from their competitors)
|
354
|
+
if ɴß then
|
355
|
+
if avid then new_inst.name! ɴß else new_inst.name = ɴß end
|
356
|
+
else
|
357
|
+
__avid_instances__ << new_inst
|
358
|
+
end
|
359
|
+
# return the new instance
|
360
|
+
return new_inst
|
361
|
+
end
|
362
|
+
|
363
|
+
# Compared to #new method, #new! uses avid mode: without
|
364
|
+
# concerns about overwriting existing named instances.
|
365
|
+
#
|
366
|
+
def new! *args, &block
|
367
|
+
# extract options
|
368
|
+
if args[-1].is_a? Hash then oo = args.pop else oo = {} end
|
369
|
+
# and call #new with added name_avid: true
|
370
|
+
new *args, oo.merge!( name_avid: true )
|
371
|
+
end
|
372
|
+
end # module ClassMethods
|
373
|
+
end # module NameMagic
|
@@ -0,0 +1,96 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
|
3
|
+
require 'y_support'
|
4
|
+
|
5
|
+
# Null object pattern implementation in YSupport. apart from the expected null
|
6
|
+
# object behavior (such as returning self in response to almost all messages),
|
7
|
+
# this null object instances can carry a signature specified by the user upon
|
8
|
+
# creation, which can serve to hint the origin of the null object. (This
|
9
|
+
# signature is opional, default is <tt>nil</tt>.)
|
10
|
+
#
|
11
|
+
class NullObject
|
12
|
+
attr_reader :null_object_signature
|
13
|
+
|
14
|
+
# Signature can be given as an optional argument upon initialization.
|
15
|
+
#
|
16
|
+
def initialize null_object_signature=nil
|
17
|
+
@null_object_signature = null_object_signature
|
18
|
+
end
|
19
|
+
|
20
|
+
# Inquirer whether an object is a NullObject. Again, optional signature
|
21
|
+
# argument can be given to distinguish between different null objects.
|
22
|
+
#
|
23
|
+
def null_object? signature=nil
|
24
|
+
null_object_signature == signature
|
25
|
+
end
|
26
|
+
alias null? null_object?
|
27
|
+
|
28
|
+
# Empty array.
|
29
|
+
#
|
30
|
+
def to_a; [] end
|
31
|
+
|
32
|
+
# Description string.
|
33
|
+
#
|
34
|
+
def to_s
|
35
|
+
sgn = null_object_signature
|
36
|
+
sgn.nil? ? "#<NullObject>" : "#<NullObject #{sgn}>"
|
37
|
+
end
|
38
|
+
|
39
|
+
# Inspection string.
|
40
|
+
#
|
41
|
+
def inspect; to_s end
|
42
|
+
|
43
|
+
# Float zero.
|
44
|
+
#
|
45
|
+
def to_f; 0.0 end
|
46
|
+
|
47
|
+
# Integer zero.
|
48
|
+
#
|
49
|
+
def to_i; 0 end
|
50
|
+
|
51
|
+
# Always false.
|
52
|
+
#
|
53
|
+
def present?; false end
|
54
|
+
|
55
|
+
# Always true.
|
56
|
+
#
|
57
|
+
def empty?; true end
|
58
|
+
|
59
|
+
# Always true.
|
60
|
+
#
|
61
|
+
def blank?; true end
|
62
|
+
|
63
|
+
# True if and only if the other object is a NullObject with same signature.
|
64
|
+
#
|
65
|
+
def == other
|
66
|
+
other.is_a?( self.class ) &&
|
67
|
+
other.null_object_signature == null_object_signature
|
68
|
+
end
|
69
|
+
|
70
|
+
def method_missing ß, *args, &block # :nodoc:
|
71
|
+
self
|
72
|
+
end
|
73
|
+
|
74
|
+
def respond_to? ß, *args, &block # :nodoc:
|
75
|
+
true
|
76
|
+
end
|
77
|
+
end # class nullobject
|
78
|
+
|
79
|
+
|
80
|
+
class Object
|
81
|
+
# Always false for ordinary objects, overriden in NullObject instances.
|
82
|
+
#
|
83
|
+
def null_object? signature=nil; false end
|
84
|
+
alias :null? :null_object?
|
85
|
+
|
86
|
+
# Converts #nil?-positive objects to a NullObject. Second optional argument
|
87
|
+
# specifies the signature of the null object to be created.
|
88
|
+
#
|
89
|
+
def Maybe object, null_object_signature=nil
|
90
|
+
object.nil? ? NullObject.new( null_object_signature ) : object
|
91
|
+
end
|
92
|
+
|
93
|
+
# NullObject constructor.
|
94
|
+
#
|
95
|
+
def Null( signature=nil ); NullObject.new signature end
|
96
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
|
3
|
+
require 'y_support'
|
4
|
+
|
5
|
+
# RespondTo class for easy use of respond_to? in case statements.
|
6
|
+
#
|
7
|
+
class RespondTo
|
8
|
+
Matchers = {}
|
9
|
+
attr_reader :method
|
10
|
+
def self.create method; Matchers[ method ] ||= new method end
|
11
|
+
def initialize method; @method = method end
|
12
|
+
def === obj; obj.respond_to? method end
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
class Object
|
17
|
+
# RespondTo constructor.
|
18
|
+
#
|
19
|
+
def RespondTo method; RespondTo.create method end
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
class Symbol
|
24
|
+
# Creates a RespondTo object from the receiver symbol. Intended use for this
|
25
|
+
# is nin case statements: RespondTo has customized #=== method, that calls
|
26
|
+
# #respond_to? to determine the return value.
|
27
|
+
#
|
28
|
+
# For example, <tt>when ~:each</tt> in a case statement is valid only if the
|
29
|
+
# tested object respond_to?( :each ) returns true.
|
30
|
+
#
|
31
|
+
def ~@; RespondTo self end
|
32
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
|
3
|
+
require 'matrix'
|
4
|
+
|
5
|
+
class Matrix
|
6
|
+
# Pretty inspect
|
7
|
+
def pretty_inspect
|
8
|
+
return inspect if row_size == 0 or column_size == 0
|
9
|
+
aa = send(:rows).each.with_object [] do |row, memo|
|
10
|
+
memo << row.map{ |o|
|
11
|
+
os = o.to_s
|
12
|
+
case o
|
13
|
+
when Numeric then os[0] == '-' ? os : ' ' + os
|
14
|
+
else o.to_s end
|
15
|
+
}
|
16
|
+
end
|
17
|
+
width = aa.map{ |row| row.map( &:size ).max }.max + 1
|
18
|
+
aa.each_with_object "" do |row, memo|
|
19
|
+
row.each{ |e| memo << e << ' ' * ( width - e.size ) }
|
20
|
+
memo << "\n"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Pretty print
|
25
|
+
def pretty_print
|
26
|
+
print pretty_inspect
|
27
|
+
return nil
|
28
|
+
end
|
29
|
+
alias :pp :pretty_print
|
30
|
+
|
31
|
+
# Given two arrays, creates correspondence matrix, with no. of cols
|
32
|
+
# equal to the 1st array, and no. of rows to the 2nd. This matrix can
|
33
|
+
# be used eg. for conversion between column vectors corresponding to
|
34
|
+
# the 1st and 2nd array:
|
35
|
+
#
|
36
|
+
# Matrix.correspondence_matrix( array1, array2 ) * col_vector_1
|
37
|
+
# #=> col_vector_2
|
38
|
+
#
|
39
|
+
def self.correspondence_matrix( array1, array2 )
|
40
|
+
return Matrix.empty 0, array1.size if array2.empty?
|
41
|
+
return Matrix.empty array2.size, 0 if array1.empty?
|
42
|
+
self[ *array2.map { |e2| array1.map { |e1| e1 == e2 ? 1 : 0 } } ] # FIXME: Ordinary zero
|
43
|
+
end
|
44
|
+
|
45
|
+
# Converts a column into array. If argument is given, it chooses
|
46
|
+
# column number, otherwise column 0 is assumed.
|
47
|
+
#
|
48
|
+
def column_to_a n=0; ( col = column( n ) ) ? col.to_a : nil end
|
49
|
+
|
50
|
+
# Converts a row into array. If argument is given, it chooses row
|
51
|
+
# number, otherwise row 0 is assumed.
|
52
|
+
#
|
53
|
+
def row_to_a n=0; ( r = row( n ) ) ? r.to_a : nil end
|
54
|
+
|
55
|
+
# Shorter aliases for #row_vector, #column_vector
|
56
|
+
#
|
57
|
+
def self.cv *aa, &b; column_vector *aa, &b end
|
58
|
+
def self.rv *aa, &b; row_vector *aa, &b end
|
59
|
+
|
60
|
+
#join_bottom method
|
61
|
+
#
|
62
|
+
def join_bottom other;
|
63
|
+
raise ArgumentError, "Column size mismatch" unless
|
64
|
+
column_size == other.column_size
|
65
|
+
return other.map { |e| e } if row_size == 0
|
66
|
+
return Matrix.empty row_size + other.row_size, 0 if column_size == 0
|
67
|
+
self.class[ *( row_vectors + other.row_vectors ) ]
|
68
|
+
end
|
69
|
+
|
70
|
+
#join_right methods
|
71
|
+
#
|
72
|
+
def join_right other;
|
73
|
+
raise ArgumentError, "Row size mismatch" unless row_size == other.row_size
|
74
|
+
( t.join_bottom( other.t ) ).t
|
75
|
+
end
|
76
|
+
|
77
|
+
# aliasing #row_size, #column_size
|
78
|
+
alias :number_of_rows :row_size
|
79
|
+
alias :number_of_columns :column_size
|
80
|
+
alias :height :number_of_rows
|
81
|
+
alias :width :number_of_columns
|
82
|
+
|
83
|
+
#
|
84
|
+
# Creates a empty matrix of +row_size+ x +column_size+.
|
85
|
+
# At least one of +row_size+ or +column_size+ must be 0.
|
86
|
+
#
|
87
|
+
# m = Matrix.empty(2, 0)
|
88
|
+
# m == Matrix[ [], [] ]
|
89
|
+
# => true
|
90
|
+
# n = Matrix.empty(0, 3)
|
91
|
+
# n == Matrix.columns([ [], [], [] ])
|
92
|
+
# => true
|
93
|
+
# m * n
|
94
|
+
# => Matrix[[0, 0, 0], [0, 0, 0]]
|
95
|
+
#
|
96
|
+
def Matrix.empty(row_size = 0, column_size = 0)
|
97
|
+
Matrix.Raise ArgumentError, "One size must be 0" if column_size != 0 && row_size != 0
|
98
|
+
Matrix.Raise ArgumentError, "Negative size" if column_size < 0 || row_size < 0
|
99
|
+
|
100
|
+
new([[]]*row_size, column_size)
|
101
|
+
end
|
102
|
+
|
103
|
+
#
|
104
|
+
# Creates a matrix of size +row_size+ x +column_size+.
|
105
|
+
# It fills the values by calling the given block,
|
106
|
+
# passing the current row and column.
|
107
|
+
# Returns an enumerator if no block is given.
|
108
|
+
#
|
109
|
+
# m = Matrix.build(2, 4) {|row, col| col - row }
|
110
|
+
# => Matrix[[0, 1, 2, 3], [-1, 0, 1, 2]]
|
111
|
+
# m = Matrix.build(3) { rand }
|
112
|
+
# => a 3x3 matrix with random elements
|
113
|
+
#
|
114
|
+
def Matrix.build(row_size, column_size = row_size)
|
115
|
+
row_size = CoercionHelper.coerce_to_int(row_size)
|
116
|
+
column_size = CoercionHelper.coerce_to_int(column_size)
|
117
|
+
raise ArgumentError if row_size < 0 || column_size < 0
|
118
|
+
return to_enum :build, row_size, column_size unless block_given?
|
119
|
+
rows = Array.new(row_size) do |i|
|
120
|
+
Array.new(column_size) do |j|
|
121
|
+
yield i, j
|
122
|
+
end
|
123
|
+
end
|
124
|
+
new rows, column_size
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
class Vector
|
129
|
+
# .zero class method returns a vector filled with zeros
|
130
|
+
#
|
131
|
+
def zero( vector_size )
|
132
|
+
self[*([0] * vector_size)] # FIXME: Ordinary zero
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
class Array
|
3
|
+
# === Duck typing support (aka. runtime assertions)
|
4
|
+
|
5
|
+
# This method takes a block and fails with TypeError, if the receiver array
|
6
|
+
# fails to include the specified element. An optional argument customizes the
|
7
|
+
# error message (element description).
|
8
|
+
#
|
9
|
+
def aT_includes element, what_is_element=nil
|
10
|
+
e = what_is_element ? what_is_element.to_s.capitalize :
|
11
|
+
"Element (#{element.class} instance)"
|
12
|
+
m = "#{e} is absent from the array."
|
13
|
+
raise TErr, m unless include? element
|
14
|
+
return self
|
15
|
+
end
|
16
|
+
alias :aT_include :aT_includes
|
17
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'y_support/core_ext/array/typing'
|