Crystal: Move Array index methods to Indexable

Created on 3 Apr 2019  路  10Comments  路  Source: crystal-lang/crystal

Array#[](range : Range) and similar methods (including the ones added in #7338) should be valid for any Indexable. Most methods could be implemented directly in Indexable and only #[]?(start : Int, count : Int) needs to be abstract and implementation-specific.

Currently, only Array implement a full set of range methods, but most other including classes implement at least some method like #[](range : Range) or #[](start : Int, count : Int).

The only trouble are Tuple and StaticArray which can't create dynamically sized instances of subsets of themselves. So the question is, what type should Tuple#[](range : Range) return. Or should it be possible at all? If not, we can't add this to Indexable.

Instead we could consider adding a module (for example List) which inherits from Indexable and implements range methods and allows modifications using []=, << etc.

Most helpful comment

I see a String as a collection of Char in an encoding, which only happens to be a collection of bytes for some encodings (e.g. ASCII). I'd expect a String#each to iterate each Char, similar to how String#[] gives me the Char at an index.

All 10 comments

The problem with [] that returns several values is what type to return. For Array it's clear: Array. Should Deque return a Deque? What about Tuple and StaticArray as you mention?

That's why I don't think [](range) belongs in Indexable. Maybe another module... but I'm not sure if it will be a big win.

It's possible to return Tuple for Tuple#[range], but this means having potentially huge macro expands, with every possible subsets size of the instance pre-defined in it.

For Tuple I always thought about supporting [](range) in a built-in way, similar to how [](index) is magical. But I didn't find good use cases for that so it might not be worth it.

I've a use case:

def first_n_args(num : Int, *args)                                                                 
  args[0..n]                                                                             
end                                                                                    
puts first_n_args 2, 'a', 'b', 'c'

I'm using a similar logic for an interpreter, for now I use case/whens with case num; when 0 {...}; when 1 {...} etc, then another case/when to match this Tuple against others.

I don't think it's that necessary, but I like the idea of having #[] for all Indexables.

And what about String? We could include Indexable in it. This means having String#each, String#first and loads of new methods form Indexable

Ruby had each for String, I think. The problem is that a String is a collection of chars and bytes so it's not clear what should be iterated by default. Plus String already implement those tons of methods from Indexable.

Ruby no longer supports String#each since 1.9
I don't think it's wise to include Indexable(Char) because of Char::Reader, nor possible to include Indexable(Bytes) due to #[] methods clash.

I see a String as a collection of Char in an encoding, which only happens to be a collection of bytes for some encodings (e.g. ASCII). I'd expect a String#each to iterate each Char, similar to how String#[] gives me the Char at an index.

By comparing https://github.com/crystal-lang/crystal/blob/master/src/indexable.cr#L186 and https://github.com/crystal-lang/crystal/blob/master/src/string.cr#L3722, this means creating a new Char::Reader for each unsafe_fetch, if we use the actual String#at method.
It will be less efficient, unless we refactor this. It may be beyond my knowledge :grimacing:

Tuple#first(n : Int) and Deque's one returns Array since Enumerable#first(n : Int) is defined, so it's reasonable that Indexable#[](range) returns Array.

IMHO, Indexable#[](start, count) should be defined in Indexable and it returns Array as default. Then we can implement these method by using [](start, count):

  • [](range)
  • first(n)
  • last(n)
  • skip
  • skip_while
  • take_while

When some classes require to change these methods result (e.g. Slice), we can override [](start, count).

Was this page helpful?
0 / 5 - 0 ratings

Related issues

relonger picture relonger  路  3Comments

jhass picture jhass  路  3Comments

costajob picture costajob  路  3Comments

asterite picture asterite  路  3Comments

oprypin picture oprypin  路  3Comments