Class: RangeExtd::Infinity

Inherits:
Object show all
Includes:
Comparable
Defined in:
lib/range_extd/infinity/infinity.rb,
lib/range_extd/infinity/infinity.rb

Overview

Class RangeExtd::Infinity

Authors

Masa Sakano

License

MIT

Summary

Class to hold just two constants:

  • RangeExtd::Infinity::NEGATIVE

  • RangeExtd::Infinity::POSITIVE

and two more:

  • FLOAT_INFINITY (OBSOLETE; workaround for Ruby 1.8 to represent Float::INFINITY)

  • CLASSES_ACCEPTABLE (see below)

There is no other object in this class (you can not create a new one).

This class includes Comparable module.

Description

Both the two constant are an abstract value which is always smaller/larger, respectively, than any other Comparable objects (1 or -1 by i#<=>(obj)) except for infinities with the same polarity, that is, positive or negative, in which case 0 is returned. See the document of the method #== for the definition of “infinity”. Also, #succ is defined, which just returns self.

There is a note of caution. The method #<=> is defined in this class as mentioned above. However any operator is, by Ruby’s definition, not commutative, unless both the classes define so.

There are only three built-in classes that are Comparable: String, Time and Numeric (except for Complex). Note Date and DateTime objects are so, too, however practically they need “require”, hence are (and must be) treated, the same as any other class. For String and Time class objects, the [#<=>] operator work as expected in the commutative way.

?z <=> RangeExtd::Infinity::POSITIVE    # => nil
RangeExtd::Infinity::POSITIVE <=> ?z    # => 1.

For Numeric, it does not.

50 <=> RangeExtd::Infinity::POSITIVE    # => nil
RangeExtd::Infinity::POSITIVE <=> 50    # => 1.

For that reason, for example,

( 50 .. RangeExtd::Infinity::POSITIVE)

raises an exception, because the Numeric instance 50 does not know how to compare itself with a RangeExtd::Infinity instance, and Range class does not allow such a case.

For Numeric, this is deliberately so. Please use Float::INFINITY instead in principle; it will be a lot faster in run-time, though it is perfectly possible for you to implement the feature in Numeric sub-classes, if need be.

Any other Comparable classes are defined by users by definition, whether you or authors of libraries. The comparison with Infinity instances are implemented in Object#<=> in this library. Hence, as long as the method [#<=>] in the classes is written sensibly, that is, if it respects the method of the super-class when it does not know how to deal with an unknown object, there is no need for modification. Any object in your class (say, YourComparable) is immediately comparable with the Infinity instances,

YourComparable.new <=> RangeExtd::Infinity::POSITIVE    # => -1
RangeExtd::Infinity::POSITIVE <=> YourComparable.new    # => 1

except for the infinity inscances in YourComparable (see #==).

See the document in Object#<=> in this code/package for detail.

However, some existing Comparable classes, perhaps written by some one else may not be so polite, and has disabled comparison with any object but those intended. Unlucky you! For example, the classes like Date and DateTime are one of them.

For that sort of circumstances, the class method Infinity.overwrite_compare provides a convenient way to overcome this problem to make the operator [#<=>] commutative for a given Comparable class.

Note Infinity.overwrite_compare does nothing for the classes registered in the Class constant Array CLASSES_ACCEPTABLE. So, if you want to avoid such modification of the method [#<=>], perhaps by some other end users, you can register the class in that array.

Only the methods defined in this class are #===, #==, #<=>, #succ, #to_s, #inspect, #infinity?, #positive? and #negative?.

Note that the unary operand [#-@] is not defined.

Constant Summary collapse

CLASSES_ACCEPTABLE =

Class that accept to be compared with Infinity instances.

[self, Float, Integer, Rational, Numeric, String]

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.overwrite_compare(obj) ⇒ Boolean?

Overwrite [#<=>] method of the given class, if necessary, to make its instances be comparable with RangeExtd::Infinity objects (constants). For example,

RangeExtd::Infinity::NEGATIVE.<=>(any_comparable)

always gives back -1 (except for same infinities). However the other way around,

SomeClass.new.<=>(RangeExtd::Infinity::NEGATIVE)

usually returns nil, which is not handy. Therefore, this function (Class method) provides a convenient way to overcome it, that is, if the given class (or the class of the given object) is Comparable, its [#<=>] method is modified (and true is returned), unless it has been already done so, or some classes as listed below, such as Numeric and String, in which case nil is returned. If it is not Comparable, false is returned. The judgement whether it is Comparable or not is based whether the class has an instance method ThatClass#<=

In processing, this method first looks up at an Array CLASSES_ACCEPTABLE, and if the given class is registered in it, it does nothing. If not, and if all the othe conditions are met, it overwrites its <=> method and register the class in the array.

Parameters:

  • obj (Object)

    Either Class or its object.

Returns:

  • (Boolean, nil)

    (see the description).



223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
# File 'lib/range_extd/infinity/infinity.rb', line 223

def self.overwrite_compare(obj)
  if defined? obj.instance_methods
    klass = obj
  else
    klass = obj.class

    begin
      _ = 1.0 + obj # Use  "rescue ArgumentError"  if using "1.0<obj"
      return nil  # No change for Numeric
    rescue TypeError
    end
  end   # if defined? obj.instance_methods

  # [Numeric, Fixnum, Bignum, Float, Rational, String, Complex].each do |i| # , BigFloat
  (self::CLASSES_ACCEPTABLE+[self]).each do |i| # , BigFloat
    # The class itself (RangeExtd::Infinity) must be rejected!
    # Otherwise the rewrites itself, and may cause an infinite loop.
    # In fact it is pre-defined in RangeExtd::Infinity, so the above addition is a duplication - just to make sure.
    return nil if i == klass    # No change for Numeric etc
    # Built-in String, Numeric etc try to flip over "<=>" if it doesn't know the object!
  end
  self::CLASSES_ACCEPTABLE.push(klass)  # The class is registered, so it would not come here again for the class.

  a = klass.instance_methods
  if !a.include?( :<= ) # NOT Comparable
    return false
  elsif a.include?(:compare_before_infinity)
    return nil
  else
    # Overwrite the definition of "<=>" so that it is fliped over for Infinity.

    code = "alias_method :compare_before_infinity, :<=> if ! self.method_defined?(:compare_before_infinity)\ndef <=>(c)\n  if defined?(self.<=) && RangeExtd::Infinity === c\n    if defined?(self.infinity?) && defined?(self.positive?)\n      if (self.positive? ^! c.positive?)\n        0\n      elsif self.positive?\n        1\n      else\n        -1\n      end\n    else\n      if c.positive?\n        -1\n      else\n        1\n      end\n    end\n  else\n    compare_before_infinity(c)\n  end\nend\n"
#<<__EOF__  # for Emacs hilit.

    klass.class_eval(code)

    true
  end
end

Instance Method Details

#<=>(c) ⇒ Integer?

Always -1 or 1 except for itself and the corresponding infinities (== 0). See #==. Or, nil (as defined by Object), if the argument is not Comparable, such as, nil and IO.

Returns:

  • (Integer, nil)


138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/range_extd/infinity/infinity.rb', line 138

def <=>(c)
  if c.nil?
    super
  elsif !defined?(c.<=) # Not Comparable?
    super
  elsif @positive 
    if self == c
      0
    else
      1
    end
  else  # aka negative
    if self == c
      0
    else
      -1
    end
  end
end

#==(c) ⇒ Object

Always false except for itself and the corresponding Float::INFINITY and those that have methods of #infinity? and #positive? with the corresponding true/false values, in which case this returns true.



161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/range_extd/infinity/infinity.rb', line 161

def ==(c)
  if (Infinity === c)
    (@positive ^! c.positive?)  # It should be OK to compare object_id?
  elsif c ==  FLOAT_INFINITY &&  @positive
    true
  elsif c == -FLOAT_INFINITY && !@positive
    true
  elsif defined?(c.infinity?) && defined?(c.positive?)
    (c.infinity? && (@positive ^! c.positive?))
  else
    false
  end
end

#===(c) ⇒ Object

Equivalent to #==



176
177
178
# File 'lib/range_extd/infinity/infinity.rb', line 176

def ===(c)
  self == c
end

#cmp_before_rangeextd_infinity?Object

No overwriting.



133
# File 'lib/range_extd/infinity/infinity.rb', line 133

alias_method :cmp_before_rangeextd_infinity?, :==

#infinity?Boolean

Returns:

  • (Boolean)


121
122
123
# File 'lib/range_extd/infinity/infinity.rb', line 121

def infinity?
  true
end

#inspectString Also known as: to_s

Returns:

  • (String)


186
187
188
189
190
191
192
# File 'lib/range_extd/infinity/infinity.rb', line 186

def inspect
  if @positive 
    "INFINITY"
  else
    "-INFINITY"
  end
end

#negative?Boolean

Returns:

  • (Boolean)


129
130
131
# File 'lib/range_extd/infinity/infinity.rb', line 129

def negative?
  !@positive 
end

#positive?Boolean

Returns:

  • (Boolean)


125
126
127
# File 'lib/range_extd/infinity/infinity.rb', line 125

def positive?
  @positive 
end

#succInfinity

Returns self.

Returns:



181
182
183
# File 'lib/range_extd/infinity/infinity.rb', line 181

def succ
  self
end