ProcParty

A couple months ago, I wrote a gem called ProcParty. When included in a class, it lets you call #to_proc on instances of the class.

Here’s a quick example:

class Exponenter
  include ProcParty

  def initialize(power)
    @power = power
  end

  def call(n)
    n ** @power
  end
end

[1, 2, 3].map(&Exponenter.new(2)) # => [1, 4, 9]

That example seems kinda lame, but it becomes pretty powerful since classes can have initializers. This lets us, in a sense, use partial application in a very natural, Ruby-like way

squarer = Exponenter.new(2)
cuber = Exponenter.new(3)

[1, 2, 3].map(&squarer) # => [1, 4, 9]
[1, 2, 3].map(&cuber) # => [1, 8, 27]

This is equivalent to the traditional functional style (this example uses Ruby procs)

exponenter = -> (power, n) { n ** power }
squarer = exponenter.curry[2]
cuber = exponenter.curry[3]

[1, 2, 3].map(&squarer) # => [1, 4, 9]
[1, 2, 3].map(&cuber) # => [1, 8, 27]

Technical Details

The & operator tries to convert a Proc to a block. If the object isn’t already a Proc, it first calls #to_proc on that object, then converts the result into a block.

[1, Kernel, Object.new].each { |object| object.inspect }
# => [1, Kernel, #<Object:0x007febe9000a50>]

[1, Kernel, Object.new].each(&-> (object) { object.inspect })
# => [1, Kernel, #<Object:0x007febe8039cc0>]

[1, Kernel, Object.new].each(&:inspect)
# => [1, Kernel, #<Object:0x007febe8059250>]

funny_example = -> (object) { object.inspect }
def funny_example.to_proc
  -> { "overriden inspection! JK" }
end
[1, Kernel, Object.new].each(&funny_example)
# => [1, Kernel, #<Object:0x007febe8021c88>]
# Note that the original wasn't "overridden". It used the original since it
# was already a Proc

Since the & operator calls #to_proc on the object, we can define any arbitrary object to return any arbitrary proc and have that run as a block.

class Inspected
  def to_proc
    -> (object) { object.inspect }
  end
end

[1, Kernel, Object.new].each(&Inspected.new)
# => [1, Kernel, #<Object:0x007f962fb3c5a0>]

The core functionality of ProcParty boils down to this idea. It uses Method#to_proc to convert the class’ #call method into a Proc, which is then converted into a block

class Inspected
  def call(object)
    object.inspect
  end

  def to_proc
    method(:call).to_proc
  end
end

[1, Kernel, Object.new].each(&Inspected.new)
# => [1, Kernel, #<Object:0x007f962fb95b78>]

Aside

As a side note, I find it extremely interesting that it’s possible to maintain statelessness in functional programming by using currying/partial application. In a functional language, the example with currying is considered stateless. However, the example with the initializer is considered stateful in an object oriented language.

I personally like to think of these classes as stateless, or at a minimum, that it’s free of mutable state.

ProcParty is on RubyGems and on Github. Please let me know what you think!

Posted on 2017-07-28 12:25 AM -0400
Contact
hello(at)zachahn(dot)com
© Copyright 2008–2023 Zach Ahn