Dmitry Dmitriev
Dmitry Dmitriev

Reputation: 1059

Ruby Extended Array class that supports full method chain

Short:

Is there a way to create extended Array class named A, that supports full method chain with map, sort, sort_by and new method word and does not harm Array class?

Example:

 [
    A.new(a).word,
    A.new(a).sort.word,
    A.new(a).sort_by(&:-@).word,
    A.new(a).map(&:chr).sort.map(&:ord).word
  ]

Long story:

I solved this kata and created code that extends Array class with new word method:

class Array
  def word
    [*self[0..1],*self[-2..-1]].map(&:chr).join
  end
end
def sort_transform(a)
  [
    a.word,
    a.sort.word,
    a.sort_by(&:-@).word,
    a.map(&:chr).sort.map(&:ord).word
  ].join ?-
end

Then I thought this is not a good idea to add method for such kind of base classes. And I tried to implement new class that inherited all behavior from an Array.

class A < Array
  def word
    [*self[0..1],*self[-2..-1]].map(&:chr).join
  end
end

This addition breaks my code, because map, sort, sort_by returns an Array instance: A.new([1,2,3]).sort.class # Array. And Array does not understand a word method. And instead of A.new(a).sort.word I have to encapsulate part of chain into a A.new constructor: A.new(a.sort).word. That's definitely breaks pure method chain.

Is it possible to extend Array such way to reach pure method chains like this; A.new(a).sort.word?

When I tried to write line this: class A < Array

def word
    [*self[0..1],*self[-2..-1]].map(&:chr).join
  end
  def sort
    A.new(self.sort)
  end
end

This brings me main.rb:8:in 'sort': stack level too deep (SystemStackError) Finally already writing this lines I found a way to avoid deep stack: converting self to Array and then again convert it to A.

class A < Array
  def word
    [*self[0..1],*self[-2..-1]].map(&:chr).join
  end
  def sort
    A.new(self.to_a.sort)
  end
end

So is It an only way to implement such extension?

Upvotes: 1

Views: 246

Answers (1)

Amadan
Amadan

Reputation: 198388

If you ever want to monkey-patch a core class but you think it's icky, you should remember that refinements are made just for this scenario.

module ArrayWithWord
  refine Array do
    def word
      [*self[0..1],*self[-2..-1]].map(&:chr).join
    end
  end
end

class WordTest
  using ArrayWithWord

  # [].word works here
  def self.sort_transform(a)
    [
      a.word,
      a.sort.word,
      a.sort_by(&:-@).word,
      a.map(&:chr).sort.map(&:ord).word
    ].join ?-
  end
end

WordTest.sort_transform(%w(foo bar baz quux))
# => "fbbq-bbfq-bbfq-bbfq"

# [].word doesn't work here
[].word
# => NoMethodError (undefined method `word' for []:Array)

Upvotes: 4

Related Questions