Module that allows you to treat any object or class as a function--any thoughts?

Invokable

Objects are functions! Treat any Object, Hashes, Arrays, and Sets as Procs (like Enumerable but for Procs)

Synopsis

require 'invokable'
require 'invokable/hash'

number_names = { 1 => "One", 2 => "Two", 3 => "Three" }
[1, 2, 3, 4].map(&number_names) # => ["One", "Two", "Three", nil]
require 'invokable'
require 'invokable/array'

alpha = ('a'..'z').to_a
[1, 2, 3, 4].map(&alpha) # => ["b", "c", "d", "e"]
require 'invokable'
require 'invokable/set'

favorite_numbers = Set[3, Math::PI]
[1, 2, 3, 4].select(&favorite_numbers) # => [3]
# service objects
require 'invokable'

class GetDataFromSomeService
  include Invokable

  def call(user)
    # do the dirt
  end
end

data_for_user = GetDataFromSomeService.new.memoize # 'memoize' makes a proc that caches results
User.all.map(&data_for_user)
# command objects that enclose state, can be treated as automatically curried functions.
require 'invokable'
require 'invokable/closure'

class TwitterPoster
  include Invokable::Closure

  enclose :model

  def call(user)
    # do the dirt
    ...
    TwitterStatus.new(user, data)
  end
end

TwitterPoster.call(Model.find(1)) # => #<TwitterPoster ...>
TwitterPoster.call(Model.find(1), current_user) # => #<TwitterStatus ...>

# both the class and it's instances can be used any where Procs are.

Model.where(created_at: Date.today).map(&TwitterPoster) # => [#<TwitterPoster ...>, ...]

Use as much or a little as you need:

require 'invokable'         # loads Invokable module
require 'invokable/closure' # loads Invokable::Closure module
require 'invokable/hash'    # loads hash patch
require 'invokable/array'   # loads array patch
require 'invokable/set'     # loads set patch
require 'invokable/data'    # loads hash and set patches

Why?

A function is a mapping of one value to another with the additional constraint that for the one input value you will always get the same output value. So, conceptually, Ruby Hashes, Arrays, and Sets are all functions. Also, there are many one method objects out there (e.g. Service Objects) that are essentially functions. Why not treat them as such?

See https://github.com/delonnewman/invokable for more information.

Interesting stuff! It prompted me to look into this stuff from first principles :slight_smile:

One question that it provoked:

If you can execute a Proc by .calling it, why doesn’t the & syntax just check to see if the thing responds to call instead of to_proc?

My guess was that Matz invented the block syntax before deciding how to back it - and then when to_proc came along, it was decided not to change the expectation from is_a?(Proc) to respond_to?(:call)

Thanks @andrewdotnich. I think what you said makes sense, and that was what prompted making the module—trying to blend object-oriented and functional programming in a seamless way that seems idiomatic. Ruby takes you really close on that front, but it seemed to me that the notion of a function abstracted away so that it can be freely composed into a class so it and it’s instances can be treated as a function could be a nice addition.

This is a cool idea. I’ve said many times that I would LOVE to see a first-class Function concept in Ruby represented as a class that you can inherit from. This would do more-or-less what your gem is doing. One day I’ll submit a proposal to Ruby tracker :slight_smile:

Please do @solnic. I made an attempt here: https://bugs.ruby-lang.org/issues/17043, it seems the core team are focused on Ruby 3.0 issues, but I would imagine your voice might carry a bit more weight than mine.

1 Like

@delonnewman great! I’ll follow up in the issue.

I just made some improvements to the API… enclose in Invokable::Closure was bothering me. it was really a hack because I kept getting a negative arity for initialize, but I guess I was using method(:initialize).arity rather than instance_method(:initialize).arity, because now it’s working.

So I’ve deprecated Invokable::Closure and moved the behavior into Invokable minus enclose.

So now the example above is a bit cleaner:

# command objects that enclose state, can be treated as automatically curried functions.
require 'invokable'

class TwitterPoster
  include Invokable

  def initialize(model)
    @model = model
  end

  def call(user)
    # do the dirt
    ...
    TwitterStatus.new(user, data)
  end
end

TwitterPoster.call(Model.find(1)) # => #<TwitterPoster ...>
TwitterPoster.call(Model.find(1), current_user) # => #<TwitterStatus ...>

# both the class and it's instances can be used anywhere Procs are.

Model.where(created_at: Date.today).map(&TwitterPoster) # => [#<TwitterPoster ...>, ...]

