Project

General

Profile

Feature #20080

Updated by stuyam (Stuart Yamartino) 10 months ago

Followup Reference: #20027  

 **Update 1/11/24:** **Updated Proposal:** (based on many wonderful suggestions!) 

 1. Call the method `#bounds`. 
 ```ruby 
 first, last = (1..300).bounds # => [1, 300] 
 first, last = (300..1).bounds # => [300, 1] 
 first, last = (..300).bounds # => [nil, 300] 
 first, last = (1..).bounds # => [1, nil] 
 ``` 

 2. Add `exclude_end?` support so re-hydration of Range works: 
 ```ruby 
 b = (1..2).bounds    #=> [1,2] 
 Range.new(*b)        #=> 1..2 

 b = (1...2).bounds #=> [1,2,true] 
 Range.new(*b)        #=> 1...2 
 ``` 

 I did a better job of outlining use cases in this comment below so I will let that speak for itself: https://bugs.ruby-lang.org/issues/20080#note-3 

 **Update: 2/13/24** 
 Browsing the ruby codebase I noticed that the `#as_json` method on Range (when you `require 'json/add/range'`) does something similar to what the `#bounds` method we are describing is doing: https://github.com/ruby/ruby/blob/master/ext/json/lib/json/add/range.rb#L34 

 ```ruby 
 { 
   JSON.create_id    => self.class.name, 
   'a'               => [ first, last, exclude_end? ] 
 } 
 ``` 

 This tells me we are on the right track. Though the difference here is that `exclude_end?` is always included. I am thinking the `#bounds` should maybe always be including the `exclude_end?` piece rather than only include it if it's `true`. I am inclined to suggest always including the `exclude_end?` regardless of if it is true or false to avoid confusion or people ever calling `#last` on the `#bounds` results thinking it is the `#last` value where it could be the `#last` or `#exclude_end?` value depending on the type or range. This would still satisfy all of the use cases outlined in this comment: https://bugs.ruby-lang.org/issues/20080#note-3 

 **Therefore I think `#bounds` should just always return: `[ first, last, exclude_end? ]`** 

 **Original Proposal:** 
 This feature request is to implement a method called `#begin_and_end` on `Range` that returns an array of the first and last value stored in a range: 
 ```ruby 
 (1..300).begin_and_end #=> [1, 300] 

 first, last = (300..1).begin_and_end 
 first #=> 300 
 last #=> 1 
 ``` 
 I believe this would be a great addition to Ranges as they are often used to pass around a single object used to hold endpoints, and this allows easier retrieval of those endpoints. 
 This would allow easier deconstruction into start and end values using array deconstruction as well as a simpler way to serialize to a more primitive object such as an array for database storage. 
 This implementation was suggested by @mame in my initial feature suggestion regarding range deconstruction: https://bugs.ruby-lang.org/issues/20027 

 This implementation would work similar to how `#minmax` works where it returns an array of two numbers, however the difference is that `#minmax` doesn't work with reverse ranges as @Dan0042 pointed out in the link above: 
 ```ruby 
 (1..42).minmax #=> [1, 42] 
 (42..1).minmax #=> [nil, nil] 
 ``` 

Back