Search code examples
rubylazy-evaluationenumerable

Listen to lazy enumerable


I'm trying to build something that will process an event stream. I want my processors to be side effect free so they are easy to manage, but I also want to ensure I do not run through the stream twice.

class A
  def initialize(*processors)
    @processors = processors
  end
  def call(list)
    @processors.inject(0) { |acc, p|
      acc + p.call(list)
    }
  end
end

class B
  def initialize(multiplier)
    @multiplier = multiplier
  end

  def call(list)
    list.map { |i|
      i * @multiplier
    }.inject(0) { |acc, i|
      puts "B with multipler: #{@multiplier}, adding: #{i}"
      acc + i
    }
  end
end

A.new(B.new(1), B.new(2)).call([1, 2, 3, 4, 5].lazy)
# => 45

Class A is totally wrong as the execution order is:

# B with multipler: 1, adding: 1
# B with multipler: 1, adding: 2
# B with multipler: 1, adding: 3
# B with multipler: 1, adding: 4
# B with multipler: 1, adding: 5
# B with multipler: 2, adding: 2
# B with multipler: 2, adding: 4
# B with multipler: 2, adding: 6
# B with multipler: 2, adding: 8
# B with multipler: 2, adding: 10

How could I modify class A to give me an execution order of:

# B with multipler: 1, adding: 1
# B with multipler: 2, adding: 2
# B with multipler: 1, adding: 2
# B with multipler: 2, adding: 4
# B with multipler: 1, adding: 3
# B with multipler: 2, adding: 6
# B with multipler: 1, adding: 4
# B with multipler: 2, adding: 8
# B with multipler: 1, adding: 5
# B with multipler: 2, adding: 10

Do I need some type of more sophisticated enumerable?


Solution

  • If you want to apply processors to values in order you need to send them one at a time

    class A
      def initialize(*processors)
        @processors = processors
      end
      def call(list)
        list.inject(0) do |acc, item|
          acc + @processors.map { |processor| processor.call(item) }.inject(:+)
        end
      end
    end
    
    class B
      def initialize(multiplier)
        @multiplier = multiplier
      end
    
      def call(item)
        puts "B with multipler: #{@multiplier}, adding: #{item}"
        item * @multiplier
      end
    end
    
    p A.new(B.new(1), B.new(2)).call([1, 2, 3, 4, 5].lazy)