I recently started working with Ruby after writing mostly very functional/compositional JS and some point free clojure.
I've learned that Ruby is very "open" to modification or extension. Neato.
I set myself a challenge, and promptly failed to make any progress towards it. Likely this is because I'm still in the "Don't know what terms to search" stage of learning Ruby.
Write a module such that when used it does two things...
For rule 1 this would look something like
class TestClass
include ThingIWannaMake
def something
puts "hello world"
end
end
would effectively be
class TestClass
def self.something
puts "hello world"
end
end
while for rule 2 this
class TestClass
include ThingIWannaMake
def something(a)
puts a
end
end
would mean the same as
class TestClass
def self.something
-> a { puts a }
end
end
Leading to a final, ideal, output (for the second input) being...
class TestClass
def self.something
-> a { puts a }
end
end
This task was primarily taken on as a learning exercise in Ruby so my primary interest is in the terms and tutorials that would teach my how I could, or why I could not, achieve this goal or another one like it.
While this question was wonderfully answered (thank you!) the majority of feedback was negative. Please allow me to address some of the points here, for future readers who may stumble across this as well as in response to the comments themselves.
Thank you again for the answers! Hopefully these clarifications make this at least a less frustrating question to read.
Oh this is going to be terrible, but you asked for it!
Here's what we're making work:
class TestClass
include Lambdifier
def something
puts "hello world"
end
end
TestClass.something.call
# hello world
# => nil
The first conundrum to solve is the fact that at the point when Lambdifier
is included there are no methods defined yet. Ruby's definitions run sequentially. So we're going to have to run a piece of code whenever a method is added to the class. Conveniently there's a Ruby hook method_added
that does just that.
Another issue would be that you technically can unbind methods from their original owners and rebind them to new ones via instance_method
and bind
, but Ruby still requires the new owner to be of the same class or its descendant. We can work around this by creating an owner we need from the original class, effectively making it a singleton. And Ruby's standard library provides this solution already, so we'll make use of it and not reinvent the wheel. And since the methods we need are already bound to a valid receiver, rebinding won't be necessary.
One could say that it's a major deviation as this doesn't not attach instance methods to their respective class, but doing so breaks the type system, because an object's class is not guaranteed to be type-compatible to its instances (although realistically, even instances of subclasses may not be), so the fact that it's not allowed is probably for the better.
(There is at least one exception to this rule, Class
, which is an instance of itself... but there isn't much you can do with it, as it's treated by the language in a rather special way.)
require "singleton"
module Lambdifier
def self.included(base)
base.include ::Singleton
base.extend ClassMethods
end
module ClassMethods
def method_added(name)
prior_method = instance.method(name)
define_singleton_method(name) { prior_method }
end
end
end
class TestClass
include Lambdifier
def something
puts "hello world"
end
end
TestClass.something.call
# hello world
# => nil
A few remarks:
The closest thing to "static methods" in Ruby are "singleton methods". They're essentially methods, but are defined on a value's "singleton class", a lazily defined class (in that it doesn't exist until accessed) just for this value. And define_singleton_method
is essentially singleton_class.define_method
. And the same thing as doing define_method
or def
in a class << self
block.
The return values of methods aren't technically lambdas, but they "quack like lambdas", in that they're callables (have a call
method). Most prominent callables in Ruby are procs and lambdas, but there are neither — these are Method
s:
TestClass.something
# => #<Method: TestClass#something>
The instance methods are still there in their original form! You can "undefine" them with undef_method
if you need to though. It replaces a method with a "tombstone" inside the class which causes Ruby to stop going up the ancestor chain and raise NoMethodError
.
TestClass.instance_methods(false)
# => [:something]
TestClass.undef_method(:something) # Where to insert? Left as an exercise :)
TestClass.send(:new).something # Singleton hides .new...
# !> NoMethodError: undefined method `something' for #<TestClass:...>
TestClass.something.call
# hello world
# => nil
Mind you, if you inline prior_method
this won't work, as class methods will be looking up their counterparts on instance
on every call.
Could you make instance methods return "lambdas of themselves" before making them "static"? Hm... to return a "lambda" you're not going to need arguments, so you'd have to change the arity to 0, which would make the interface incompatible (even more so, because return type change already happened). So probably not.