Adam Shelly wrote: > I just saw this quote in pickaxe, which helped clarify my thinking: > > "Ranges can be constructed using objects of any type, as long as the > objects can be compared using their <=> operator and they support the > succ method to return the next object in sequence. " > > But a<=>a.succ != -1 for all a, especially if a is a String. > This causes r.find{|a| !r.member?(a)} to return non-nil for some > ranges, which is unexpected, or possibly just plain wrong. > > So I think I like your suggestion, which I would boil down to: > Change the requirement as follows: "Ranges can be constructed from > objects of any class which supports #succ and #cmp, where > a.cmp(a.succ)==-1 for all a." Very nicely put. I wish I were as gifted at explaining things. Thanks Adam. > For numeric classes #cmp is an alias for #<=>. For strings #cmp is a > custom function, matching the string succession generator. > And for your own classes you can write your own #cmp, which does not > have to match #<=>. For instance: > President.new("Kennedy").cmp President.new("Nixon") #=> -1 (Nixon came later) > President.new("Kennedy")<=> President.new("Nixon") #=> 1 (but > Kennedy is greater) :-) > The issue I see is that I don' t know if it is possible to write a > valid #cmp for all cases. > What is the result of 'a'.cmp('0') ? You can't ever get a '0' with > 'a'.succ. So is '0' before or after 'a'? > > I suppose that you could require that a.cmp b returns nil when > a=a.succ will never produce b. Then Range#member? becomes a test for > set membership, just like enum#member?: > class Range > def member? v > (f=first.cmp(v) && f<=0 && l=last.cmp(v) && l>=0) > end > end Yes that's exactly it. The nice thing about having #cmp seperate from #<=> is you can have optimizations to determing this, and it can be uses by the #member? method. > So yes, I like this suggestion. > And I just realized it it may not be orthogonal to the other: > The suggested change leaves no way to test for Interval inclusion in > those cases where the interval and the sequence are different (like > 'a'..'bb'). So perhaps there still should be a method of testing > Range inclusion using <=>. (my suggestion is #spans?) > ('a'..'bb').member? 'z' #=> true > ('a'..'bb').spans? 'z' #=> false > ('a'..'bb').member? 'aardvark' #=> false > ('a'..'bb').spans? 'aardvark' #=> true Good point. Boundry checks with #<=>, irregardelss of membership, would still be useful. Thanks Adam. T.