From: "trans. (T. Onoma)" Date: 2004-10-09T23:31:04+09:00 Subject: Re: Range behavior (Re: [RCR] New [] Semantics) On Saturday 09 October 2004 08:07 am, Brian Candler wrote: | You can argue that a Ruby Range shouldn't be able to iterate then, but I | just see it as a freebie bonus behaviour, which is actually useful in | practice. After expirementing with implementation of Interval which coerces to a purely Iterative Range (see code below), I can safely say, that it is certainly formally precise and seems to works quite well. On the other hand, I am not sure the gain is great enough to justify it, but please, prove me wrong if you see otherwise! So Brian's assessment may indeed be the best take. Ruby's Range _is_ a mathematical Interval. The Enumerable mixin is simply an added bonus for utility. That said, the non-POLS of this stems from the fact the #member? and #include? are defined in Enumerable, but Range goes and redefines them for it's own purposes; also === and case statements become useless in the Enumerable sense as well. So you end up with a hodgepodge. The only way I see to clean it up is A) per a division like my coerce code below, -OR- B) using a different method for checking inclusion (ex- #contains?) so that #member? and #include? still belong to Enumerable, PLUS expanding case to take an argument: case 5.5 using :member? when 0..10 puts "wrong" else puts "right" end That would do the trick. (Or is there already some way to do this?) The only problem is that changing the meaning of Range#include? would likely break some code. OTOH, matz has a point when he ask how useful is Range#member? anyway. Hence we should just well document the fact Range in NOT technically Enumerable it can just be utilized as one in simple cases. Food for thought, T. --------------- code ------------------- class Interval def initialize(first, last, exclude_first=false, exclude_last=false) @first = first @last = last @exclude_first = exclude_first @exclude_last = exclude_last end def first ; @first ; end def last ; @last ; end def exclude_first? ; @exclude_first ; end def exclude_last? ; @exclude_last ; end def include?(x) (@exclude_first ? (x <=> @first) == 1 : (x <=> @first) >= 0) && (@exclude_last ? (x <=> @last) == -1 : (x <=> @last) <= 0) end def length ; @last - @first ; end alias_method( :begin, :first ) alias_method( :end, :last ) alias_method( :exclude_begin?, :exclude_first? ) alias_method( :exclude_end?, :exclude_last? ) alias_method( :member?, :include? ) alias_method( :===, :include? ) alias_method( :size, :length ) def to_rng seed = ( @exclude_first ? @first.succ : @first ) term = ( @exclude_last ? proc{|x| (x <=> @last) == -1} : proc{|x| (x <=> @last) <= 0} ) Range.new( seed, term ) end # Any method of Enumerable coerce the Interval into to a Range # Is there a better way to do this? alias non_enumerable_method_missing method_missing def method_missing(meth, *args, &yld) if meth == :each or Enumerable.instance_methods.include?(meth.to_s) to_rng.send(meth, *args, &yld) else non_enumerable_method_missing(meth, *args, &yld) end end end ### Modified Range Class (purely discrete) class Range attr_reader :start, :step, :term, :skip def initialize(start, step, skip=1) @start = start @skip = skip if step.kind_of?(Proc) @step = nil @term = step else @step = step @term = nil end end def each if @term seed = @start while @term.call(seed) yield(seed) @skip.times { seed = seed.succ } end else seed = @start @step.times { @skip.times { seed = seed.succ } yield(seed) } end end # cache (bad idea?) def last if ! @last if @term seed = @start while @term.call(seed) prior = seed @skip.times { seed = seed.succ } end @last = prior else seed = @start @step.times { @skip.times { seed = seed.succ } } @last = seed end end @last end alias_method( :end, :last ) alias_method( :first, :start ) alias_method( :length, :step ) alias_method( :size, :step ) undef_method :exclude_end? end # --- quick testing --- # interval i = Interval.new(0,10) puts i.first puts i.last puts i.member?(0) puts i.member?(5) puts i.include?(10) puts i.include?(11) puts i.include?(-1) puts i.length # range r = i.to_rng puts r.last # coerce i.each {|e| puts e } puts i.select { |e| e % 2 == 0 } T.