range_extd 1.1.1 → 2.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.
data/README.ja.rdoc CHANGED
@@ -1,135 +1,470 @@
1
1
 
2
2
  = RangeExtd - Extended Range class with exclude_begin and open-ends
3
3
 
4
+ == Introduction
5
+
4
6
  This package contains RangeExtd class, the Extended Range class that features:
5
7
 
6
- 1. includes exclude_begin? (to exclude the "begin" boundary),
7
- 2. allows open-ended range (to the infinity),
8
- 3. defines NONE and ALL constants,
9
- 4. the first self-consistent logical structure,
10
- 5. complete backward-compatibility within the built-in Range.
8
+ 1. includes exclude_begin? (to exclude the "begin" boundary),
9
+ 2. allows open-ended range to the infinity (*not* undefined ends, i.e., +nil+),
10
+ 3. defines NONE and ALL constants,
11
+ 4. the first self-consistent logical range structure in Ruby,
12
+ 5. complete compatibility within the built-in Range.
11
13
 
12
14
  With the introduction of the excluded status of begin, in addition
13
15
  to the end as in built-in Range, and open-ended feature,
14
16
  the logical completeness of the 1-dimensional range is realised.
15
17
 
16
- Then the validity of range is strictly defined now.
17
- Following that, this package adds a few methods, most notably
18
- {Range#valid?} and {Range#empty?}
19
- to Range, and accordingly its any sub-classes,
18
+ A major pro of this library is application to logical operations of multiple ranges,
19
+ most typically Float and/or Rational.
20
+ {Rangeary}[https://rubygems.org/gems/rangeary] uses this library to
21
+ fullest to realise the concept of logical range operations. In doing
22
+ them, the concept of potentially open-ended Ranges with potential
23
+ exclusions of begin and end is essential. For example, the negation
24
+ of Range +(?a..?d)+ is a pair of Ranges +(-"Infinity-Character"...3)+ and
25
+ +(?d(exclusive).."Infinity-Character")+ and its negation is back to
26
+ the original +(?a..?d)+. Such operations are possible only with this
27
+ class +RangeExtd+
28
+
29
+ Rangeary: {https://rubygems.org/gems/rangeary}
30
+
31
+ The built-in Range class is very useful, and has given Ruby users
32
+ a power to make easy coding. Yet, the lack of definition of
33
+ exclusive begin boundary is a nuisance in some cases.
34
+
35
+ Having said that, there is a definite and understandable reason;
36
+ Range in Ruby is not limited at all to Numeric (or strictly speaking,
37
+ Real numbers or their representatives). Range with any object that has a method
38
+ of <tt>succ()</tt> is found to be useful, whereas there is no reverse method for
39
+ <tt>succ()</tt> in general.
40
+ In that sense Range is inherently not symmetric. In addition,
41
+ some regular Range objects are continuous (like Float), while others are discrete
42
+ (like Integer or String). That may add some confusion to the strict definition.
43
+
44
+ To add the feature of the exclusive-begin boundary is in that sense
45
+ not 100 per cent trivial. The definition I adopt for the behaviour of
46
+ {RangeExtd} is probably not the only solution. Personally, I am content
47
+ with it, and I think it achieves the good logical completeness within the frame.
48
+
49
+ I hope you find this package to be useful.
50
+
51
+ === Validity of a Range
52
+
53
+ Ruby built-in Range is very permissive for the elements (members). For example,
54
+ +(true...true)+ is a valid Range whatever it means, although its use
55
+ is highly limited because you cannot iterate over it, that is,
56
+ methods like +each+ with an associated iterator and +to_a+ would raise an Exception (+TypeError+).
20
57
 
21
- For example, <tt>(3...3).valid?</tt> returns false, because the element 3 is
58
+ With this library, the validity of a Range is strictly defined.
59
+ This library adds a few methods, most notably
60
+ {Range#valid?}, {Range#null?} and {Range#empty?}
61
+ to Range class, which would take immediate effect in any of its sub-classes.
62
+
63
+ As an example, <tt>(3...3).valid?</tt> returns false, because the element 3 is
22
64
  inclusive for the begin boundary, yet exclusive for the end boundary,
23
65
  which are contradictory to each other. With this RangeExtd class,
24
- it is expressed as a valid range,
66
+ the following two are regarded as valid ranges,
25
67
 
26
- * RangeExtd.new(3, 3, true, true) # => an empty range
27
- * RangeExtd.new(3, 3, false, false) # => a single-point range (3..3)
68
+ * RangeExtd.new(3, 3, true, true) # => an empty range
69
+ * RangeExtd.new(3, 3, false, false) # => a single-point range (3..3)
28
70
 
29
- However, as long as it is within built-in Range, nothing has changed,
71
+ However, as long as the use is closed within the built-in Range, nothing has changed,
30
72
  so it is completely compatible with the standard Ruby.
31
73
 
32
- To express open-ended ranges is simple; you just use either of
74
+ === Open-ended ranges to infinity
75
+
76
+ Ruby 2.6 and 2.7 have introduced endless and beginless Ranges, respectively.
77
+ The difference between borderless Ranges and open-ended ranges to infinity
78
+ introduced in this library is subtle and perhaps more
79
+ conceptual or philosophical than meaningful in the practical term (see Section "Background"
80
+ for detail). Fear not, though. In practical applications, they are
81
+ compatible and you do not have to be aware of them. In short, you can
82
+ mostly use built-in borderless Ranges unless you want otherwise.
83
+
84
+ To express open-ended ranges defined in this library, you use either of
33
85
  the two (negative and positive, or former and later) constants
34
86
  defined in the class {RangeExtd::Infinity}
35
87
 
36
- * RangeExtd::Infinity::NEGATIVE
37
- * RangeExtd::Infinity::POSITIVE
88
+ * {RangeExtd::Infinity::NEGATIVE}
89
+ * {RangeExtd::Infinity::POSITIVE}
38
90
 
39
- They are basically the object that generalised <tt>Float::INFINITY</tt> to
91
+ They are basically the objects that *generalize* <tt>Float::INFINITY</tt> to
40
92
  any Comparable object. For example,
41
93
 
42
94
  ("a"..RangeExtd::Infinity::POSITIVE).each
43
95
 
44
96
  gives an infinite iterator with <tt>String#succ</tt>, starting from "a"
45
97
  (therefore, make sure to code so it breaks the iterator at one stage!).
98
+ In this case it work in an identical way to (a Ruby-2.6 form of)
46
99
 
100
+ ("a"..).each
47
101
 
48
- The built-in Range class is very useful, and has given Ruby users
49
- a power to make easy coding. Yet, the lack of definition of
50
- exclusive begin boundary is a nuisance in some cases.
102
+ === News: Library locations and support of beginless Ranges
51
103
 
52
- Having said that, there is a definite and understandable reason;
53
- Range in Ruby is not limited at all to Numeric (or strictly speaking,
54
- Real number or its representative). Range with any object that has a method
55
- of <tt>succ()</tt> is found to be useful, whereas there is no reverse method for
56
- <tt>succ()</tt> in general.
57
- In that sense Range is inherently not symmetric. In addition
58
- some regular Range objects are continuous (like Float), while others are discrete
59
- (like Integer or String). That may add some confusion to the strict definition.
104
+ **IMPORTANT**: The paths for the libraries are moved up by one
105
+ directory in {RangeExtd} Ver.2 from Ver.1 in order that their
106
+ locations follow the Ruby Gems convention. In short, the standard way
107
+ to require is +require "range_extd"+, the path of which used to be "range_extd/range_extd"
60
108
 
61
- To add the feature of the exclusive begin boundary is in that sense
62
- not 100 per cent trivial. The definition I adopt for the behaviour of
63
- RangeExtd is probably not the only solution. Personally, I am content
64
- with it, and I think it achieves the good logical completeness within the frame.
109
+ Version of {RangeExtd} is now 2.0.
65
110
 
66
- I hope you find this package to be useful.
111
+ Here is a brief summary of other significant changes in recent
112
+ upgrades. For more extensive information, see History section in this doc.
67
113
 
68
- === News: Endless Range supported
114
+ ==== News: Beginless Range supported
69
115
 
70
- Now, as of 2019 October, this fully supports {Endless Range}[https://rubyreferences.github.io/rubychanges/2.6.html#endless-range-1]
71
- introduced in Ruby 2.6. It is released as Version 1.* finally!
116
+ Ruby 2.7 supports {Beginless range}[https://rubyreferences.github.io/rubychanges/2.7.html#beginless-range].
72
117
 
73
- ==== NOTE: Relationship with Rangeary
118
+ +RangeExtd+ also supports it now.
119
+ With this, there are important changes in specification.
74
120
 
75
- The class to handle multiple Ranges with objects of the same class
76
- (most typically Float),
77
- {Rangeary}[https://rubygems.org/gems/rangeary] uses this library to
78
- fullest, because the concept of potentially open-ended Range on both
79
- begin and end is essential to realise it. For example, the negation
80
- of Range +(?a..?d)+ is Ranges +(-"Infinity-Character"...3)+ and
81
- +(?d(exclusive).."Infinity-Character")+ and its negation is back to
82
- the original +(?a..?d)+. Such operations are possible only with this
83
- class +RangeExtd+
121
+ First, {RangeExtd::NONE} is now in practice
122
+ +RangeExtd((RangeExtd::Nowhere::NOWHERE...RangeExtd::Nowhere::NOWHERE), true)+,
123
+ that is, both ends are {RangeExtd::Nowhere::NOWHERE} and both ends ({RangeExtd#begin} and
124
+ {RangeExtd#end}) are exclusive.
125
+ In the previous versions, both ends of {RangeExtd::NONE} used to have
126
+ +nil+. Range +(nil..nil)+ did not use to be allowed in Ruby-2.6 or earlier,
127
+ and hence it was unique to {RangeExtd::NONE}, conveniently.
128
+ However, Ruby-2.7 and later now accepts nil to nil Range. Then,
129
+ +(nil..nil)+ is perfectly valid and it has in practice
130
+ no difference from +RangeExtd((nil...nil), true)+, which used to be
131
+ the form of {RangeExtd::NONE}, despite the fact
132
+ the former is a completely different object that is close to {RangeExtd::ALL} except
133
+ for the exclusion flags. That is why this change is required.
134
+
135
+ Having said that, this change is majorly conceptual and there is almost no
136
+ change from users' point of view. The begin and end value for {RangeExtd::NONE},
137
+ {RangeExtd::Nowhere::NOWHERE}, behaves almost exactly like {NilClass}
138
+ (see Description below for detail). Given that the value was literally +nil+ in {RangeExtd}
139
+ Ver.1 and earlier, the use of {RangeExtd::Nowhere::NOWHERE} in
140
+ {RangeExtd::NONE} in Ver.2 should not demand any changes in the existing code
141
+ that use {RangeExtd::NONE}. The recommended way to check whether an
142
+ object is {RangeExtd::NONE} or not with use of {RangeExtd#is_none?} (as
143
+ it always has been) (do not be confused with +Enumerable#none?+). Or, in most practical cases, {Range#null?} is
144
+ likely to be what a user wants (n.b., a potential caveat is
145
+ +(true..true).null?+ returns +false+; see subsection "RangeExtd Class"
146
+ in "Description" in this doc for detail).
84
147
 
85
- Rangeary: https://rubygems.org/gems/rangeary
148
+ Second, +RangeExtd.valid?(nil..)+ now returns +true+, which used to be +false+, and it
149
+ is equal to {RangeExtd::ALL}.
150
+
151
+ For example, +"abc"[nil..]+ is a perfectly valid Ruby
152
+ expression in Ruby-2.7 and later, though it used to be invalid or even
153
+ SyntaxError in earlier versions of Ruby. Hence it would be strange if +RangeExtd+ considered it invalid.
154
+
155
+ Note that +RangeExtd.valid?(true..)+ still returns +false+.
156
+
157
+ Other major changes in specification include:
158
+
159
+ * +RangeExtd::Infinity#succ+ is now undefined, in line with Float.
160
+ * Extensions for +Object+ and +Numeric+ are now not in default and optional.
161
+ * +RangeExtd#eql?+ follows the Ruby default behaviour (comparison based on [#hash]), eliminating special cases in comparison with {RangeExtd::NONE}.
162
+ * Fixed a bug where +RangeExtd#min_by+ (and +max_by+ and +minmax_by+) did not work correctly.
163
+
164
+ ==== News: Endless Range supported
165
+
166
+ Now, as of 2019 October, this fully supports {Endless Range}[https://rubyreferences.github.io/rubychanges/2.6.html#endless-range-1]
167
+ introduced in Ruby 2.6. It is released as Version 1.* finally!
86
168
 
87
169
  ==== NOTE: Relationship with Rangesmaller
88
170
 
89
171
  This package RangeExtd supersedes the obsolete {Rangesmaller}[https://rubygems.org/gems/rangesmaller] package and class,
90
- with the added open-ended feature, and a different interface in
172
+ with the added open-ended feature and different interfaces in
91
173
  creating a new instance.
92
- https://rubygems.org/gems/rangesmaller
174
+ {https://rubygems.org/gems/rangesmaller}
175
+
176
+
177
+ == Background
178
+
179
+ === Endless and Beginless Ranges
180
+
181
+ {Endless Range}[https://rubyreferences.github.io/rubychanges/2.6.html#endless-range-1] and
182
+ {Beginless Range}[https://rubyreferences.github.io/rubychanges/2.7.html#beginless-range]
183
+ were introduced in Ruby 2.6 and 2.7, respectively, released in Decembers 2018
184
+ and 2019.
185
+
186
+ Thanks to them, the Ruby built-in +Range+ has achieved part of the
187
+ functionalities and concept of what +RangeExtd+ provides.
188
+ However, there are still some differences, some of which are clear but
189
+ some are more obscured.
190
+
191
+ The most clear advantage of this library is the support for +exclude_begin?+ (to exclude the "begin" boundary).
192
+
193
+ As for the boundary-less Ranges, this library offers, in a word,
194
+ the abstraction of open-ended range (to the infinity).
195
+ A major conceptual difference is that Ruby's built-in beginless/endless +Range+
196
+ provides *undefined* boundaries, whereas +RangeExtd+ does *infinite* boundaries.
197
+
198
+ The difference is subtle but significant.
199
+ Let us take a look at some examples of the standard Ruby, particularly
200
+ Numeric, because Ruby Ranges for Numeric practically provide both
201
+ functionalities. These examples highlight the difference:
202
+
203
+ "abcdef"[..2] # => "abc"
204
+ "abcdef"[..2.0] # => "abc"
205
+ "abcdef"[(-Float::INFINITY)..2] # raise (RangeError)
206
+ "abcdef"[(-1)..2] # => ""
207
+ "abcdef"[(-6)..2] # => "abc"
208
+ "abcdef"[(-7)..2] # => nil
209
+ (-Float::INFINITY..5).first(1) # raise: can't iterate from Float (TypeError)
210
+ (-Float::INFINITY..5).first # => -Infinity
211
+ (-Float::INFINITY..5).begin # => -Infinity
212
+ (..5).first # raise: cannot get the first element of beginless range (RangeError)
213
+ (..5).begin # => nil
214
+
215
+ The first (and second) examples use a beginless Range, where the begin
216
+ value is *undefined*. Then, String class *interprets* the "begin value"
217
+ as 0. By contrast, the third example raises an Exception; it is
218
+ understandable because the begin value is defined but infinitely negative.
219
+ Indeed, a negative value for the index for a String has a special
220
+ meaning as demonstrated in the 4th to 6th examples.
221
+
222
+ The last five examples are interesting. +Range#begin+ simply returns
223
+ the begin-boundary value always. +Range#first+ returns the first
224
+ "element" when no argument is given;
225
+ +(Float::INFINITY..5)+ has the first element and so it is returned. A beginless
226
+ {Range} is a different story; it does not have a defined first element
227
+ and hence +Range#first+ raises +RangeError+. By contrast,
228
+ When an argument +n+ is given to +Range#first+, an Array of
229
+ +n+ elements should be returned. Since counting from any Float is undefined, the Range
230
+ from the negative infinity raises +TypeError+. It makes sense?
231
+
232
+ By the way, I note that +(5..8.6).last(2)+ is valid and returns +[7, 8]+ and +(2.2..8.6).size+
233
+ is also valid, to add confusion.
234
+
235
+ Another point is, although the infinity has a clear mathematical
236
+ definition, not all Ranges accept it. Let us consider your own
237
+ subset class where each instance has only a single lower-case alphabet
238
+ character and where a Range of instances can be defined in the same
239
+ way as the String class. Then, the minimum begin value and maximum end
240
+ value the Range can have are "+a+" and "+z+", respectively. In this
241
+ case, what would the positive (or negative) infinities mean? Perhaps,
242
+ in the strictest term, the infinities for the Range should be invalid?
243
+ Or, the positive infinity should be interpreted as the index for "+z+"
244
+ in this case, or else?
245
+
246
+ Conceptually, the inclusive interpretation is more convenient.
247
+ Indeed, {Rangeary}[https://rubygems.org/gems/rangeary] uses
248
+ +RangeExtd+ in such a way, that is,
249
+ the negation of the Range is actively used for logical operations of
250
+ Arrays of Ranges. However, you cannot force each application to
251
+ accept the definition.
252
+
253
+ In summary, *undefined* boundaries are undefined by definition and
254
+ their interpretations are up to each application, whereas positive or
255
+ negative infinities may have clear definitions although more
256
+ flexible interpretations may be preferred in practical applications.
257
+
258
+ Given all these, {RangeExtd::Infinity::NEGATIVE} and {RangeExtd::Infinity::POSITIVE}
259
+ in this library behaves like +nil+, though it is possible for users to
260
+ distinguish them.
261
+
262
+
263
+ === Behaviours of endless and beginless Ranges
264
+
265
+ The behaviours of the built-in Endless/Beginless Range can be a little confusing.
266
+ In addition, it seems there are bugs for +Range#size+
267
+ ({Bug #18983}[https://bugs.ruby-lang.org/issues/18983] and
268
+ {Bug #18993}[https://bugs.ruby-lang.org/issues/18993])
269
+ or at least points that contradict the specification described in the
270
+ official doc, which adds confusion.
271
+
272
+ In the Ruby implementation, the begin and end values of a beginless and endless Ranges
273
+ are both interpreted as +nil+. In Ruby, +nil == nil+ is true and
274
+ therefore
275
+
276
+ (?a..).end == (5..).end
277
+
278
+ is also +true+, whereas
279
+
280
+ (?a..).end == (5..Float::INFINITY).end
281
+
282
+ is +false+. Below is a more extended set of examples.
283
+
284
+ (-Float::INFINITY..Float::INFINITY).size # => Infinity
285
+ ( Float::INFINITY..Float::INFINITY).size # raises FloatDomainError
286
+ num1 = (5..Float::INFINITY)
287
+ num2 = (5..)
288
+ num1.end != num2.end # => true
289
+ num1.size # => Infinity
290
+ num2.size # => Infinity
291
+
292
+ str1 = (?a..)
293
+ str1.end != num1.end # => true
294
+ str1.end == num2.end # => true (because both are nil)
295
+ str1.size # => nil (because Range#size is defined for Numeric only)
296
+ (..?z).size # => Infinity (contradicting the specification?)
297
+
298
+ (..3).to_s => "..3"
299
+ (3..).to_s => "3.."
300
+ (3..nil).to_s => "3.."
301
+ (nil..3).to_s => "..3"
93
302
 
303
+ (nil..) == (..nil) # => true
304
+ (nil..) != (...nil) # => true (because exclude_end? differ)
305
+ "abcdef"[..nil] # => "abcdef" (i.e., it is interpreted as (0..IntegerInfinity)
306
+ # (n.b., nil.to_i==0; Integer(nil) #=> TypeError))
307
+ "abcdef"[..?a] # raise: no implicit conversion of String into Integer (TypeError)
308
+ "abcdef"[0..100] # => "abcdef"
309
+ "abcdef"[-100..100] # => nil
310
+
311
+ (..nil).size # => Float::INFINITY
312
+
313
+ (..nil).begin # => nil
314
+ (..nil).first # raise: cannot get the first element of beginless range (RangeError)
315
+ (..nil).last # raise: cannot get the last element of endless range (RangeError)
316
+ (..nil).end # => nil
317
+
318
+ (..nil).cover? 5 # => true
319
+ (..nil).cover? ?a # => true
320
+ (..nil).cover? [?a] # => true
321
+ (..nil).cover? nil # => true
322
+
323
+ For Integer,
324
+
325
+ num1 = (5..Float::INFINITY)
326
+ num2 = (5..)
327
+ num1.end != num2.end # => true (because (Float::INFINITY != nil))
328
+ num1.size # => Float::INFINITY
329
+ num2.size # => Float::INFINITY
330
+
331
+ (3...) == (3...nil) # => true
332
+ (3..) != (3...nil) # => true (because exclude_end? differ)
333
+
334
+ (3..).size # => Float::INFINITY
335
+ (..3).begin # => nil
336
+ (..3).first # raise: cannot get the first element of beginless range (RangeError)
337
+ (3..).last # raise: cannot get the last element of endless range (RangeError)
338
+ (3..).end # => nil
339
+ (..3).each{} # raise: `each': can't iterate from NilClass (TypeError)
340
+ (..3).to_a # raise: `each': can't iterate from NilClass (TypeError)
341
+ (3..).to_a # raise: `to_a': cannot convert endless range to an array (RangeError)
342
+ (3..Float::INFINITY).to_a # Infinite loop!
343
+
344
+ (-Float::INFINITY..4).first # => -Float::INFINITY
345
+ (4..Float::INFINITY).last # => Float::INFINITY
346
+ (-Float::INFINITY..4).first(2) # raise: can't iterate from Float (TypeError)
347
+ (4..Float::INFINITY).last(2) # Infinite loop!
348
+
349
+ For String (or any user-defined class?),
350
+
351
+ (?a..).end == (5..).end # => true (because both are nil)
352
+ (?a..).end != (5..Float::INFINITY).end # => true
353
+ (..?a).begin == (..5).begin # => true (because both are nil)
354
+ (..?a).begin != ((-Float::INFINITY)..5).begin # => true
355
+ (..?a).size # => Float::INFINITY
356
+ (?a..).size # => nil
357
+
358
+ (..?a).begin # => nil
359
+ (..?a).first # raise: cannot get the first element of beginless range (RangeError)
360
+ (?a..).last # raise: cannot get the last element of endless range (RangeError)
361
+ (?a..).end # => nil
362
+ (..?a).each{} # raise: `each': can't iterate from NilClass (TypeError)
363
+ (..?a).to_a # raise: `each': can't iterate from NilClass (TypeError)
364
+ (?a..).to_a # raise: `to_a': cannot convert endless range to an array (RangeError)
365
+ (?a..Float::INFINITY).to_a # raise: bad value for range (ArgumentError) # b/c it is not String!
366
+
367
+ === Comment on Range#size
368
+
369
+ The behaviour of +Range#size+ is highly confusing.
370
+ According to {Official doc}[https://ruby-doc.org/core-3.1.2/Range.html#method-i-size],
371
+
372
+ Returns the count of elements in self if both begin and end values are numeric;
373
+ otherwise, returns nil
374
+
375
+ But actually Ruby does not necessarily behaves in this way (see examples above).
376
+ In addition, the meaning of "elements" in the doc for general Numeric is
377
+ ambiguous. The following demonstrates it (reported as {Bug #18993}[https://bugs.ruby-lang.org/issues/18993]):
378
+
379
+ (5.quo(3)...5).size # => 3
380
+ (5.quo(3).to_f...5).size # => 4
381
+ (5.quo(3)..5).size # => 4
382
+ (5.quo(3).to_f..5).size # => 4
383
+
384
+ === Comment on Range#count
385
+
386
+ The behaviour of +Range#count+ is mostly understandable, but those of
387
+ borderless or with infinities are not trivial.
388
+
389
+ (5..).count # => Float::INFINITY
390
+ (..5).count # => Float::INFINITY
391
+ (..nil).count # => Float::INFINITY
392
+ (-Float::INFINITY..nil) # => Float::INFINITY
393
+ (-Float::INFINITY..Float::INFINITY).count # raises (TypeError) "can't iterate from Float"
394
+ (..5).count(4) # raises (TypeError)
395
+ (..5).count{|i| i<3} # raises (TypeError)
396
+ (1..).count(4) # infinite loop!
397
+ (1..).count{|i| i<3} # infinite loop!
398
+
399
+ Basically, in some limited cases, the method returns Infinity, which
400
+ are special cases.
401
+
402
+ Given these, +RangeExtd::ALL.count+ returns +Float::INFINITY+ as
403
+ another special case.
94
404
 
95
405
  == Install
96
406
 
97
407
  gem install range_extd
98
408
 
99
- Two files
409
+ installs several files including
100
410
 
101
- range_extd/range_extd.rb
102
- range_extd/infinity/infinity.rb
411
+ range_extd.rb
412
+ range_extd/infinity.rb
103
413
 
104
- should be installed in one of your <tt>$LOAD_PATH</tt>
414
+ in one of your <tt>$LOAD_PATH</tt>
105
415
 
106
- Alternatively get it from
416
+ Alternatively get it from {http://rubygems.org/gems/range_extd}
107
417
 
108
- http://rubygems.org/gems/range_extd
418
+ Or, if you manually install it, place all the Ruby files under +lib/+
419
+ directory under one of your +RUBYLIB+ directory paths, preserving the
420
+ directory structure. Note that +range_extd.rb+ must be directly under
421
+ the library directory.
109
422
 
110
423
  Then all you need to do is
111
424
 
112
- require 'range_extd/range_extd'
425
+ require "range_extd/load_all"
426
+
427
+ Or, if you only want minimum functions of this library, you can instead
113
428
 
114
- or, possibly as follows, if you manually install it
429
+ require "range_extd"
115
430
 
116
- require 'range_extd'
431
+ Basically, "+range_extd/load_all.rb+" is a wrapper Ruby file, which requires the following files:
117
432
 
118
- in your Ruby script (or irb). The other file
433
+ require "range_extd"
434
+ require "range_extd/numeric"
435
+ require "range_extd/object"
436
+ require "range_extd/infinity"
437
+ require "range_extd/nowhere"
438
+ require "range_extd/range"
439
+ require "range_extd/nil_class"
119
440
 
120
- range_extd/infinity/infinity.rb
441
+ Among these, the first three files are independent, whereas the last
442
+ four files are inseparable from the first one and are automatically
443
+ require-d from the first one.
121
444
 
122
- is called (required) from it automatically.
445
+ The second and third files are a set of utility libraries; if your code
446
+ requires them, some methods are added or some existing methods are slightly altered in the
447
+ existing Ruby built-in classes: +Object+ and +Numeric+
448
+ (including +Float+ and +Integer+).
449
+ How they are modified are backward-compatible; simply a few
450
+ new features are added. Their use is highly recommended; otherwise,
451
+ the use of this library would be very limited. For example,
452
+ the comparison operator +<=>+ would not be commutative without them,
453
+ which might result in some nasty surprises. For detail, refer to the
454
+ individual references.
123
455
 
124
456
  Have fun!
125
457
 
126
458
 
127
459
  == Simple Examples
128
460
 
461
+ In the following, I assume all the files are required.
462
+
129
463
  === How to create a RangeExtd instance
130
464
 
131
465
  Here are some simple examples.
132
466
 
467
+ require "range_extd/load_all"
133
468
  r = RangeExtd(?a...?d, true) # => a<...d
134
469
  r.exclude_begin? # => true
135
470
  r.to_a # => ["b", "c"]
@@ -141,20 +476,21 @@ Here are some simple examples.
141
476
  (RangeExtd::Infinity::NEGATIVE..RangeExtd::Infinity::POSITIVE) \
142
477
  == RangeExtd::ALL # => true
143
478
 
144
- Basically, there are three forms:
479
+ +RangeExtd+ provides three forms for initialization (hint: the first
480
+ form is probably the handiest with least typing and is the easiest to remember):
145
481
 
146
- RangeExtd(range, [exclude_begin=false, [exclude_end=false]], opts)
147
- RangeExtd(obj_begin, obj_end, [exclude_begin=false, [exclude_end=false]], opts)
148
- RangeExtd(obj_begin, string_form, obj_end, [exclude_begin=false, [exclude_end=false]], opts)
482
+ RangeExtd(range, [exclude_begin=false, [exclude_end=false]])
483
+ RangeExtd(obj_begin, obj_end, [exclude_begin=false, [exclude_end=false]])
484
+ RangeExtd(obj_begin, string_form, obj_end, [exclude_begin=false, [exclude_end=false]])
149
485
 
150
- The two parameters in the brackets specify the respective boundary to be excluded if true,
151
- or included if false (Default). If they contradict to the first
152
- parameter of the range (Range or RangeExtd), those latter two parameters are used.
153
- Also, you can specify the same parameters as the options <tt>:exclude_begin</tt>
154
- and <tt>:exclude_end</tt>, which have the highest priority, if specified.
155
- The <tt>string_form</tt> in the third form is like ".." and "<...", which can be
156
- defined by a user (see {RangeExtd.middle_strings=}() for detail), and is arguably
157
- the most visibly recognisable way for any range with <tt>exclude_begin=true</tt>.
486
+ The two parameters in the square-brackets specify the respective boundaries to be excluded if true,
487
+ or included if false (Default). If they contradict the first
488
+ parameter of the range (+Range+ or +RangeExtd+), the latter two parameters have priorities.
489
+ Alternatively, you can specify the same parameters as the options <tt>:exclude_begin</tt>
490
+ and <tt>:exclude_end</tt>, which have the highest priority if specified.
491
+ The <tt>string_form</tt> in the third form is like ".." (including both ends) and "<..." (excluding both ends),
492
+ set by users (see {RangeExtd.middle_strings=}() for detail), and is arguably
493
+ the most visibly-recognisable way to specify any range with <tt>exclude_begin=true</tt>.
158
494
 
159
495
  <tt>RangeExtd.new()</tt> is the same thing.
160
496
  For more detail and examples, see {RangeExtd.initialize}.
@@ -162,102 +498,106 @@ For more detail and examples, see {RangeExtd.initialize}.
162
498
 
163
499
  === Slightly more advanced uses
164
500
 
165
- (1..RangeExtd::Infinity::POSITIVE).each do |i|
501
+ RangeExtd((0..), true).each do |i|
166
502
  print i
167
503
  break if i >= 9
168
- end # => self ( "123456789" => STDOUT )
169
- (nil..nil).valid? # => false
504
+ end # => self; "123456789" => STDOUT
505
+ # *NOT* "012..."
506
+ (nil..nil).valid? # => true
170
507
  (1...1).valid? # => false
171
508
  (1...1).null? # => true
172
509
  RangeExtd.valid?(1...1) # => false
173
510
  RangeExtd(1, 1, true, true).valid? # => true
174
511
  RangeExtd(1, 1, true, true).empty? # => true
175
512
  RangeExtd(?a, ?b, true, true).to_a? # => []
176
- RangeExtd(?a, ?b, true, true).empty? # => true
513
+ RangeExtd(?a, ?b, true, true).null? # => true (empty? is same in this case)
177
514
  RangeExtd(?a, ?e, true, true).to_a? # => ["b", "c", "d"]
178
- RangeExtd(?a, ?e, true, true).empty? # => false
515
+ RangeExtd(?a, ?e, true, true).null? # => false
179
516
  RangeExtd::NONE.is_none? # => true
517
+ RangeExtd(1...1, true) == RangeExtd::NONE # => true
180
518
  RangeExtd::ALL.is_all? # => true
519
+ (nil..nil).is_all? # => false
520
+ (-Float::INFINITY..Float::INFINITY).is_all? # => false
521
+ (nil..nil).equiv_all? # => true
522
+ (-Float::INFINITY..Float::INFINITY).equiv_all? # => true
181
523
  (3...7).equiv?(3..6) # => true
524
+ (nil..nil).equiv?(RangeExtd::ALL) # => true
182
525
 
183
- All the methods that are in the built-in Range can be used.
526
+ All the methods that are in the built-in Range can be used in
527
+ {RangeExtd}, which is a child class of {Range}.
184
528
 
185
529
 
186
530
  == Description
187
531
 
188
- Once the file +range_extd/range_extd.rb+ is required, the two classes are defined:
532
+ Once the file +range_extd.rb+ is required, the three classes are defined:
189
533
 
190
534
  * RangeExtd
191
535
  * RangeExtd::Infinity
536
+ * RangeExtd::Nowhere
192
537
 
193
- Also, several methods are added or altered in Range class.
194
- All the changes made in Range are backward-compatible with the original.
538
+ Also, several methods are added or altered in {Range} class and {NilClass}.
539
+ All the changes made in them are backward-compatible with the original.
540
+
541
+ Note that whereas the changes in {Range} could be in principle separable
542
+ from {RangeExtd}, if no one would likely want to use them separately, those
543
+ in {NilClass} are unavoidable. Without them, {RangeExtd::NONE} could
544
+ not be defined, for +ArgumentError+ (bad value for range) would be
545
+ raised in the initialization due to the way Ruby built-in Range is
546
+ implemented.
547
+
548
+ See {discussion at Stackoverflow}[https://stackoverflow.com/a/14449380/3577922].
195
549
 
196
550
  === RangeExtd::Infinity Class
197
551
 
198
- Class {RangeExtd::Infinity} has basically only two constant instances.
552
+ Class {RangeExtd::Infinity} has only two constant instances.
199
553
 
200
554
  * RangeExtd::Infinity::NEGATIVE
201
555
  * RangeExtd::Infinity::POSITIVE
202
556
 
203
- They are the objects that generalise the concept of
557
+ They are the objects that generalize the concept of
204
558
  <tt>Float::INFINITY</tt>
205
- to any Comparable objects. The methods <tt><=></tt> and <tt>succ</tt> are defined.
559
+ to any Comparable objects. The methods <tt><=></tt> are defined.
206
560
 
207
- You can use them the same as other objects, such as,
561
+ You can use them in the same way as other objects, such as,
208
562
 
209
- ("k"..RangeExtd::Infinity::POSITIVE)
563
+ (RangeExtd::Infinity::NEGATIVE.."k")
210
564
 
211
- However as they do not have any other methods,
212
- the use out of Range-type class is probably meaningless.
565
+ However, since they do not have any other methods,
566
+ the use of them out of Range or its sub-classes is probably meaningless.
213
567
 
214
- Note for any Numeric object, please use <tt>Float::INFINITY</tt> instead in principle.
568
+ Note for any Numeric object, you probably would like to use <tt>Float::INFINITY</tt> instead in principle.
215
569
 
216
570
  Any objects in any user-defined Comparable class are commutatively comparable with
217
571
  those two constants, as long as the cmp method of the class is written
218
- politely.
572
+ in the *standard* way, that is, delegating the cmp method to the
573
+ parent class, ultimately +Object+, when they encounter an object of a
574
+ class they don't know.
219
575
 
220
- For more detail, see its documents (YARD or RDoc-style
221
- documents embedded in the code, or see {RubyGems webpage}[http://rubygems.org/gems/range_extd]).
222
-
223
- **Note1:** +RangeExtd::Infinity::POSITIVE+ is practically the same as
224
- {Endless Range}[https://rubyreferences.github.io/rubychanges/2.6.html#endless-range-1]
225
- introduced in Ruby 2.6 released in 2018 December!! In other words,
226
- the official Ruby has finally implement a part of this library!
227
- However, +RangeExtd::Infinity::NEGATIVE+ is not yet implemented in the
228
- official Ruby Range (it has no "boundless begin"), and hence this library still has some use, which
229
- supplements the mathematical incompleteness of the standard Range in
230
- the official Ruby.
576
+ For more detail, see the document at {RubyGems webpage}[http://rubygems.org/gems/range_extd],
577
+ which is generated from the source-code annotation with YARD.
231
578
 
232
- **Note2:**
233
- As of Ver.1.1, the +RangeExtd::Infinity+ class instances are not
234
- comparable with +Float::INFINITY+; for example,
579
+ === RangeExtd::Nowhere Class
235
580
 
236
- RangeExtd::Infinity::POSITIVE != Float::INFINITY # => true
581
+ Class {RangeExtd::Nowhere} is a Singleton class, which mimics
582
+ {NilClass}. The sole instance is available as
237
583
 
238
- Conceptionally, the former is a generalised object of the latter and
239
- hence they should not be *equal*. See the reference of
240
- {RangeExtd::Infinity} for detail. Note, the behaviour of Endless Range from Ruby 2.6
241
- may feel a little odd, as follows:
584
+ * RangeExtd::Nowhere::NOWHERE
242
585
 
243
- num1 = (5..Float::INFINITY)
244
- num2 = (5..)
245
- num1.end != rnum2.end # => true
246
- num1.size # => Infinity
247
- num2.size # => Infinity
586
+ This instance returns, for example, true for +nil?+ and the same
587
+ object-ID for +object_id+ as +nil+ and equal (+==+) to nil. It is used to constitute {RangeExtd::NONE}.
248
588
 
249
- str1 = (?a..)
250
- str1.end == num2.end # => true (because both are nil)
251
- str1.size # => nil
589
+ It is not, however, recognised as the false value in conditional statements.
252
590
 
591
+ Also, a Range containing {RangeExtd::Nowhere::NOWHERE} is *not* "valid"
592
+ as a Range (see below), except for {RangeExtd::NONE}.
253
593
 
254
594
  === RangeExtd Class
255
595
 
256
- RangeExtd objects are immutable, the same as Range.
257
- Hence once an instance is created, it would not change.
596
+ {RangeExtd} objects are immutable, the same as {Range}.
597
+ Hence once an instance has been created, it would not change.
258
598
 
259
599
  How to create an instance is explained above (in the Examples
260
- sections). Any attempt to try to create an instance that is not
600
+ sections). Any attempt to try to create an instance of {RangeExtd} that is not
261
601
  "valid" as a range (see below) raises an exception (<tt>ArgumentError</tt>), and fails.
262
602
 
263
603
  There are two constants defined in this class:
@@ -266,7 +606,7 @@ There are two constants defined in this class:
266
606
  * RangeExtd::ALL
267
607
 
268
608
  The former represents the empty range and the latter does the range
269
- covers everything, namely open-ended for the both negative and
609
+ that covers everything, namely open-ended for the both negative and
270
610
  positive directions.
271
611
 
272
612
  In addition to all the standard methods of {Range}, the following
@@ -278,7 +618,7 @@ See the document of each method for detail (some are defined only in
278
618
  * <tt>valid?</tt>
279
619
  * <tt>empty?</tt>
280
620
  * <tt>null?</tt>
281
- * <tt>is_none?</tt>
621
+ * <tt>is_none?</tt>
282
622
  * <tt>is_all?</tt>
283
623
  * <tt>equiv?</tt>
284
624
 
@@ -289,65 +629,93 @@ to the instance method <tt>valid?</tt>:
289
629
  * <tt>RangeExtd.middle_strings=(ary)</tt>
290
630
  * <tt>RangeExtd.middle_strings</tt>
291
631
 
632
+ ==== Details about validity, emptiness, and nullness
633
+
292
634
  What is valid (<tt>#valid?</tt> => true) as a range is defined as follows.
293
635
 
294
636
  1. Both <tt>begin</tt> and <tt>end</tt> elements must be Comparable to each other,
295
637
  and the comparison results must be consistent between the two.
296
- The sole exception is {RangeExtd::NONE}, which is valid.
297
- For example, <tt>(nil..nil)</tt> is NOT valid (nb., it raised Exception in Ruby 1.8).
298
- 2. begin must be smaller than or equal (<tt>==</tt>) to end,
638
+ The three exceptions are {RangeExtd::NONE} and Beginless and Endless Ranges
639
+ (introduced in Ruby 2.7 and 2.6, respectively),
640
+ which are all valid. Accordingly, +(nil..nil)+ is
641
+ valid in {RangeExtd} Ver.2.0+ (nb., it used to raise Exception in Ruby 1.8).
642
+ 2. Except for {RangeExtd::NONE} and Beginless Range, the object of +Range#begin+ must have the method +<=+.
643
+ Therefore, some Endless Ranges (Ruby 2.6 and later) like +(true..)+ are *not* valid.
644
+ Note even "+true+" has the method +<=>+ and hence checking +<=+ is essential.
645
+ 3. Similarly, except for {RangeExtd::NONE} and Endless Range, +Range#end+ must have the method +<=+.
646
+ Therefore, some Beginless Ranges (Ruby 2.7 and later) like +(..true)+ are *not* valid.
647
+ 4. *begin* must be smaller than or equal (<tt>==</tt>) to *end*,
299
648
  that is, <tt>(begin <=> end)</tt> must be either -1 or 0.
300
- 3. If begin is equal to end, namely, <tt>(begin <=> end) == 0</tt>,
301
- the exclude status of the both ends must agree.
302
- That is, if the <tt>begin</tt> is excluded, <tt>end</tt> must be also excluded,
649
+ 5. If *begin* is equal to *end*, namely, <tt>(begin <=> end) == 0</tt>,
650
+ the exclude status of the both ends must agree, except for the cases
651
+ where both +begin+ and +end+ are +nil+ (beginless and endless Range).
652
+ In other words, if the +begin+ is excluded, +end+ must be also excluded,
303
653
  and vice versa.
304
- For example, <tt>(1...1)</tt> is NOT valid for that reason,
654
+ For example, +(1...1)+ is NOT valid for this reason,
305
655
  because any built-in Range object has the exclude status
306
- of false (namely, inclusive) for <tt>begin</tt>.
656
+ of +false+ (namely, inclusive) for +begin+, whereas
657
+ +RangeExtd(1...1, true)+ is valid and equal (<tt>==</tt>) to {RangeExtd::NONE}.
658
+ 6. Range containing {RangeExtd::Nowhere::NOWHERE} except for
659
+ {RangeExtd::NONE} is *not* valid.
307
660
 
308
- For more detail and examples see the documents of
661
+ For more detail and examples, see the documents of
309
662
  {RangeExtd.valid?} and {Range#valid?}
310
663
 
311
- The definition of what is empty (<tt>#empty?</tt> => true) as a range is as follows;
664
+ The definition of what is empty ({Range#empty?} == +true+) as a range is as follows;
312
665
 
313
666
  1. the range must be valid: <tt>valid?</tt> => true
314
- 2. if the range id discrete, that is, begin has
667
+ 2. if the range id discrete, that is, +begin+ has the
315
668
  <tt>succ</tt> method, there must be no member within the range
316
669
  (which means the begin must be excluded, too):
317
670
  <tt>to_a.empty?</tt> => true
318
- 3. if the range is continuous, that is, begin does not have
319
- <tt>succ</tt> method, begin and end must be equal
320
- (<tt>(begin <=> end)</tt> => 0) and both the boundaries must
671
+ 3. if the range is continuous, that is, begin does not have the
672
+ <tt>succ</tt> method, +begin+ and +end+ must be equal
673
+ (<tt>(begin <=> end) == 0</tt>) and both the boundaries must
321
674
  be excluded: <tt>(exclude_begin? && exclude_end?)</tt> => true.
322
675
 
323
676
  Note that ranges with equal <tt>begin</tt> and <tt>end</tt> with inconsistent two
324
677
  exclude status are not valid, as mentioned in the previous paragraph.
325
678
  The built-in Range always has the begin-exclude status of
326
- <tt>false</tt>. For that reason, no instance of built-in Range
327
- has the status of <tt>empty?</tt> of <tt>true</tt>.
679
+ <tt>false</tt>. For that reason, no instances of built-in Range
680
+ have the status of <tt>empty?</tt> of <tt>true</tt>.
328
681
 
329
682
  For more detail and examples see the documents of
330
683
  {Range#empty?}
331
684
 
332
685
  Finally, {Range#null?} is equivalent to "either empty or not valid".
333
686
  Therefore, for RangeExtd objects <tt>null?</tt> is equivalent to
334
- <tt>empty?</tt>.
687
+ <tt>empty?</tt>. In most practical cases, {Range#null?} will be
688
+ perhaps more useful than {Range#empty?}.
335
689
 
336
690
  In comparison (<tt><=></tt>) between a RangeExtd and another RangeExtd or Range
337
- object, those definitions are taken into account.
691
+ object, these definitions are taken into account.
338
692
  Some of them are shown in the above Examples section.
339
- For more detail, see {Range#==}> and {RangeExtd#==}>, as
340
- well as <tt>#eql?</tt>.
693
+ For more detail, see {Range#<=>} and {RangeExtd#<=>}.
341
694
 
342
695
  Note that as long as the operation is within Range objects, the
343
696
  behaviour is identical to the standard Ruby -- it is completely
344
- compatible. Therefore, requiring this library would not affect any
697
+ backward-compatible. Therefore, requiring this library should not affect any
345
698
  existing code in principle.
346
699
 
700
+ ==== equality
701
+
702
+ The method +eql?+ checks the equality of the hash values according to
703
+ Ruby's specification and hence every parameter must agree. By
704
+ contrast, <tt>==</tt> makes a more rough comparison and if the two
705
+ objects are broadly the same, returns +true+.
706
+
707
+ RaE(0...0, true) == RaE(?a...?a, true) # => false
708
+ RaE(0...1, true) == RaE(5...6, true) # => true
347
709
 
348
710
  == Known bugs
349
711
 
350
- * Note this library does not work in Ruby 1.8 or earlier.
712
+ * Although {RangeExtd::Nowhere::NOWHERE} cannot be used in the context of
713
+ {RangeExtd} (because it is not {Range#valid?}), users could still
714
+ use it within just the built-in Range framework.
715
+ Perhaps, {RangeExtd::Nowhere::NOWHERE} should be redefined as a
716
+ non-nil object?
717
+ * This library of Version 2+ does not work in Ruby 2.6 or earlier.
718
+ * This library of Version 1 does not work in Ruby 1.8 or earlier.
351
719
  For Ruby 1.9.3 it is probably all right, though I have never tested it.
352
720
  * Some unusual (rare) boundary conditions are found to vary from
353
721
  version to version in Ruby, such as an implementation of +Hash#=>+.
@@ -358,24 +726,88 @@ existing code in principle.
358
726
  * {RangeExtd#hash} method does not theoretically guarantee to return a unique
359
727
  number for a {RangeExtd} object, though to encounter a hash number that is
360
728
  used elsewhere is extremely unlikely to happen in reality.
729
+ * +RangeExtd::NONE.inspect+ and +RangeExtd::NONE.to_s+ return "Null<...Null", but it is
730
+ displayed as "nil...nil" in Ruby +irb+ and hence it is not easily
731
+ recognizable in +irb+.
361
732
 
362
733
  Extensive tests have been performed, as included in the package.
363
734
 
364
735
  == ToDo
365
736
 
366
- Nothing on the horizon.
737
+ * If {RangeExtd::Infinity::POSITIVE} (and NEGATIVE) behaves like
738
+ +nil+ (in the same way as {RangeExtd::Nowhere::NOWHERE}, it may be useful.
739
+ However, a range containing such objects would not work with String
740
+ like <tt>"abcde"[my_nil..]</tt>, for it seems the String class makes
741
+ a pretty rigorous check about +nil+. So, I guess the practical
742
+ applicability would not be improved so much, as far as the built-in
743
+ Ruby classes are concerned.
744
+ * A method like "+similar+" may be useful. For example,
745
+ +(-Float::INFINITY..Float::INFINITY)+ and +(-Float::INFINITYnil...Float::INFINITY)+
746
+ have no mathematical difference, because excluding an infinity is
747
+ meaningless. Indeed it makes no difference in the results of operations with
748
+ non-infinite Range/Rangeary.
367
749
 
368
750
  == History memo
369
751
 
370
752
  * <tt>((?a..?z) === "cc")</tt> would give false with Ruby 2.6.x or earlier, but true if later.
753
+ * <tt>(Float::INFINITY..Float::INFINITY).size</tt> used to return 0
754
+ (in Ruby-2.1 at least) but raises +FloatDomainError: NaN+ as of
755
+ Ruby-2.6 and later, including Ruby 3. I do not know in which version
756
+ the behaviour changed.
757
+
758
+
759
+ === RangeExtd Ver.2
760
+
761
+ * The paths for the libraries are moved up by one directory in {RangeExtd} Ver.2 from Ver.1 in order that their locations follow the Ruby Gems convention.
762
+ * Compatible with Beginless Range introduced in Ruby-2.7.
763
+ * +RangeExtd::Infinity#succ+ is now undefined, in line with Float.
764
+ * Extensions for +Object+ and +Numeric+ are not in default anymore and are optional.
765
+ * +RangeExtd#eql?+ follows the Ruby default behaviour (comparison based on [#hash]), eliminating special cases in comparison with {RangeExtd::NONE}.
766
+ * Fixed a bug where +RangeExtd#min_by+ (and +max_by+ and +minmax_by+) did not work correctly.
767
+
768
+ === RangeExtd Ver.1.1
769
+
770
+ As of Ver.1.1, the +RangeExtd::Infinity+ class instances are not
771
+ comparable with +Float::INFINITY+; for example,
772
+
773
+ RangeExtd::Infinity::POSITIVE != Float::INFINITY # => true
774
+
775
+ Conceptionally, the former is a generalized object of the latter and
776
+ hence they should not be *equal*. See the reference of
777
+ {RangeExtd::Infinity} for detail. Note, the behaviour of Endless Range from Ruby 2.6
778
+ may feel a little odd, as follows:
779
+
780
+ num1 = (5..Float::INFINITY)
781
+ num2 = (5..)
782
+ num1.end != num2.end # => true
783
+ num1.size # => Infinity
784
+ num2.size # => Infinity
785
+
786
+ str1 = (?a..)
787
+ str1.end == num2.end # => true (because both are nil)
788
+ str1.size # => nil
789
+
790
+ === RangeExtd Ver.1.0
791
+
792
+ +RangeExtd::Infinity::POSITIVE+ is practically the same as
793
+ {Endless Range}[https://rubyreferences.github.io/rubychanges/2.6.html#endless-range-1]
794
+ introduced in Ruby 2.6 released in 2018 December!! In other words,
795
+ the official Ruby has finally implement a part of this library!
796
+ However, +RangeExtd::Infinity::NEGATIVE+ was not yet implemented (at
797
+ the time) in the
798
+ official Ruby Range (it has no "boundless begin").
799
+
371
800
 
372
801
  == Final notes
373
802
 
374
803
  All the behaviours within RangeExtd (not Range), such as
375
804
  any comparison between two RangeExtd, should be (or hopefully?)
376
805
  natural for you. At least it is well-defined and self-consistent, as
377
- the logical structure of the ranges is now complete with RangeExtd.
378
- Note some behaviours for open-ended or begin-excluded ranges may
806
+ the logical structure of the ranges is now complete with {RangeExtd}.
807
+
808
+ In this section in the earlier versions, I wrote:
809
+
810
+ > Note that some behaviours for open-ended or begin-excluded ranges may
379
811
  give you a little shock at first. For example, the method
380
812
  <tt>member?(obj)</tt> for an open-ended range for the negative direction with
381
813
  discrete elements returns <tt>nil</tt>. That is because no meaningful method
@@ -384,11 +816,15 @@ theoretically impossible in general to check whether the given obj is a member o
384
816
  the range or not. You may find it to be weird, but that just means
385
817
  the concept of the infinity is unfamiliar to us mortals!
386
818
 
387
- On the other hand, the comparison between RangeExtd and Range may have
388
- more occasional surprises. That is because some of the accepted
819
+ Now, interestingly, the introduction of "beginless Range" in Ruby
820
+ means every Ruby programmer must be familiar with the concept!
821
+ I would call it a progress.
822
+
823
+ Still, comparisons between RangeExtd and Range may give you
824
+ occasional surprises. This is because some of the accepted
389
825
  ranges by built-in Range class are no longer valid in this framework with the
390
826
  inclusion of exclude-status of the begin boundary, as explained.
391
- Hopefully you will feel it to be natural as you get accustomed to it.
827
+ Hopefully you will feel it natural as you get accustomed to it.
392
828
  And I bet once you have got accustomed to it, you will never want to
393
829
  go back to the messy world of logical incompleteness, that is, the
394
830
  current behaviour of Range!
@@ -396,9 +832,6 @@ current behaviour of Range!
396
832
  Enjoy.
397
833
 
398
834
 
399
-
400
- == Miscellaneous
401
-
402
835
  == Copyright etc
403
836
 
404
837
  Author:: Masa Sakano < info a_t wisebabel dot com >
@@ -410,130 +843,459 @@ Versions:: The versions of this package follow Semantic Versioning (2.0.0) http:
410
843
 
411
844
  = RangeExtd - 拡張Rangeクラス - exclude_begin と無限大に開いた範囲と
412
845
 
846
+ == はじめに
847
+
413
848
  このパッケージは、Range を拡張した RangeExtd クラスを定義しています。
414
849
  以下の特徴を持ちます。
415
850
 
416
- 1. メソッド exclude_begin? の導入 (レンジの始点を除外できる),
417
- 2. (無限大に)開いたレンジ
418
- 3. NONE (空レンジ) と ALL (全範囲レンジ)定数の導入
419
- 4. 初めて自己論理的に完結したレンジ構造の達成
420
- 5. 組込Rangeとの完全後方互換性
851
+ 1. メソッド exclude_begin? の導入 (レンジの始点を除外できる),
852
+ 2. (無限大に)開いたレンジ(+nil+のように*未定義*のレンジではない)
853
+ 3. NONE (空レンジ) と ALL (全範囲レンジ)定数の導入
854
+ 4. Rubyで初めて自己論理的に完結したレンジ構造の達成
855
+ 5. 組込Rangeとの完全後方互換性
421
856
 
422
857
  組込Rangeにある exclude_end に加えて、exclude_beginを導入したこと、及
423
858
  び無限大へ開いた範囲を許可したことで、一次元上の範囲の論理的完全性を実
424
859
  現しました。
425
860
 
426
- これにより、レンジの有効性を厳密に定義しています。それに従って、数個の
427
- メソッドを Range及び(自然に)そのサブクラスに追加しました。なかでも特徴的なのが、
428
- {Range#valid?} {Range#empty?} です。
861
+ このライブラリの最大の利点は、FloatやRationalのような数で応用場面の多
862
+ い複数レンジの論理演算が可能になったことです。
863
+ Gem {Rangeary}[https://rubygems.org/gems/rangeary] は本ライブラリをフ
864
+ ルに用いて、レンジの論理演算の概念を実現しています。そのためには、無限
865
+ に開いた可能性がありまた始端と終端のいずれもが除外されている可能性がある
866
+ レンジの概念が不可欠でした。たとえば、
867
+ レンジ +(?a..?d)+ の否定(あるいは補集合)が2つのレンジ +(-"Infinity-Character"...3)+ と
868
+ +(?d(exclusive).."Infinity-Character")+ であり、その否定が元の
869
+ +(?a..?d)+ になります。このような演算は、本 +RangeExtd+ クラスを用いる
870
+ ことで初めて可能になります。
871
+
872
+ Rangeary: {https://rubygems.org/gems/rangeary}
873
+
874
+ 組込 Rangeは大変有用なクラスであり、Rubyユーザーに容易なプログラミングを可能にす
875
+ るツールでした。しかし、始点を除外することができないのが玉に瑕でありました。
876
+
877
+ ただし、それにはれっきとした理由があることは分かります。Rubyの Rangeは、Numeric
878
+ (厳密にはその実数を表現したもの)だけに限ったものではありません。 <tt>succ()</tt> メソッ
879
+ ドを持つオブジェクトによる Rangeは極めて有用です。一方、<tt>succ()</tt> の逆に相
880
+ 当するメソッドは一般的には定義されていません。そういう意味で、Rangeは本質的に非
881
+ 対称です。加えて、よく使われる Rangeオブジェクトのうちあるもの(たとえば Float)は
882
+ 連続的なのに対し、そうでないものも普通です(たとえば Integer や String)。この状況
883
+ が厳密な定義をする時の混乱に拍車をかけています。
884
+
885
+ ここで始点を除外可能としたことは、そういう意味で、道筋が100パーセント明らかなも
886
+ のではありませんでした。ここで私が採用した {RangeExtd}クラスの定義は、おそらく、考え
887
+ られる唯一のものではないでしょう。とはいえ、個人的には満足のいくものに仕上がりま
888
+ したし、このレンジという枠内での論理的完全性をうまく達成できたと思います。
889
+
890
+ このクラスが少なからぬ人に有用なものであることを願ってここにリリースします。
891
+
892
+ === Rangeの正当性
893
+
894
+ Rubyの組込みRangeは、メンバーに許されるものに対してとても慣用です。た
895
+ とえば、+(true...true)+ は、それが何を意味するのかはともかく、完全に正
896
+ 当なRangeです。もっとも、イテレーターを伴う+each+や+to_a+ といったメソッ
897
+ ドを使おうとすると例外(+TypeError+)が発生ますし、利用価値はごく限られ
898
+ るでしょうが。
899
+
900
+ 本ライブラリにより、Rangeの「正当性」が厳密に定義されます。本ライブラ
901
+ リは、Rangeクラスに{Range#valid?}, {Range#null?} and {Range#empty?}を
902
+ はじめとするいつくつかのメソッドを追加します。それらはもちろん、すべて
903
+ の子クラスにも継承されます。
904
+
905
+ 一例として、 <tt>(3...3).valid?</tt> は偽(false)を返します。なぜならば、
906
+ 要素3は始端では含まれるのに終端では除外されているため、相互に矛盾してい
907
+ るからです。このRangeExtd クラスでは、次の2つが正当なレンジと見做され
908
+ ます。
429
909
 
430
- たとえば、<tt>(3...3).valid?</tt> は偽を返します。要素の 3 が、始点と
431
- しては含まれているのに対し、終点としては除外されていて、これは相互に矛
432
- 盾しているためです。ここで導入する RangeExtdクラスにおいては、以下のよ
433
- うにこれが有効なレンジとして定義できます。
910
+ * RangeExtd.new(3, 3, true, true) # => an empty range
911
+ * RangeExtd.new(3, 3, false, false) # => a single-point range (3..3)
434
912
 
435
- * RangeExtd.new(3, 3, true, true) # => 空レンジ
436
- * RangeExtd.new(3, 3, false, false) # => 一点レンジ (3..3)
913
+ ただし、もし組込みRangeに閉じて使う限りは、何も変わりません。つまり、
914
+ 標準Rubyと完全に互換性を保っています。
437
915
 
438
- しかしながら、組込Rangeの範囲内に収まっている限り、何も変わっていませ
439
- ん。つまり、標準の Rubyとの完全な後方互換性を実現しています。
916
+ === 無限に開いたレンジ
440
917
 
441
- 無限に開いたレンジを表すのは簡単です。単に {RangeExtd::Infinity}クラスで
918
+ Ruby 2.6 と 2.7 でそれぞれ終端および始端のないRangeが導入されました。
919
+ これら境界のないRangeと本ライブラリの無限に開いたRangeとの違いは
920
+ 少々難解で、現実の場面で実用的というよりは、概念的哲学的なものといって
921
+ いいでしょう(詳しくは「背景」の章を参照)。しかし、気にすることはありま
922
+ せん。実用という意味では、両者は互換であり、それほど気を遣うことはあり
923
+ ません。端的には、特に不満がない限りは、組込みの境界のないRangeを使え
924
+ ばよいでしょう。
925
+
926
+ 無限に開いたレンジを表すのは以下のようにします。{RangeExtd::Infinity}クラスで
442
927
  定義されている二つの定数(無限大または無現小、あるいは無限前と無限後)の
443
928
  いずれかを用います。
444
929
 
445
930
  * RangeExtd::Infinity::NEGATIVE
446
931
  * RangeExtd::Infinity::POSITIVE
447
932
 
448
- これらは基本的に <tt>Float::INFINITY</tt> を全ての Comparableであるオ
449
- ブジェクトに一般化したものです。たとえば、
933
+ これらは基本的に <tt>Float::INFINITY</tt> を全ての Comparable
934
+ であるオブジェクトに*一般化*したものです。たとえば、
450
935
 
451
936
  ("a"..RangeExtd::Infinity::POSITIVE).each
452
937
 
453
938
  は、"a"から始まる <tt>String#succ</tt> を使った無限のイテレーターを与えます
454
939
  (だから、どこかで必ず breakするようにコードを書きましょう!)。
940
+ この例の場合、Ruby-2.6以上の以下とまったく同じように動きます。
455
941
 
456
- 組込 Rangeは大変有用なクラスであり、Rubyユーザーに容易なプログラミングを可能にす
457
- るツールでした。しかし、始点を除外することができないのが玉に瑕でありました。
942
+ ("a"..).each
458
943
 
459
- ただし、それにはれっきとした理由があることは分かります。Rubyの Rangeは、Numeric
460
- (厳密にはその実数を表現したもの)だけに限ったものではありません。 <tt>succ()</tt> メソッ
461
- ドを持つオブジェクトによる Rangeは極めて有用です。一方、<tt>succ()</tt> の逆に相
462
- 当するメソッドは一般的には定義されていません。そういう意味で、Rangeは本質的に非
463
- 対称です。加えて、よく使われる Rangeオブジェクトのうちあるもの(たとえば Float)は
464
- 連続的なのに対し、そうでないものも普通です(たとえば Integer や String)。この状況
465
- が厳密な定義をする時の混乱に拍車をかけています。
944
+ === News: Libraryの場所他
466
945
 
467
- ここで始点を除外可能としたことは、そういう意味で、道筋が100パーセント明らかなも
468
- のではありませんでした。ここで私が採用した RangeExtdクラスの定義は、おそらく、考え
469
- られる唯一のものではないでしょう。とはいえ、個人的には満足のいくものに仕上がりま
470
- したし、このレンジという枠内での論理的完全性をうまく達成できたと思います。
946
+ **重要**: ライブラリのパスが{RangeExtd} Ver.1 から Ver.2 で、
947
+ ディレクトリの階層一つ上がりました。これは、Ruby Gemの慣用にそうように
948
+ するためです。端的には、標準的方法は、+require "range_extd"+ です。
949
+ 以前のパスは、"range_extd/range_extd" でした。
471
950
 
472
- このクラスが少なからぬ人に有用なものであることを願ってここにリリースします。
951
+ それに伴い、{RangeExtd} のバージョンを2.0にあげました。
473
952
 
953
+ 以下が、その主な変更点です。詳しくは、「履歴メモ」章を参照ください。
474
954
 
475
- === News: Endless Range サポートしました
955
+ ==== News: Beginless Range サポートしました
476
956
 
477
- 2019年10月より、本パッケージは、Ruby 2.6 で導入された {Endless Range}[https://rubyreferences.github.io/rubychanges/2.6.html#endless-range-1]
478
- (終端のない Range)を正式サポートしました。よって、Version 1.0 をリリースしました!
957
+ Ruby 2.7 で始端のない {Beginless range}[https://rubyreferences.github.io/rubychanges/2.7.html#beginless-range]
958
+ がサポートされました。
959
+ +RangeExtd+ も今やサポートします。この影響で、仕様に重要な変更があります。
960
+
961
+ まず、{RangeExtd::NONE} は、事実上
962
+ +RangeExtd((RangeExtd::Nowhere::NOWHERE...RangeExtd::Nowhere::NOWHERE), true)+
963
+ になりました。すなわち、両端が
964
+ {RangeExtd::Nowhere::NOWHERE} であり、
965
+ 両端({RangeExtd#begin} と {RangeExtd#end})とも除外されています。
966
+ 以前のバージョンでは、{RangeExtd::NONE} の両端は +nil+ でした。
967
+ Range +(nil..nil)+ は、Ruby-2.6 およびそれ以前ではそもそも許されていな
968
+ くて、そのために{RangeExtd::NONE}に独特な表記として幸便だったものです。
969
+ しかし、Ruby-2.7 以降では nil から nil のRangeが許容されます。つまり、
970
+ +(nil..nil)+ は完全に正当であり、それは {RangeExtd::NONE} を表していた
971
+ +RangeExtd((nil...nil), true)+ と事実上同じになってしまいます。前者は、
972
+ 後者とはまったく異なるオブジェクトであり、(除外フラグをのぞけば)むしろ
973
+ {RangeExtd::ALL} に極めて近いにも拘らずです。だから、変更が必要になったのです。
974
+
975
+ もっとも、この変更は概念的なものであり、ユーザー視点ではほぼ変更は見えません。
976
+ {RangeExtd::NONE}の始端と終端である {RangeExtd::Nowhere::NOWHERE} は、
977
+ {NilClass} とほぼまったく同じように振る舞います(以下の「詳説」章を参照)。
978
+ {RangeExtd} Ver.1以前でその値はまさに +nil+ だったことを考えれば、
979
+ Ver.2で{RangeExtd::Nowhere::NOWHERE} が{RangeExtd::NONE}
980
+ に使われるようになったと言っても、今まで動いていたコードには何の変更も必要ないはずです。
981
+ オブジェクトが{RangeExtd::NONE} かどうかをチェックする推奨方法は、
982
+ 今までもずっとそうだったように、{RangeExtd#is_none?} です
983
+ (+Enumerable#none?+とは異なるのでご注意)。実用的には、
984
+ {Range#null?} がユーザーが希望する挙動であることが大半でしょう
985
+ (注: +(true..true).null?+ は偽(+false+)を返すことに注意。
986
+ 本マニュアルの「詳説」章の「RangeExtd Class」を参照)。
987
+
988
+ 次に、+RangeExtd.valid?(nil..)+ は、真(+true+)を返すようになりました。
989
+ 以前は、偽を返していました。そしてそれは {RangeExtd::ALL} に等しいです。
990
+
991
+ たとえば、+"abc"[nil..]+ は以前のバージョンでは不正もしくは文法エラー
992
+ さえ出ていましたが、Ruby-2.7以降では完全に正当なRuby表現です。
993
+ したがって、もし仮に+RangeExtd+ がそれらを正当でないと見做したならば、
994
+ 不自然に受け取られるでしょう。
479
995
 
480
- ==== 注: Rangearyとの関係
996
+ +RangeExtd.valid?(true..)+ は、依然 +false+ を返します。
481
997
 
482
- 同クラス(典型的にはFloat)のオブジェクトからなる複数のRangeを扱うクラス
483
- {Rangeary}[https://rubygems.org/gems/rangeary] は、本ライブラリを使い
484
- 切っています。Rangeを実現するためには、始端と終端との両方で開いた可能
485
- 性があるRangeを扱うことが必須だからです。例えば、
486
- Range +(?a..?d)+ の否定は、複数Range +(-"Infinity(文字)"...3)+ と
487
- +(?d(始端除外).."Infinity(文字)")+ であり、その否定は、元の +(?a..?d)+
488
- です。このような演算は、+RangeExtd+ があって初めて可能になります。
998
+ 他の大きな変更には以下があります。
489
999
 
490
- Rangeary: https://rubygems.org/gems/rangeary
1000
+ * +RangeExtd::Infinity#succ+ はFloatクラスに合わせて未定義になりました。
1001
+ * +Object+ と +Numeric+ クラスの拡張はデフォルトではなく、オプション(ユーザーの選択)となりました。
1002
+ * +RangeExtd#eql?+ はRubyの標準([#hash]値を比較)にそうようにし、今まであった{RangeExtd::NONE}との特別な比較ルーチンを削除しました。
1003
+ * +RangeExtd#min_by+ (+max_by+ と +minmax_by+) のバグを修正しました。
1004
+
1005
+ ==== News: Endless Range サポートしました
1006
+
1007
+ 2019年10月より、本パッケージは、Ruby 2.6 で導入された {Endless Range}[https://rubyreferences.github.io/rubychanges/2.6.html#endless-range-1]
1008
+ (終端のない Range)を正式サポートしました。よって、Version 1.0 をリリースしました!
1009
+
1010
+ Ruby 2.7 では、{Beginless range}[https://rubyreferences.github.io/rubychanges/2.7.html#beginless-range] が導入されました.
491
1011
 
492
1012
  ==== 注: Rangesmallerとの関係
493
1013
 
494
1014
  このパッケージは、(今やサポートされていない) {Rangesmaller}[https://rubygems.org/gems/rangesmaller] パッケージ及びクラスを
495
1015
  後継するものです。同クラスの機能に、無限に開いた範囲を許す機能が加わり、また、オ
496
1016
  ブジェクト生成時のインターフェースが変更されています。
497
- https://rubygems.org/gems/rangesmaller
1017
+ {https://rubygems.org/gems/rangesmaller}
1018
+
1019
+
1020
+ == 背景
1021
+
1022
+ === Endless Range と Beginless Range
1023
+
1024
+ {Endless Range}[https://rubyreferences.github.io/rubychanges/2.6.html#endless-range-1]
1025
+ (終端のないRange)と
1026
+ {Beginless Range}[https://rubyreferences.github.io/rubychanges/2.7.html#beginless-range]
1027
+ (始端のないRange)はそれぞれ
1028
+ 2018年12月および2019年12月リリースの Ruby 2.6 と 2.7 で導入されました。
1029
+
1030
+ そのおかげで、Rubyの組込み+Range+ は、+RangeExtd+ が提供していた機能の
1031
+ いくつかを持つようになりました。
1032
+ ただし、今でも、明快なものも微妙なものも含めていくつかの違いがあります。
1033
+
1034
+ 本ライブラリのはっきりとした利点は、+exclude_begin?+ (つまり始端を除外する)機能です。
1035
+
1036
+ 境界のないRangeについては、本ライブラリが提供するものは、一言で言えば、
1037
+ 抽象的な意味で無限に開いたレンジです。
1038
+ 概念的な主な違いは、Rubyの組込み+Range+ は*未定義*の境界を表すのに対し、
1039
+ +RangeExtd+ は*無限に開いた*境界を表します。
1040
+
1041
+ この違いは微妙ながら、はっきりとした意味があります。
1042
+ 以下に、標準Rubyの特にNumericのRangeを例示します。というのも、Numeric
1043
+ はこの両方を提供しているために、違いがわかりやすいのです。
1044
+
1045
+ "abcdef"[..2] # => "abc"
1046
+ "abcdef"[..2.0] # => "abc"
1047
+ "abcdef"[(-Float::INFINITY)..2] # raise (RangeError)
1048
+ "abcdef"[(-1)..2] # => ""
1049
+ "abcdef"[(-6)..2] # => "abc"
1050
+ "abcdef"[(-7)..2] # => nil
1051
+ (-Float::INFINITY..5).first(1) # raise: can't iterate from Float (TypeError)
1052
+ (-Float::INFINITY..5).first # => -Infinity
1053
+ (-Float::INFINITY..5).begin # => -Infinity
1054
+ (..5).first # raise: cannot get the first element of beginless range (RangeError)
1055
+ (..5).begin # => nil
1056
+
1057
+ 最初、そして2番目の式に出てくるのが始端のないRangeで、始端が未定義です。
1058
+ Stringクラスは、その「始点の値」を0だと*解釈*しています。
1059
+ 対照的に、3番目の式では、例外が発生しています。この仕様は、
1060
+ 始点の値が定義されていて、でも負の無限大だから、と考えれば、理解できます。
1061
+ 実際、Stringの場合、負の数の添字は、(4番目、6番目の例にあるように)
1062
+ 特別な意味を持っていますからね。
1063
+
1064
+ 最後の5つの例は、興味深いです。
1065
+ +Range#begin+ は単純に始点の値を返します。
1066
+ +Range#first+ は引数が与えられなければ、最初の「要素」を返します。
1067
+ +(Float::INFINITY..5)+ には最初の要素があるため、それが返されます。
1068
+ しかしbeginless {Range} では話が異なります。定義された最初の要素がないため、
1069
+ +Range#first+ は、+RangeError+ 例外を発生させます。対照的に、
1070
+ 引数 +n+ が +Range#first+ に与えられた時は、+n+個の要素を持つ配列
1071
+ (Array)が返されなければなりません。Floatから値を数えるのは未定義であるため、
1072
+ 負の無限大からのRangeの場合は、+TypeError+ 例外が発生します。
1073
+ 筋が通っていると思いませんか?
1074
+
1075
+ ところで、補足すると、+(5..8.6).last(2)+ は正当であって配列 +[7, 8]+
1076
+ を返します。また、+(2.2..8.6).size+ も(なぜか?)正当です。混乱しますね……。
1077
+
1078
+ 別のポイントとして、無限大には明快な数学的定義がありますが、
1079
+ すべてのRangeがそれを認めるわけではありません。たとえば、
1080
+ 自作クラスで、アルファベット小文字1文字だけを持ち、
1081
+ Stringクラスと同様にRangeが定義できる例を考えてみます。
1082
+ すると、最小の始端と最大の終端は、それぞれ"+a+"と"+z+"です。
1083
+ この場合、「無限大」(あるいは無限小)とは何を意味するでしょうか?
1084
+ 厳密な意味では、それは不正とすべきでしょうか。あるいは、
1085
+ この場合の無限大は"+z+"を表す数と解釈すべきでしょうか?
1086
+ それとも別のなにか?
1087
+
1088
+ 概念としては、包括的な解釈のほうが便利です。実際、
1089
+ {Rangeary}[https://rubygems.org/gems/rangeary] ライブラリは、
1090
+ +RangeExtd+ をそのように利用しています。すなわち、
1091
+ Range の「否定」(補集合)を積極的に用いて、複数Ranges
1092
+ の論理演算を実現しています。しかし、
1093
+ すべてのアプリケーションにそう解釈することを強要することはできません。
1094
+
1095
+ まとめると、*未定義*境界は定義上未定義であり、その解釈はアプリケーション任せになるのに対し、
1096
+ 正負の無限大境界は明快な定義はあるかも知れないけれど、実際の応用では柔軟な解釈が望ましい場合もあるかもしれない、
1097
+ というところです。
1098
+
1099
+ これらを考慮し、本ライブラリの
1100
+ {RangeExtd::Infinity::NEGATIVE} と {RangeExtd::Infinity::POSITIVE}
1101
+ とは、事実上 +nil+ のように振る舞うようにデザインされています。ただし、
1102
+ ユーザーが別扱いすることは可能です。
1103
+
1104
+ === endless and beginless Rangesの振舞い
1105
+
1106
+ 組込み Endless/Beginless Range の振舞いは幾分混乱するところがあります。
1107
+ 加えて、+Range#size+にはバグが複数あるようです
1108
+ ({Bug #18983}[https://bugs.ruby-lang.org/issues/18983] と
1109
+ {Bug #18993}[https://bugs.ruby-lang.org/issues/18993])。
1110
+ 少なくとも、公式マニュアルに記載されている仕様とは矛盾する振舞いがあり、
1111
+ 混乱に拍車をかけます。
1112
+
1113
+ Rubyの実装では、beginless/endless Rangesの始端と終端の値は、
1114
+ +nil+ と解釈されます。 Rubyでは +nil == nil+ が真であるために、
1115
+
1116
+ (?a..).end == (5..).end
1117
+
1118
+ も真です。一方、
1119
+
1120
+ (?a..).end == (5..Float::INFINITY).end
1121
+
1122
+ は偽(+false+)です。以下が幅広い例です。
1123
+
1124
+ (-Float::INFINITY..Float::INFINITY).size # => Infinity
1125
+ ( Float::INFINITY..Float::INFINITY).size # raises FloatDomainError
1126
+ num1 = (5..Float::INFINITY)
1127
+ num2 = (5..)
1128
+ num1.end != num2.end # => true
1129
+ num1.size # => Infinity
1130
+ num2.size # => Infinity
1131
+
1132
+ str1 = (?a..)
1133
+ str1.end != num1.end # => true
1134
+ str1.end == num2.end # => true (because both are nil)
1135
+ str1.size # => nil (because Range#size is defined for Numeric only)
1136
+ (..?z).size # => Infinity (contradicting the specificatin?)
1137
+
1138
+ (..3).to_s => "..3"
1139
+ (3..).to_s => "3.."
1140
+ (3..nil).to_s => "3.."
1141
+ (nil..3).to_s => "..3"
1142
+
1143
+ (nil..) == (..nil) # => true
1144
+ (nil..) != (...nil) # => true (because exclude_end? differ)
1145
+ "abcdef"[..nil] # => "abcdef" (i.e., it is interpreted as (0..IntegerInfinity)
1146
+ # (n.b., nil.to_i==0; Integer(nil) #=> TypeError))
1147
+ "abcdef"[..?a] # raise: no implicit conversion of String into Integer (TypeError)
1148
+ "abcdef"[0..100] # => "abcdef"
1149
+ "abcdef"[-100..100] # => nil
1150
+
1151
+ (..nil).size # => Float::INFINITY
1152
+
1153
+ (..nil).begin # => nil
1154
+ (..nil).first # raise: cannot get the first element of beginless range (RangeError)
1155
+ (..nil).last # raise: cannot get the last element of endless range (RangeError)
1156
+ (..nil).end # => nil
1157
+
1158
+ (..nil).cover? 5 # => true
1159
+ (..nil).cover? ?a # => true
1160
+ (..nil).cover? [?a] # => true
1161
+ (..nil).cover? nil # => true
1162
+
1163
+ Integerクラスならば、
1164
+
1165
+ num1 = (5..Float::INFINITY)
1166
+ num2 = (5..)
1167
+ num1.end != num2.end # => true (because (Float::INFINITY != nil))
1168
+ num1.size # => Float::INFINITY
1169
+ num2.size # => Float::INFINITY
1170
+
1171
+ (3...) == (3...nil) # => true
1172
+ (3..) != (3...nil) # => true (because exclude_end? differ)
1173
+
1174
+ (3..).size # => Float::INFINITY
1175
+ (..3).begin # => nil
1176
+ (..3).first # raise: cannot get the first element of beginless range (RangeError)
1177
+ (3..).last # raise: cannot get the last element of endless range (RangeError)
1178
+ (3..).end # => nil
1179
+ (..3).each{} # raise: `each': can't iterate from NilClass (TypeError)
1180
+ (..3).to_a # raise: `each': can't iterate from NilClass (TypeError)
1181
+ (3..).to_a # raise: `to_a': cannot convert endless range to an array (RangeError)
1182
+ (3..Float::INFINITY).to_a # Infinite loop!
1183
+
1184
+ (-Float::INFINITY..4).first # => -Float::INFINITY
1185
+ (4..Float::INFINITY).last # => Float::INFINITY
1186
+ (-Float::INFINITY..4).first(2) # raise: can't iterate from Float (TypeError)
1187
+ (4..Float::INFINITY).last(2) # Infinite loop!
1188
+
1189
+ Stringクラス(あるいはユーザー定義クラス?)ならば、
1190
+
1191
+ (?a..).end == (5..).end # => true (because both are nil)
1192
+ (?a..).end != (5..Float::INFINITY).end # => true
1193
+ (..?a).begin == (..5).begin # => true (because both are nil)
1194
+ (..?a).begin != ((-Float::INFINITY)..5).begin # => true
1195
+ (..?a).size # => Float::INFINITY
1196
+ (?a..).size # => nil
1197
+
1198
+ (..?a).begin # => nil
1199
+ (..?a).first # raise: cannot get the first element of beginless range (RangeError)
1200
+ (?a..).last # raise: cannot get the last element of endless range (RangeError)
1201
+ (?a..).end # => nil
1202
+ (..?a).each{} # raise: `each': can't iterate from NilClass (TypeError)
1203
+ (..?a).to_a # raise: `each': can't iterate from NilClass (TypeError)
1204
+ (?a..).to_a # raise: `to_a': cannot convert endless range to an array (RangeError)
1205
+ (?a..Float::INFINITY).to_a # raise: bad value for range (ArgumentError) # b/c it is not String!
1206
+
1207
+ === Range#size についての注記
1208
+
1209
+ +Range#size+ の振舞いはとてもわかりにくいです。
1210
+ {公式マニュアル}[https://ruby-doc.org/core-3.1.2/Range.html#method-i-size] によれば、
1211
+
1212
+ Returns the count of elements in self if both begin and end values are numeric;
1213
+ otherwise, returns nil
1214
+
1215
+ しかし、実際のRubyの挙動は必ずしもこの通りではありません(上述の例参照)。
1216
+ 加えて、一般のNumeric に対して"elements"が一体何かは不明瞭です。
1217
+ だから、Stringならば必ずnilが買える
1218
+ 以下が一例です({Bug #18993}[https://bugs.ruby-lang.org/issues/18993] として報告済):
1219
+
1220
+ (5.quo(3)...5).size # => 3
1221
+ (5.quo(3).to_f...5).size # => 4
1222
+ (5.quo(3)..5).size # => 4
1223
+ (5.quo(3).to_f..5).size # => 4
1224
+
1225
+ === Range#count についての注記
1226
+
1227
+ +Range#count+ の振舞いの大半は理解できます。しかし、
1228
+ 境界のないものや無限大関係は自明ではありません。
1229
+
1230
+ (5..).count # => Float::INFINITY
1231
+ (..5).count # => Float::INFINITY
1232
+ (..nil).count # => Float::INFINITY
1233
+ (-Float::INFINITY..nil) # => Float::INFINITY
1234
+ (-Float::INFINITY..Float::INFINITY).count # raises (TypeError) "can't iterate from Float"
1235
+ (..5).count(4) # raises (TypeError)
1236
+ (..5).count{|i| i<3} # raises (TypeError)
1237
+ (1..).count(4) # infinite loop!
1238
+ (1..).count{|i| i<3} # infinite loop!
1239
+
1240
+ 端的には、一部の特別なケースについては、同メソッドは Infinity (無限大)を返します。
1241
+
1242
+ これを考慮して本ライブラリの+RangeExtd::ALL.count+ は、特別なケースとして、
1243
+ returns +Float::INFINITY+ を返します。
1244
+
498
1245
 
499
1246
  == インストール
500
1247
 
501
1248
  gem install range_extd
502
1249
 
503
- により、ファイルが 2個、
1250
+ により、
504
1251
 
505
- range_extd/range_extd.rb
506
- range_extd/infinity/infinity.rb
1252
+ range_extd.rb
1253
+ range_extd/infinity.rb
507
1254
 
508
- <tt>$LOAD_PATH</tt> の一カ所にインストールされるはずです。
1255
+ をはじめとした数個のファイルが<tt>$LOAD_PATH</tt> の一カ所にインストールされるはずです。
509
1256
 
510
- あるいは、パッケージを以下から入手できます。
511
-
512
- http://rubygems.org/gems/range_extd
1257
+ あるいは、パッケージを{http://rubygems.org/gems/range_extd}から入手できます。
513
1258
 
514
1259
  後は、Ruby のコード(又は irb)から
515
1260
 
516
- require 'range_extd/range_extd'
517
-
518
- とするだけです。もしくは、特に手でインストールした場合は、
1261
+ require "range_extd/load_all"
519
1262
 
520
- require 'range_extd'
1263
+ とするだけです。もしくは、本ライブラリのの最小限セットだけ使う場合は、
521
1264
 
522
- とする必要があるかも知れません。もう一方のファイル
1265
+ require "range_extd"
523
1266
 
524
- range_extd/infinity/infinity.rb
1267
+ でもいいです。
1268
+ 端的には "+range_extd/load_all.rb+" は、ラッパーであり、以下のファイルを読み込みます:
525
1269
 
526
- は、自動的に読み込まれます。
1270
+ require "range_extd"
1271
+ require "range_extd/numeric"
1272
+ require "range_extd/object"
1273
+ require "range_extd/infinity"
1274
+ require "range_extd/nowhere"
1275
+ require "range_extd/range"
1276
+ require "range_extd/nil_class"
527
1277
 
528
- お楽しみあれ!
1278
+ このうち、最初の3つは独立で、下の4つは一番上のファイルと必ず一緒に使われるもので、最初のファイルを読めば自動的に読み込まれます。
529
1279
 
1280
+ 2番目と3番目のファイルは、ユーティリティライブラリです。読み込めば、
1281
+ Ruby組込みクラスの +Object+ と +Numeric+ (+Float+ と +Integer+を含む)
1282
+ にいくつかのメソッドが追加されたり機能が追加されます。
1283
+ 追加された機能はすべて後方互換であり、単に既存のクラスに機能を追加するだけです。
1284
+ これらの読み込みを強く推奨します。もし読み込まない場合は、本ライブラリ
1285
+ のパワーがごく限られてしまいます。たとえば、比較演算子+<=>+
1286
+ が可換でないため、驚くような挙動になることがあるでしょう。
1287
+ 具体的な追加機能はそれぞれのマニュアルを参照ください。
530
1288
 
531
1289
  == 単純な使用例
532
1290
 
1291
+ 以下の例では、ライブラリのすべてのファイルが読み込まれている(require)
1292
+ と仮定します。
1293
+
533
1294
  === RangeExtd インスタンスを作成する方法
534
1295
 
535
1296
  以下に幾つかの基本的な使用例を列挙します。
536
1297
 
1298
+ require "range_extd/load_all"
537
1299
  r = RangeExtd(?a...?d, true) # => a<...d
538
1300
  r.exclude_begin? # => true
539
1301
  r.to_a # => ["b", "c"]
@@ -545,7 +1307,8 @@ https://rubygems.org/gems/rangesmaller
545
1307
  (RangeExtd::Infinity::NEGATIVE..RangeExtd::Infinity::POSITIVE) \
546
1308
  == RangeExtd::ALL # => true
547
1309
 
548
- インスタンスを作成するのには、三通りあります。
1310
+ +RangeExtd+ のインスタンスを作成する方法が3通りあります(おそらく
1311
+ 最初のやり方が最も単純でタイプ量が少なく、かつ覚えやすいでしょう)。
549
1312
 
550
1313
  RangeExtd(range, [exclude_begin=false, [exclude_end=false]], opts)
551
1314
  RangeExtd(obj_begin, obj_end, [exclude_begin=false, [exclude_end=false]], opts)
@@ -553,7 +1316,7 @@ https://rubygems.org/gems/rangesmaller
553
1316
 
554
1317
  大括弧の中の二つのパラメーターが、それぞれ始点と終点とを除外する(true)、または含む
555
1318
  (false)を指示します。もし、その二つのパラメーターが最初のパラメーターのレンジ
556
- (Range or RangeExtd) と矛盾する場合は、ここで与えた二つのパラメーターが優先され
1319
+ (+Range+ または +RangeExtd+) と矛盾する場合は、ここで与えた二つのパラメーターが優先され
557
1320
  ます。同じパラメーターをオプションHash
558
1321
  (<tt>:exclude_begin</tt> と <tt>:exclude_end</tt>)で指定することもできて、
559
1322
  もし指定されればそれらが最高の優先度を持ちます。
@@ -567,36 +1330,53 @@ https://rubygems.org/gems/rangesmaller
567
1330
 
568
1331
  === 少し上級編
569
1332
 
570
- (1..RangeExtd::Infinity::POSITIVE).each do |i|
1333
+ RangeExtd((0..), true).each do |i|
571
1334
  print i
572
1335
  break if i >= 9
573
- end # => self ( "123456789" => STDOUT )
574
- (nil..nil).valid? # => false
1336
+ end # => self; "123456789" => STDOUT
1337
+ # *NOT* "012..."
1338
+ (nil..nil).valid? # => true
575
1339
  (1...1).valid? # => false
576
1340
  (1...1).null? # => true
577
1341
  RangeExtd.valid?(1...1) # => false
578
1342
  RangeExtd(1, 1, true, true).valid? # => true
579
1343
  RangeExtd(1, 1, true, true).empty? # => true
580
1344
  RangeExtd(?a, ?b, true, true).to_a? # => []
581
- RangeExtd(?a, ?b, true, true).empty? # => true
1345
+ RangeExtd(?a, ?b, true, true).null? # => true (empty? is same in this case)
582
1346
  RangeExtd(?a, ?e, true, true).to_a? # => ["b", "c", "d"]
583
- RangeExtd(?a, ?e, true, true).empty? # => false
1347
+ RangeExtd(?a, ?e, true, true).null? # => false
584
1348
  RangeExtd::NONE.is_none? # => true
1349
+ RangeExtd(1...1, true) == RangeExtd::NONE # => true
585
1350
  RangeExtd::ALL.is_all? # => true
1351
+ (nil..nil).is_all? # => false
1352
+ (-Float::INFINITY..Float::INFINITY).is_all? # => false
1353
+ (nil..nil).equiv_all? # => true
1354
+ (-Float::INFINITY..Float::INFINITY).equiv_all? # => true
586
1355
  (3...7).equiv?(3..6) # => true
1356
+ (nil..nil).equiv?(RangeExtd::ALL) # => true
587
1357
 
588
- 組込Rangeに含まれる全てのメソッドが使用可能です。
1358
+ 組込Rangeに含まれる全てのメソッドが、(子クラスである){RangeExtd}で使用可能です。
589
1359
 
590
1360
 
591
1361
  == 詳説
592
1362
 
593
- ファイル +range_extd/range_extd.rb+ が読まれた段階で、次の二つのクラスが定義されます。
1363
+ ファイル +range_extd.rb+ が読まれた段階で、次の3つのクラスが定義されます。
594
1364
 
595
1365
  * RangeExtd
596
1366
  * RangeExtd::Infinity
1367
+ * RangeExtd::Nowhere
1368
+
1369
+ 加えて、{Range} クラスと {NilClass}に数個のメソッドが追加また改訂されます。
1370
+ これらに加えられる改訂は、全て後方互換性を保っています。
597
1371
 
598
- 加えて、Range クラスに数個のメソッドが追加また改訂されます。Rangeクラスに加えら
599
- れる改変は、全て後方互換性を保っています。
1372
+ この時、{Range} の改訂は、原理的には{RangeExtd}と分離可能だと思います
1373
+ (分離したい人がいるとは思えませんが!)が、{NilClass} の方は不可避です。
1374
+ というのも、それなしには{RangeExtd::NONE}が定義不可能だからです。
1375
+ 具体的には、初期化の時に+ArgumentError+ (bad value for range)
1376
+ の例外が出てしまいます。Rubyの組込みのRangeの仕様のためです。
1377
+
1378
+ {Stackoverflow上の議論}[https://stackoverflow.com/a/14449380/3577922]
1379
+ を参考にあげておきます。
600
1380
 
601
1381
  === RangeExtd::Infinity クラス
602
1382
 
@@ -606,42 +1386,54 @@ https://rubygems.org/gems/rangesmaller
606
1386
  * RangeExtd::Infinity::POSITIVE
607
1387
 
608
1388
  これらは、 <tt>Float::INFINITY</tt> を全ての Comparable なオブジェクトに一般化し
609
- たものです。メソッド <tt><=></tt> と <tt>succ</tt> が定義されています。
1389
+ たものです。メソッド <tt><=></tt>が定義されています。
610
1390
 
611
1391
  これらは、他のオブジェクトと同様に普通に使用可能です。たとえば、
612
- ("k"..RangeExtd::Infinity::POSITIVE)
1392
+
1393
+ (RangeExtd::Infinity::NEGATIVE.."k")
1394
+
613
1395
  とはいえ、他には何もメソッドを持っていないため、 Range型のクラスの中以外での使用
614
1396
  はおそらく意味がないでしょう。
615
1397
 
616
1398
  なお、Numericのオブジェクトに対しては、原則として <tt>Float::INFINITY</tt> の方
617
1399
  を使って下さい。
618
1400
 
619
- ユーザー定義のどの Comparable なクラスに属するどのオブジェクトも、これら二定数と
620
- 可換的に比較可能です。その際、同クラスに置ける比較メソッドがマナー良く書かれてあ
621
- る、という前提で。
1401
+ ユーザー定義のどの Comparable なクラスに属するどのオブジェクトも、比較
1402
+ 演算子が*標準的な方法で*実装されているという条件付きで、これら二定数と
1403
+ 可換的に比較可能です。「標準的」とは自分の知らないオブジェクトと比較す
1404
+ る際には、上位クラス、究極的には+Object+クラスに判断を委譲する、という
1405
+ 意味です。
622
1406
 
623
1407
  さらに詳しくは、マニュアルを参照して下さい(YARD または RDoc形式で書かれた文書が
624
1408
  コード内部に埋込まれていますし、{RubyGemsのウェブサイト}[http://rubygems.org/gems/range_extd]でも閲覧できます。
625
1409
 
626
- **(注)** +RangeExtd::Infinity::POSITIVE+ は、
627
- 2018年12月に公式リリースされたRuby 2.6で導入された
628
- {Endless Range}[https://rubyreferences.github.io/rubychanges/2.6.html#endless-range-1]
629
- (終端のないRange)で実用上同一です!! 言葉を替えれば、公式Rubyがついに本
630
- ライブラリの一部をサポートしました! ただし、公式Rubyには、
631
- +RangeExtd::Infinity::NEGATIVE+ は依然ありません(始端のないRangeがない)。
632
- 本ライブラリにより、組込Rangeに欠けている数学的不完全性を補うことができます。
1410
+ === RangeExtd::Nowhere クラス
1411
+
1412
+ {RangeExtd::Nowhere} は{NilClass}のように振舞うシングルトンクラスです。
1413
+ 唯一のインスタンスが
1414
+
1415
+ * RangeExtd::Nowhere::NOWHERE
1416
+
1417
+ として定義されています。このインスタンスは、たとえば +nil?+ に真を返し、
1418
+ また+nil+ と同じ object-ID を +object_id+ で返し、nil と等しい(+==+)
1419
+ です。これは、{RangeExtd::NONE} を構成するために使われます。
633
1420
 
1421
+ なお、Rubyの条件文では、このインスタンスは真(true)であり、偽(false)
1422
+ ではありません。
1423
+
1424
+ また、{RangeExtd::NONE} を除き、{RangeExtd::Nowhere::NOWHERE} を含む
1425
+ Range は、"valid"では*ない*と判断されます(後述)。
634
1426
 
635
1427
  === RangeExtd クラス
636
1428
 
637
- RangeExtd のインスタンスは、 Rangeと同じくイミュータブルです。だから、一度インス
638
- タンスが生成されると、変化しません。
1429
+ {RangeExtd} のインスタンスは、 {Range}と同じくイミュータブルです。だから、一度
1430
+ インスタンスが生成されると、変化しません。
639
1431
 
640
- インスタンスの生成方法は上述の通りです(「使用例」の章)。レンジとして"valid"(後述)と見
641
- なされないインスタンスを生成しようとすると、例外(<tt>ArgumentError</tt>)が発生し、
1432
+ インスタンスの生成方法は上述の通りです(「使用例」の章)。レンジとして"valid"(後述)
1433
+ 見なされない{RangeExtd} インスタンスを生成しようとすると、例外(<tt>ArgumentError</tt>)が発生し、
642
1434
  失敗します。
643
1435
 
644
- このクラスには、二つの定数が定義されています。
1436
+ このクラスには、2つの定数が定義されています。
645
1437
 
646
1438
  * RangeExtd::NONE
647
1439
  * RangeExtd::ALL
@@ -656,7 +1448,7 @@ RangeExtd のインスタンスは、 Rangeと同じくイミュータブルで
656
1448
  * <tt>valid?</tt>
657
1449
  * <tt>empty?</tt>
658
1450
  * <tt>null?</tt>
659
- * <tt>is_none?</tt>
1451
+ * <tt>is_none?</tt>
660
1452
  * <tt>is_all?</tt>
661
1453
  * <tt>equiv?</tt>
662
1454
 
@@ -667,24 +1459,37 @@ RangeExtd のインスタンスは、 Rangeと同じくイミュータブルで
667
1459
  * <tt>RangeExtd.middle_strings=(ary)</tt>
668
1460
  * <tt>RangeExtd.middle_strings</tt>
669
1461
 
670
- 何がレンジとして有効 (<tt>#valid?</tt> => true) かの定義は以下です。
1462
+ ==== 正当性、空かどうか、ヌルかどうかについての詳説
1463
+
1464
+ 何がレンジとして正当または有効 (<tt>#valid?</tt> => true) かの定義は以下です。
671
1465
 
672
1466
  1. 始点と終点とが互いに Comparable であり、かつその比較結果に矛盾がないこと。
673
- この唯一の例外は {RangeExtd::NONE} で、これは valid です。
674
- たとえば、<tt>(nil..nil)</tt> valid では「ありません」(参考までに、この例は
1467
+ この例外が3つあって、{RangeExtd::NONE}、(Ruby-2.7/2.6で導入された)Beginless/Endless Ranges で、
1468
+ これらはすべて valid です。
1469
+ たとえば、<tt>(nil..nil)</tt> は{RangeExtd} Ver.2.0+では valid です(参考までに、この例は
675
1470
  Ruby 1.8 では例外を生じていました)。
676
- 2. 始点は終点と等しい(<tt>==</tt>)か小さくなければなりません。すなわし、
1471
+ 2. {RangeExtd::NONE} と Beginless Rangeを除き +Range#begin+ のオブジェクトはメソッド +<=+
1472
+ を持たなければなりません。ゆえに、+(true..)+のようなEndless Ranges
1473
+ (Ruby 2.6以上)はvalidでは*ありません*。
1474
+ なお、"+true+" もメソッド +<=>+ を持っているため、+<=+ メソッドによる確認が不可欠です。
1475
+ 3. 同様に、{RangeExtd::NONE} と Endless Rangeを除き +Range#end+ のオブジェクトはメソッド +<=+
1476
+ を持たなければなりません。ゆえに、+(..true)+のようなBeginless Ranges
1477
+ (Ruby 2.7以上)はvalidでは*ありません*。
1478
+ 4. 始点は終点と等しい(<tt>==</tt>)か小さくなければなりません。すなわち、
677
1479
  <tt>(begin <=> end)</tt> は、-1 または 0 を返すこと。
678
- 3. もし始点と終点とが等しい時、すなわち <tt>(begin <=> end) == 0</tt>ならば、
1480
+ 5. もし始点と終点とが等しい時、すなわち <tt>(begin <=> end) == 0</tt>ならば、
679
1481
  端を除外するかどうかのフラグは両端で一致していなければなりません。
680
1482
  すなわち、もし始点が除外ならば、終点も除外されていなくてはならず、逆も真です。
681
1483
  その一例として、 <tt>(1...1)</tt> は、"valid" では「ありません」。なぜならば
682
1484
  組込レンジでは、始点を常に含むからです。
1485
+ +RangeExtd(1...1, true)+ は validで、{RangeExtd::NONE}と等しい(<tt>==</tt>)です。
1486
+ 6. {RangeExtd::NONE} 以外で{RangeExtd::Nowhere::NOWHERE} を含むRange
1487
+ は、validでは*ありません*。
683
1488
 
684
1489
  さらなる詳細は {RangeExtd.valid?} と {Range#valid?} のマニュアルを
685
1490
  参照して下さい。
686
1491
 
687
- 何がレンジとして空(<tt>#empty?</tt> => true)かの定義は以下の通りです。
1492
+ 何がレンジとして空({Range#empty?} == +true+)かの定義は以下の通りです。
688
1493
 
689
1494
  1. レンジは、valid であること: <tt>valid?</tt> => true
690
1495
  2. もしレンジの要素が離散的であれば、すなわち始点の要素がメソッド <tt>succ</tt>
@@ -705,21 +1510,35 @@ RangeExtd のインスタンスは、 Rangeと同じくイミュータブルで
705
1510
 
706
1511
  最後、 {Range#null?} は、「<tt>empty?</tt> または "valid"でない」ことに等
707
1512
  価です。従って、 RangeExtd オブジェクトにとっては、<tt>null?</tt> は
708
- <tt>empty?</tt> に等価です。
1513
+ <tt>empty?</tt> に等価です。実用的には、ほとんどのケースにおいて、
1514
+ {Range#null?} の方が、{Range#empty?}よりも有用でしょう。
709
1515
 
710
1516
  RangeExtd と別の RangeExtd または Rangeの比較 (<tt><=></tt>) においては、これら
711
1517
  の定義が考慮されます。そのうちの幾つかは、上の「使用例」の項に示されています。
712
- さらなる詳細は {Range#==}、{RangeExtd#==} および
713
- <tt>#eql?</tt> のマニュアルを参照して下さい。
1518
+ さらなる詳細は {Range#<=>}、{RangeExtd#<=>} のマニュアルを参照して下さい。
714
1519
 
715
1520
  なお、処理が Rangeオブジェクト内部で閉じている限り、その振舞いは標準 Rubyと同一
716
1521
  で、互換性を保っています。したがって、このライブラリを読込むことで既存のコードに
717
1522
  影響を与えることは原理的にないはずです。
718
1523
 
1524
+ ==== 等価性
1525
+
1526
+ メソッド +eql?+ は、Ruby標準ではハッシュ値を比較して等価性を判断するため、
1527
+ 基本的にオブジェクトのすべてのパラメーターが一致する必要があります。
1528
+ 一方、 <tt>==</tt> はもっと大雑把な比較を行います。以下が一例。
1529
+
1530
+ RaE(0...0, true) == RaE(?a...?a, true) # => false
1531
+ RaE(0...1, true) == RaE(5...6, true) # => true
1532
+
719
1533
 
720
1534
  == 既知のバグ
721
1535
 
722
- * このライブラリは Ruby 1.8 およびそれ以前のバージョンでは動作しません。
1536
+ * {RangeExtd::Nowhere::NOWHERE} は、{RangeExtd} の文脈では使えません
1537
+ (なぜならば{Range#valid?}が偽を返す)が、ユーザーは、Ruby組込み
1538
+ {Range}の枠組み内だけで用いることは以前可能です。
1539
+ {RangeExtd::Nowhere::NOWHERE} をnil以外の値として再定義した方が良いかも?
1540
+ * このライブラリ Version 2+ は Ruby 2.6 およびそれ以前のバージョンでは動作しません。
1541
+ * このライブラリ Version 1は Ruby 1.8 およびそれ以前のバージョンでは動作しません。
723
1542
  Ruby 1.9.3 ではおそらく大丈夫でしょうが、私は試したことがありません。
724
1543
  * いくつかの極めて稀な境界条件に於ける挙動は、Rubyのバージョンごとにあ
725
1544
  る程度変化しています。例えば、Float::INFINITY 同士の比較などの挙動が
@@ -729,35 +1548,94 @@ RangeExtd と別の RangeExtd または Rangeの比較 (<tt><=></tt>) におい
729
1548
  * {RangeExtd#hash} メソッドは、ある RangeExtdオブジェに対して常に唯一で排他的な
730
1549
  数値を返すことが理論保証はされていません。ただし、現実的にそれが破られることは、まず
731
1550
  ありません。
1551
+ * +RangeExtd::NONE.inspect+ と +RangeExtd::NONE.to_s+ はいずれも "Null<...Null"
1552
+ を返すのだが、Ruby +irb+ では "nil...nil" と表示されてしまうために、
1553
+ とても紛らわしい……。
732
1554
 
733
1555
  パッケージに含まれている通り、網羅的なテストが実行されています。
734
1556
 
735
1557
 
736
1558
  == 開発項目
737
1559
 
738
- 特になし。
1560
+ * もし {RangeExtd::Infinity::POSITIVE} (と NEGATIVE) が
1561
+ ({RangeExtd::Nowhere::NOWHERE}が振舞うように)+nil+のように振る舞えば、
1562
+ 便利かも知れない。ただし、そのようなオブジェクトを含むRangeは、
1563
+ Stringクラスに対してはたとえば<tt>"abcde"[my_nil..]</tt>などで、
1564
+ 同じようには動かない。Stringクラスは、+nil+について何か厳密なチェックを行っている
1565
+ のだろう。だから、仮にそうデザインし直しても、Ruby組込みクラスとの
1566
+ 相性という意味では、使い勝手がずっと向上するということにはなりそうもない。
1567
+ * "+similar+" というようなメソッドを定義すれば有用かもしれない。たとえば、
1568
+ +(-Float::INFINITY..Float::INFINITY)+ と +(-Float::INFINITYnil...Float::INFINITY)+
1569
+ とは、無限大(無限小)を除外することが無意味であるから、数学的に完全に同一である。
1570
+ 実際、これらと無限大を含まないRange/Rangearyとの演算の結果には何も影響を
1571
+ 及ぼすことがない。
739
1572
 
740
1573
 
741
1574
  == 履歴メモ
742
1575
 
743
1576
  * <tt>((?a..?z) === "cc")</tt> は、Ruby 2.6.x 以前は false を返していたが、2.7 以降は true を返す。
1577
+ * <tt>(Float::INFINITY..Float::INFINITY).size</tt> は以前は 0を返して
1578
+ いた(少なくともRuby-2.1)が、少なくともRuby-2.6以降(Ruby 3含む)では、例外 +FloatDomainError: NaN+
1579
+ を発生する。どのバージョンで変化したのかは私は知らない。
1580
+
1581
+ === RangeExtd Ver.2
1582
+
1583
+ * {RangeExtd} Ver.2において、Ver.1から、ライブラリのパスがディレクトリ
1584
+ の階層一つ上がった。Ruby Gems の慣用にそうため。
1585
+ * Ruby-2.7で導入されたBeginless Rangeに対応。
1586
+ * +RangeExtd::Infinity#succ+ は未定義になった。Floatに合わせた。
1587
+ * +Object+ と +Numeric+ クラスの拡張はデフォルトではなく、オプション化
1588
+ * +RangeExtd#eql?+ は、Ruby標準(ハッシュ値[#hash]比較)にそうように未定化。{RangeExtd::NONE}を特別扱いすることを廃止。
1589
+ * +RangeExtd#min_by+ (+max_by+ と +minmax_by+)のバグ修正。
1590
+
1591
+ === RangeExtd Ver.1.1
1592
+
1593
+ {RangeExtd} Ver.1.1 の時点で、the +RangeExtd::Infinity+ クラスの
1594
+ インスタンスは +Float::INFINITY+ とは比較できない。
1595
+
1596
+ RangeExtd::Infinity::POSITIVE != Float::INFINITY # => true
1597
+
1598
+ 概念として、前者は後者よりもさらに一般化された概念であるから、*等しく*
1599
+ あるべきでない。詳しくは {RangeExtd::Infinity} マニュアル参照。
1600
+ Ruby 2.6以上のEndless Range の振舞いは、以下のように一部奇妙に感じるところがある。
1601
+
1602
+ num1 = (5..Float::INFINITY)
1603
+ num2 = (5..)
1604
+ num1.end != num2.end # => true
1605
+ num1.size # => Infinity
1606
+ num2.size # => Infinity
1607
+
1608
+ str1 = (?a..)
1609
+ str1.end == num2.end # => true (because both are nil)
1610
+ str1.size # => nil
1611
+
1612
+ === RangeExtd Ver.1.0
1613
+
1614
+ **(注)** +RangeExtd::Infinity::POSITIVE+ は、
1615
+ 2018年12月に公式リリースされたRuby 2.6で導入された
1616
+ {Endless Range}[https://rubyreferences.github.io/rubychanges/2.6.html#endless-range-1]
1617
+ (終端のないRange)で実用上同一です!! 言葉を替えれば、公式Rubyがついに本
1618
+ ライブラリの一部をサポートしました! ただし、公式Rubyには、
1619
+ +RangeExtd::Infinity::NEGATIVE+ は依然ありません(始端のないRangeがない)。
1620
+
744
1621
 
745
1622
  == 終わりに
746
1623
 
747
1624
  RangeExtd内部に閉じた(Rangeでなく)挙動、たとえば RangeExtd同士の比較などは、
748
- 全てユーザーにとって自然なもののはずです(と期待します?)。少なくとも、RangeExtdに
1625
+ 全てユーザーにとって自然なもののはずです(と期待します?)。少なくとも、{RangeExtd}
749
1626
  よってレンジの論理構造が完結した今、これはよく定義されかつ自己矛盾が無いものと言
750
- えましょう。ただ、端の無限に開いた、あるいは始点が除外されたレンジの挙動には、
751
- 一瞬ぎょっとするものが無くはないかも知れないことに注意して下さい。たとえば、
752
- 片端が小さい方向に無限に開いて離散的な要素を持つレンジに対してメソッド
753
- <tt>member?(obj)</tt> を実行すると、 <tt>nil</tt>が返ります。これは、無限(小)に
754
- は実質的な意味を持つ <tt>succ()</tt> メソッドが定義されていないためで、したがっ
755
- て与えられた objがレンジの要素(member)かどうかを調べることが、一般論としては理論
756
- 的に不可能だからです。これはちょっと不思議に思うかも知れませんが、それはつまり定
757
- 命の私たちには無限という概念を計り知るのが容易でない、というだけの話でしょう!
758
-
759
- 一方、RangeExtd と Range との比較は、それ以上に驚くことがあるかも知れません。こ
760
- れは、組込Rangeクラスで許容されているレンジの一部は、始点を除外することを認めた
1627
+ えましょう。
1628
+
1629
+ 以前の版のこの章では、以下のように記述していました。
1630
+
1631
+ > ただ、端の無限に開いた、あるいは始点が除外されたレンジの挙動には、一瞬ぎょっとするものが無くはないかも知れないことに注意して下さい。たとえば、片端が小さい方向に無限に開いて離散的な要素を持つレンジに対してメソッド<tt>member?(obj)</tt> を実行すると、 <tt>nil</tt>が返ります。これは、無限(小)には実質的な意味を持つ <tt>succ()</tt> メソッドが定義されていないためで、したがって与えられた objがレンジの要素(member)かどうかを調べることが、一般論としては理論的に不可能だからです。これはちょっと不思議に思うかも知れませんが、それはつまり定命の私たちには無限という概念を計り知るのが容易でない、というだけの話でしょう!
1632
+
1633
+ ところが今や、Ruby本家に"beginless Range"組込まれたことで、すべての
1634
+ Rubyプログラマーがこの概念に親しむことになりました。
1635
+ これは進化と呼びたいです。
1636
+
1637
+ とはいえ、RangeExtd と Range との比較は、時には驚きがあるかも知れません。
1638
+ これは、組込Rangeクラスで許容されているレンジの一部は、始点を除外することを認めた
761
1639
  枠組の中では、前述のように最早有効(valid)と見なされないからです。この枠組に慣れるに
762
1640
  したがって、それらが自然だと思えるようになればいいのですが。保証しますが、一旦こ
763
1641
  れに慣れてしまえば、論理的不完全さ極まる混沌とした世界、つまりは Rangeの現在の挙
@@ -766,8 +1644,6 @@ RangeExtd内部に閉じた(Rangeでなく)挙動、たとえば RangeExtd同士
766
1644
  お楽しみ下さい。
767
1645
 
768
1646
 
769
- == その他
770
-
771
1647
  == 著作権他情報
772
1648
 
773
1649
  著者:: Masa Sakano < info a_t wisebabel dot com >