2

I'd like my SomeArray#map to return an "array" of the SomeArray class.

class SomeArray < Array
  def map
    SomeArray.new(super)
  end
end

some_array = SomeArray.new(["foo", "bar", "baz"])
p some_array.class #=> SomeArray
p some_array.map { |e| e }.class #=> SomeArray

Except now I also want to be able to use the Enumerator#with_index instance method. So ideally something like this would work:

some_array.map.with_index { |e, i| e }.class #=> SomeArray

How would that work?

I've tried:

class SomeArray < Array
  def map
    SomeArray.new(super)
  end

  def with_index(offset = 0)
    super
  end
end

some_array = SomeArray.new(["foo", "bar", "baz"])
p some_array.class #=> SomeArray
p some_array.map.with_index { |e, i| e }.class #=> no implicit conversion of Enumerator into Integer (TypeError)

But it's not working.

0

2 Answers 2

1

I think the problem here is that you're treating enumerable and array as if they're the same, which they're not.

Specifically, this in the map call: SomeArray.new(super).

I can reproduce your error:

[6] pry(main)> Array.new [1].map
TypeError: no implicit conversion of Enumerator into Integer

Now, when you pass a block to map, it works:

Array.new([1].map { |x| x })  
=> [1]

But in your map.with_index you're not doing this.

You can do something like this:

module Foo
  include Enumerable
  def map
    puts "calling my map"
    super
  end
  def with_index
    puts "calling my with_index"
    super
  end
end

class MyArr < Array
  include Foo
end

puts MyArr.new([1]).map.with_index { |x, y| [x,y] }
# calling my map
# calling my with_index
# 1
# 0

Which kind of begs the question of why you're writing this class that just calls super. But in the case that you want to modify the default functionality of enumerable, this is one way.

Sign up to request clarification or add additional context in comments.

3 Comments

Can you elaborate on the difference between Array.new [1].map and Array.new([1].map { |x| x }) ? Does it have something to do with one instantiation of an array includes the Enumerable module and one doesn't? Because they are both arrays [1].class #=> Array and Array.new.class #=> Array So I'm confused about what the difference is.
Generally you should use [ ] brackets, but Array.new has some additional capabiliies (see stackoverflow.com/questions/5324654/…). In the case that you create a MyArray class which inherits from Array, you'd need to use MyArray.new because [ ] would use the standard Array class. Because of right associativity (the rule that often makes parenthesis unnecessary), Array.new 1.map(&:class) is the same as Array.new(1.map(&:class)) but in either case the Array.new is redundant. You can remove it and the effect will be the same.
The difference between Array.new [1].map and Array.new([1].map { |x| x}) is not the parenthesis (which can be removed with no effect). It's the fact t hat [1].map (map with no block) returns an Enumerable while [1].map { |x| x } returns an Array.
1

The main problem is that map is an Array method while with_index is an Enumerator method.

  • Array methods are defined to just call super, and convert the output array to SomeArray.
  • Enumerator methods are defined with a to_enum first, convert the output to an Array and then to SomeArray.

It probably isn't the best structure for what you want to do, and isn't very efficient either. It's just a proof a concept!

class SomeArray < Array
  # Array methods are overwritten here :
  [:map, :select, :reject].each do |array_method_name|
    define_method array_method_name do |*p, &block|
      SomeArray.new(super(*p, &block).to_a)
    end
  end

  # Enumerator methods are defined for SomeArray here :
  [:with_index, :with_object].each do |enum_method_name|
    define_method enum_method_name do |*p, &block|
      SomeArray.new(to_enum.public_send(enum_method_name, *p, &block).to_a)
    end
  end
end

some_array = SomeArray.new(%w(foo bar baz biz))

p some_array.map { |s| s * 2 }.with_index.select { |_, i| i.even? }
#=> [["foofoo", 0], ["bazbaz", 2]]
p some_array.map { |s| s * 2 }.with_index.select { |_, i| i.even? }.class
#=> SomeArray

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.