That’s available now in version 0.6.0.

@solnic I had an additional thought. An “Invokable” module and a “Function” class could be complementary. If we think of the “Invokable” module as a generalized interface for Procs like “Enumerable” is for collections.

Then a “Function” class could be a specific implementation of that interface that brings in some constraints that are not present in Proc.

So, for example, imagine that the “Function” class and it’s instances (after initialization) are frozen (and perhaps other measures taken) that would help to ensure (or at least encourage) that those instances or classes that inherit are pure functions. Which would be great for actor model concurrency or other places where mutable things are undesirable.

Then we would have a generalized notion of an “Invokable” that “Proc”, “Method”, and “Function” and any other function-like objects can implement, as well as three contrasting implementations. That are useful for different purposes. Again, much as we do for collections.

1 Like

I’m not sure what the semantic meaning of multiple Function instances would be, though. If it’s to do things like Function.new(context).call(args) then I think I prefer your explicit Closure naming.

Some food for thought can be found in Scala’s Function. I think an object that represents a function makes sense. It’s essentially a higher-order function that’s partially applied:

# using procs
add = -> (x,y) { x + y }

add_2 = add.curry.(2)

add_2.(3)
=> 5

# the same encapsulated by a class
class Add
  def initialize(x)
    @x = x
  end

  def call(y)
    @x + y
  end
end

add_2 = Add.new(2)

add_2.(3)
# 5

Now, my specific concept that I’ve been promoting for the past 5+ years was to model functional objects in Ruby in a way that the state is only used for external dependencies. The example above doesn’t show this though because I just wanted to show an example of a higher-order function and how it can be “mapped” to a class-based equivalent. This still makes sense in the context of this discussion because in functional languages dependencies are indeed handled by injecting them into higher-order functions. In Ruby it’s trivial because you can just use…you guessed it! Objects :slight_smile:

Yeah that’s a great point. A unified interface for all the callable objects would be great.

1 Like

While I absolutely adore the idea, I’m still going to be a bit nitpicky and say that I’d love to avoid patching Hash/Array/Set via require. I’d rather load those patches via refinements

Also, I’ve hacked around with my own implementation of something similar – I wanted to easily treat commands as procs.

So I’m a little confused by this wholememoize thingy. Is there a way to just define to_proc and memoize it automatically?

Something like that:

def to_proc
  @proc ||= method(:call).to_proc
end

All Clojure collections are functions as well. It seems to work great for them:

; sets -- set membership
(#{1 2 3 4} 4) ;=> 4
(#{1 2 3 4} 8) ;=> nil

; vectors (arrays) -- indexing
(["cookie" "hello"] 0) ;=> "hello"
(["cookie" "hello"] 4) ;=> IndexOutOfBoundsException

; maps (hashes) -- dereferencing
({"monster" "goblin" "character" "hero"} "monster") ;=> "goblin"
({"monster" "goblin" "character" "hero"} "foe") ;=> nil

I never coded in Clojure, but this pattern is quite nice.

1 Like

@francoisb That’s absolutely where the inspiration came from for me. I’m coming back to to Ruby development after a few years of Clojure development (which I love), and it seems to me that as far as data-driven development Ruby is really not far off at all from Clojure. With just a little tweaking I think Ruby can be just a powerful in that domain. The only question in my mind is how to do that idiomatically, because there will be no buy-in if it feels like Clojure.

This is my first step in that direction. Eventually I’d like to build a framework that supports functional / relational programming with heavy inspiration from Datomic, feels more or less like ActiveRecord, but with slightly different semantics.

It would support recursive, attribute-oriented rather than table-oriented queries, a data-driven pull syntax (see https://github.com/delonnewman/activerecord-pull for an example of how that could work). And I’m thinking generic functions which is another elegant technique for blending OOP and FP (you can see some of my experiments with that here: https://gist.github.com/delonnewman/9f1c98ec2f650235a1b216256deb2955). I’d like to create it a collection of modular libraries that can be freely composed together or be used as a substitute for ActiveRecord, and I’d love to make them libraries that would integrate well with the dry-rb ecosystem.

@Morozzzko I don’t think it’ll be hard to support refinements also. If you have any suggestions on the best way to do that. I’d love to hear them.

2 Likes

@solnic I’ll definitely take a look at Scala Function. Thanks for the reference.

Also, I’d think “Callable” would be a more transparent name than “Invokable” for the standard library. I only called my module “Invokable” because there’s already a gem in rubygems called “Callable”, and “Invokable” seemed like the best alternative.