Ruby 元编程概要

前言

个人总结,方便回忆。偏向自言自语。

Ruby 的概念非常多,重点在于划分,可以让他清楚一些:

OOP 常规部分 self 元编程动态修改的部分 其他辅助功能

最后觉得理解 Ruby 复杂之处的关键就是 self,任何疑难杂症,确认了 self 也就能找到问题的突破口了。

“understanding self is the key to Ruby. Also the key to life”.
                                             —— Dave Thomas

一、Ruby 的面向对象基础

Ruby 的面向对象其实并不特别。基本概念和其他语言保持一致。

1)类、实例、模块

这完全是 普通的面向对象的概念

特别一点的存在:

2)作用域门:class、module、def 彼此互不可见

class 内部 def 通过 @ 实例变量通信 module、class以及 def 之间共通的只有常量

3)块、lambda

lambda 是匿名函数 块,自带周围环境的绑定,即闭包;和 Python、JS 不同,Ruby需要有目的的使用闭包(经过我的理解,闭包是接口预留给用户的)

二、Ruby 的继承

1. Ruby的模式

receiver.message

调用方法被认为是发给 receiver 的消息。

实际上,有一个 current_object 概念就是指当前的 receiver,它会被放在 self 变量里。

程序的执行过程中,遇到 module、class、def、xxx_eval、receiver.message形式显式调用 都会切换 self 执行完毕又会切换回来。

显式调用,self 会被设置为对象,方法会在 对象的链路里查找。

隐式调用,receiver 会被设置为 self ,方法查询就会在 self 的链路中查找。

理解 self 就是至关重要的。

2. receiver Ruby 查找方法的方式是固定的:

查询方式:

1)单例类(如果有的话) 2)类 3)类的继承链条

可以通过执行的方式获得查询路径:

1)单例类: xxx.singleton_class 2) 类以及单继承链: xxx.class.ancestors

3. Ruby 面向对象底层逻辑:

1)Ruby 的一切几乎都是对象 2)Ruby 的 class 也是实例

Ruby 的对象就是一个结构体,包含:

1)flags 2)实例属性 3)指向类

类 保存的是:

1)方法 2)superclass

设计与推论

树形结构

Ruby 只支持单继承;Ruby 的所有类默认继承 Object。

这两个推论不难得出:所有的类会构成一个树形彼此引用的结构;所有的类在 根 层级共享方法。

环形结构

Ruby 在文件中,即不在任何类中的方法,会被定义在 当前的对象 实例 main 上。 Ruby 的所有代码跑在了一个 Object 的实例化对象上面。

这就类似于所有的代码,定义执行在一个 Object 的 实例上下文中


main = Object.new

main.instance_eval {
	// your code
}

所有定义在 文件中的方法,最后都是 main 的 private 方法。

所有 后期复杂自定义类的 实例,寻找方法的时候,会沿着 祖先链条查找,最后会查到 Object,然后会访问到 Object 的私有方法,最后会访问到 main 上下文中定义的方法。

所有方法一个不剩,都会被查找一圈。有一个美丽的环形结构。

同时告诉我们,尽可能不要在 文件的最外层上下文中定义方法,这个和 Kernel 方法无异。

4.单例类

每一个对象除了类定义时候的方法,还有一个空间 —— 单例类

可以保存属于他自己的方法的地方

1) def 定义
def instance_name.xxxx

end
2) “class «” 定义形式
class << instance
	def xxx
	end
end

定义 【instance】 的 单例类

class << 【instance】, 可以用下面几种方式,来说服自己理解这个丑陋的语法:

可以认为是 class << 是 单例类 可以认为这种特殊语法 把 【instance】切换成了当前 self 可以认为这种特殊语法,把方法注入到 【instance】的单例类中

PS: 由于 self 的存在,为了通用性 【instance】 通常会是上下文中的 self

三、Ruby 特殊的地方

Ruby 特殊的地方就是 元编程 能力,可以改变自身。 讲清楚 self 就可以讲明白 元编程

程序的执行者,就是 数据+函数;而面向对象把这两个封装成了: 类(包含:数据、方法) 元编程,是动态的塑造 类 的能力。

