如何在 Ruby 中修改方法定义本身的行为
How to modify the behavior of method definitions themselves in Ruby
设置
我最近开始使用 Ruby,因为我主要编写了非常 functional/compositional JS 和一些免费的 clojure。
我了解到 Ruby 对修改或扩展非常“开放”。 Neato.
我给自己设定了一个挑战,但很快就没有取得任何进展。这可能是因为我还处于“不知道搜索什么术语”的学习阶段Ruby。
我的目标
编写一个模块,使其在使用时做两件事...
- 使 module/class 上定义的所有方法变为静态方法
- 导致在 module/class 上定义的所有方法都变成 return 它们自己的 lambda
具体例子
对于规则 1,这看起来像
class TestClass
include ThingIWannaMake
def something
puts "hello world"
end
end
实际上是
class TestClass
def self.something
puts "hello world"
end
end
而规则 2 是这样
class TestClass
include ThingIWannaMake
def something(a)
puts a
end
end
与
的意思相同
class TestClass
def self.something
-> a { puts a }
end
end
导致最终的理想输出(对于第二个输入)是...
class TestClass
def self.something
-> a { puts a }
end
end
问题
这项任务主要是在 Ruby 中作为学习练习进行的,所以我的主要兴趣是那些可以教我如何或为什么不能实现这个目标或其他目标的术语和教程一个喜欢
Edits/Responses
虽然这个问题得到了很好的回答(谢谢!),但大多数反馈都是负面的。请允许我在这里解决一些要点,以供将来可能偶然发现此问题的读者以及对评论本身的回应。
- 选词 - 我使用的许多词和术语 were/are 不正确。最严重的是使用“静态[方法]”。感谢下面的评论,我现在知道在 Ruby 世界中更准确地理解这个概念将是“class [方法]”。我的错误是我作为“Ruby 外国人”仍在学习语言和概念的地方的扩展。我相信这种语言错误是 Ruby 新用户的指示,并且可能很常见。因此,我将根据下面的更正,留下错误,希望我的失败能对以后的学习者有所启发。
- 目标很糟糕 - 虽然我不知道评论回复是否可以改变某人关于什么是或不是“好的编程”的核心信念通过解释我用作参考点的一些概念,我至少可以为我的立场提供一些同理心。最接近的是普通 lisp 中的 monkey-patching in javascript. Most commonly used (for me) are macros in clojure. But perhaps most applicable are reader macros。通过这项任务,我试图在某种程度上“校准”我对 Ruby 相对于这些更熟悉、更受欢迎的工具的期望。
- 添加了所有部分协同工作的代码示例。
再次感谢您的解答!希望这些澄清至少能让这个问题读起来不那么令人沮丧。
大部分元编程和内省方法都在Module. define_method
中,对元编程很有用。
但是你的要求很奇怪,模块应该添加功能,而不是悄悄地改变现有的功能。一个更现实的元编程问题会得到更好的答案。也许会问怎么咖喱?
,所以我将介绍如何使用 Ruby 方法。
“静态”在Ruby中不是一个概念,但是如果你想把所有的实例方法变成class方法,将它们包装在class << self
块中。
class TestClass
class << self
def something
puts "hello world"
end
end
end
如果要引用实例方法,请使用 method
。
method = TestClass.method(:something)
method.call()
哦,这会很糟糕,但你自找的!
这是我们正在做的工作:
class TestClass
include Lambdifier
def something
puts "hello world"
end
end
TestClass.something.call
# hello world
# => nil
要解决的第一个难题是在包含 Lambdifier
时 还没有定义任何方法 。 Ruby 的定义依次为 运行。因此,每当将方法 添加 到 class 时,我们将不得不 运行 一段代码。方便地有一个 Ruby 钩子 method_added
可以做到这一点。
另一个问题是,从技术上讲,您可以通过 instance_method
和 bind
解除方法与原始所有者的绑定,然后将它们重新绑定到新方法,但是 Ruby 仍然要求新所有者是相同的 class 或其后代 。我们可以通过从原始 class 创建我们需要的所有者来解决这个问题,有效地使其成为一个单例。 Ruby 的标准库已经提供了这个解决方案,所以我们将利用它而不是重新发明轮子。由于我们需要的方法已经绑定到有效的接收器,因此不需要重新绑定。
可以说这是一个重大偏差,因为这并没有将实例方法附加到它们各自的 class,但这样做会破坏类型系统,因为一个对象的 class 不能保证与其实例类型兼容(尽管实际上,即使是 subclasses 的实例也可能不兼容),所以它不允许的事实可能是更好的。
(此规则至少有一个例外,Class
,它是它自身的一个实例...但您无能为力,因为它是该语言以一种相当特殊的方式对待它。)
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
几点说明:
Ruby中最接近“静态方法”的是“单例方法”。它们本质上是方法,但定义在一个值的“单例 class”上,一个延迟定义的 class(因为它在访问之前不存在)只是为了这个值。 define_singleton_method
本质上是 singleton_class.define_method
。与在 class << self
块中执行 define_method
或 def
相同。
方法的 return 值在技术上不是 lambda,但它们“像 lambda 一样嘎嘎作响”,因为它们是可调用的(有一个 call
方法)。 Ruby 中最突出的可调用项是 proc 和 lambda,但两者都没有 — 这些是 Method
s:
TestClass.something
# => #<Method: TestClass#something>
实例方法仍然以其原始形式存在!如果需要,您可以使用 undef_method
“取消定义”它们。它用 class 中的“墓碑”替换了一个方法,这导致 Ruby 停止沿祖先链向上移动并引发 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
请注意,如果您内联 prior_method
这将不起作用,因为 class 方法将在每次调用时在 instance
上查找它们的对应方法。
您能否使 实例 方法 return “自身的 lambdas” before 使它们“静态”?嗯...对于 return 你不需要参数的“lambda”,所以你必须将 arity 更改为 0,这会使接口不兼容(更是如此,因为 return 类型更改已经发生)。所以可能不是。
您在标题中所要求的是不可能的:
How to modify the behavior of method definitions themselves in Ruby
您不能更改方法定义的行为。它们内置于语言中,无法在 Ruby 中修改语言功能,就像在大多数其他语言中一样。
您的规则 #1 也是不可能的:您正在更改 self
的内容,这意味着对 self
的任何引用很可能会被破坏,因为该方法是在假设下编写的self
是 TestClass
的一个实例,但它会在 self
是 Class
的一个实例的情况下执行。 TestClass
和 Class
具有完全不同的“形状”,因此这几乎可以保证会被破坏。
规则 #2 可能 可以使用 Method#curry
方法实施:
class TestClass
def something(a)
puts a
end
end
unbound_method = TestClass.public_instance_method(:something)
TestClass.define_method(:something) { unbound_method.bind(self).curry }
这真的很简单。事实上,这里的复杂性很大一部分是由于您要求这种转换应该由 mixin 继承触发,这与问题根本无关。
module MakeMethodsLambdas
module LambdaMethodMaker
def method_added(meth)
unbound_method = public_instance_method(meth)
prepend(Module.new do
define_method(meth) { unbound_method.bind(self).curry }
end)
super
end
end
def self.included(mod)
class << mod
prepend LambdaMethodMaker
end
end
end
class TestClass
include MakeMethodsLambdas
def something(a)
puts a
end
end
t = TestClass.new
l = t.something
l.('Hello')
# Hello
设置
我最近开始使用 Ruby,因为我主要编写了非常 functional/compositional JS 和一些免费的 clojure。
我了解到 Ruby 对修改或扩展非常“开放”。 Neato.
我给自己设定了一个挑战,但很快就没有取得任何进展。这可能是因为我还处于“不知道搜索什么术语”的学习阶段Ruby。
我的目标
编写一个模块,使其在使用时做两件事...
- 使 module/class 上定义的所有方法变为静态方法
- 导致在 module/class 上定义的所有方法都变成 return 它们自己的 lambda
具体例子
对于规则 1,这看起来像
class TestClass
include ThingIWannaMake
def something
puts "hello world"
end
end
实际上是
class TestClass
def self.something
puts "hello world"
end
end
而规则 2 是这样
class TestClass
include ThingIWannaMake
def something(a)
puts a
end
end
与
的意思相同class TestClass
def self.something
-> a { puts a }
end
end
导致最终的理想输出(对于第二个输入)是...
class TestClass
def self.something
-> a { puts a }
end
end
问题
这项任务主要是在 Ruby 中作为学习练习进行的,所以我的主要兴趣是那些可以教我如何或为什么不能实现这个目标或其他目标的术语和教程一个喜欢
Edits/Responses
虽然这个问题得到了很好的回答(谢谢!),但大多数反馈都是负面的。请允许我在这里解决一些要点,以供将来可能偶然发现此问题的读者以及对评论本身的回应。
- 选词 - 我使用的许多词和术语 were/are 不正确。最严重的是使用“静态[方法]”。感谢下面的评论,我现在知道在 Ruby 世界中更准确地理解这个概念将是“class [方法]”。我的错误是我作为“Ruby 外国人”仍在学习语言和概念的地方的扩展。我相信这种语言错误是 Ruby 新用户的指示,并且可能很常见。因此,我将根据下面的更正,留下错误,希望我的失败能对以后的学习者有所启发。
- 目标很糟糕 - 虽然我不知道评论回复是否可以改变某人关于什么是或不是“好的编程”的核心信念通过解释我用作参考点的一些概念,我至少可以为我的立场提供一些同理心。最接近的是普通 lisp 中的 monkey-patching in javascript. Most commonly used (for me) are macros in clojure. But perhaps most applicable are reader macros。通过这项任务,我试图在某种程度上“校准”我对 Ruby 相对于这些更熟悉、更受欢迎的工具的期望。
- 添加了所有部分协同工作的代码示例。
再次感谢您的解答!希望这些澄清至少能让这个问题读起来不那么令人沮丧。
大部分元编程和内省方法都在Module. define_method
中,对元编程很有用。
但是你的要求很奇怪,模块应该添加功能,而不是悄悄地改变现有的功能。一个更现实的元编程问题会得到更好的答案。也许会问怎么咖喱?
“静态”在Ruby中不是一个概念,但是如果你想把所有的实例方法变成class方法,将它们包装在class << self
块中。
class TestClass
class << self
def something
puts "hello world"
end
end
end
如果要引用实例方法,请使用 method
。
method = TestClass.method(:something)
method.call()
哦,这会很糟糕,但你自找的!
这是我们正在做的工作:
class TestClass
include Lambdifier
def something
puts "hello world"
end
end
TestClass.something.call
# hello world
# => nil
要解决的第一个难题是在包含
Lambdifier
时 还没有定义任何方法 。 Ruby 的定义依次为 运行。因此,每当将方法 添加 到 class 时,我们将不得不 运行 一段代码。方便地有一个 Ruby 钩子method_added
可以做到这一点。另一个问题是,从技术上讲,您可以通过
instance_method
和bind
解除方法与原始所有者的绑定,然后将它们重新绑定到新方法,但是 Ruby 仍然要求新所有者是相同的 class 或其后代 。我们可以通过从原始 class 创建我们需要的所有者来解决这个问题,有效地使其成为一个单例。 Ruby 的标准库已经提供了这个解决方案,所以我们将利用它而不是重新发明轮子。由于我们需要的方法已经绑定到有效的接收器,因此不需要重新绑定。可以说这是一个重大偏差,因为这并没有将实例方法附加到它们各自的 class,但这样做会破坏类型系统,因为一个对象的 class 不能保证与其实例类型兼容(尽管实际上,即使是 subclasses 的实例也可能不兼容),所以它不允许的事实可能是更好的。
(此规则至少有一个例外,
Class
,它是它自身的一个实例...但您无能为力,因为它是该语言以一种相当特殊的方式对待它。)
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
几点说明:
Ruby中最接近“静态方法”的是“单例方法”。它们本质上是方法,但定义在一个值的“单例 class”上,一个延迟定义的 class(因为它在访问之前不存在)只是为了这个值。
define_singleton_method
本质上是singleton_class.define_method
。与在class << self
块中执行define_method
或def
相同。方法的 return 值在技术上不是 lambda,但它们“像 lambda 一样嘎嘎作响”,因为它们是可调用的(有一个
call
方法)。 Ruby 中最突出的可调用项是 proc 和 lambda,但两者都没有 — 这些是Method
s:TestClass.something # => #<Method: TestClass#something>
实例方法仍然以其原始形式存在!如果需要,您可以使用
undef_method
“取消定义”它们。它用 class 中的“墓碑”替换了一个方法,这导致 Ruby 停止沿祖先链向上移动并引发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
请注意,如果您内联
prior_method
这将不起作用,因为 class 方法将在每次调用时在instance
上查找它们的对应方法。您能否使 实例 方法 return “自身的 lambdas” before 使它们“静态”?嗯...对于 return 你不需要参数的“lambda”,所以你必须将 arity 更改为 0,这会使接口不兼容(更是如此,因为 return 类型更改已经发生)。所以可能不是。
您在标题中所要求的是不可能的:
How to modify the behavior of method definitions themselves in Ruby
您不能更改方法定义的行为。它们内置于语言中,无法在 Ruby 中修改语言功能,就像在大多数其他语言中一样。
您的规则 #1 也是不可能的:您正在更改 self
的内容,这意味着对 self
的任何引用很可能会被破坏,因为该方法是在假设下编写的self
是 TestClass
的一个实例,但它会在 self
是 Class
的一个实例的情况下执行。 TestClass
和 Class
具有完全不同的“形状”,因此这几乎可以保证会被破坏。
规则 #2 可能 可以使用 Method#curry
方法实施:
class TestClass
def something(a)
puts a
end
end
unbound_method = TestClass.public_instance_method(:something)
TestClass.define_method(:something) { unbound_method.bind(self).curry }
这真的很简单。事实上,这里的复杂性很大一部分是由于您要求这种转换应该由 mixin 继承触发,这与问题根本无关。
module MakeMethodsLambdas
module LambdaMethodMaker
def method_added(meth)
unbound_method = public_instance_method(meth)
prepend(Module.new do
define_method(meth) { unbound_method.bind(self).curry }
end)
super
end
end
def self.included(mod)
class << mod
prepend LambdaMethodMaker
end
end
end
class TestClass
include MakeMethodsLambdas
def something(a)
puts a
end
end
t = TestClass.new
l = t.something
l.('Hello')
# Hello