(笔者:没有 meta-meta programming 的原因,因为再抽象一层意义不大 :P)

我们知道了class、以及实例查找方法的固定套路、以及self

元编程由几个部分组成

1)动态调用方法

除了语法的声明式,还有内置的 API 可以调用做相同的事情。

define_method
define_class
send
# 。。。

2) 类宏: 类方法,在类声明的上下文中执行,结果是生成定义的实例方法

理解类宏的关键是:

类也是实例 类的声明部分,也在执行代码 类方法除了被单独外部调用,也可以在类声明部分执行,用来生成实例方法

实际上只有 Ruby 精心的预留设计,才能促成这样的结果。 这些特点是有意而为之。

# 常见内置类宏举例
attr_accessor
alias

3)xxx_eval :eval的特点是可以执行字符串、block;最重要的差距是,他会改变 self

xxx_eval 的内部会切 self

class_eval 只在 class 中使用 eval 是 字符串和传入绑定 instance_eval 最常用,他总是在定义单例类方法 由于一切都是对象:普通对象定义的就是实例方法、如果定义在类上,就等于类方法

xxx_eval 可以接入 self 的作用域

4)modules 与 mixins

类中:

include:把方法放入当前 class 的继承链条,如果在 class 初始化中执行等同于拓展了 实例方法 extend:把方法放入 当前的单例类, class 初始化中执行等同于拓展了 类方法

至于其他的 prepend、refinement 都是辅助

5)Hooks

一些周期的钩子,比如继承

6)自省

通过读取属性,来判断 配合 =~ 内部正则来工作

7)proxy:xxx_missing

method_missing
const_missing
respond_to_missing
# 。。。

在这里可以收集失败方法,使用 其他 1)~ 6)

* 自省,来判断方法
* hooks,来判断注入阶段
* define,动态定义方法
* send,动态发送方法
* eval,求值
* 类宏,可以使用来定义方法
* super 交还给父方法,不带参数等于继承所有参数

动态生成新的类、方法

四、有效的方法 self

Ruby 的执行,用远在围绕着 self

当遇到困惑的时候:

在当前代码中,打印 self; 或者使用 instance_eval 中 打印 self

self 作为探针,可以反映出当前正在工作的对象

self 可以按图索骥:

self.class
self.class.ancestors
self.singleton_class

来查询当前的工作状态

五、DSL

self 和 instance_eval 的神奇组合

这是由 Ruby 的几个规则设定,组合下的产物:

1)由于上下文中一直存在 self 2)隐式调用的方法,会默认调用当前的 self 方法 3)instance_eval 是会切换到 当前 self 到 instance 的上下文

这样外部的 block 可以省略 receiver 直接书写 instance 内部的方法名

这样就是 DSL 的书写范式


class Demo
  attr_accessor :cache

  def initialize(&block)
    @cache = {}
    instance_eval(&block)
  end

  def attr(name, value)
    @cache[name] = value
  end
end

d = Demo.new do
	# 这里被eval 执行,这里的 self 是对象的上下文
	# 这里 调用 attr 省略了调用者,呈现出语言形态
	# 这部分就是 DSL
	# 这部分的 程序,也可以读取文件的代码,以DSL 形式书写的文件,进行读取
  attr :hobby, "programming"
end

puts d.cache

总结

Ruby 丰富的 feature,是刻意设计,有意制造出这么多的组合效果。继承 Lisp 的基因。可以持续变化自身的语言。

Ruby 设计思想非常深邃。

面向对象,强行的规定所有变量只能通过方法获取。这是使用了一种设计模式,最大可能应对未来变化。 复用结构,有 class、module,可以动态的调整模块的继承顺序。环形查找结构、树形的继承结构。 使用 Module管理模块,潜在为了配合:Monkey patching 所有语言构建都可以改变;每一个语言组件都是对象,都可以动态的添加、删除方法。 DSL,eval,proxy 语言可以动态的求值,根据输入动态的生成方法 ……

Ruby 很适合抽象程度高的目标。

文章来源:

Author:Mark24
link:https://mark24code.github.io/ruby/2023/10/18/Ruby-元编程摘要.html