笔记:Ruby可调用对象实验

观点

本来我的观点应该放在最后,但是这个实验不太复杂。而我下次可能也是直接看上面的。所以我简单写一点。

Ruby的内部无疑是很复杂的,《Ruby原理剖析》虽然已经写的很好了,然是依然是建立在大幅简化的条件下进行讲解。即使看完我们也许依然很难真正理解Ruby的工作原理。具体的还需要查询源码。

不过我觉得,最近看书自己存在一个误区,或者获得了一个新的想法。

我们想要学习和理解一个工具,更看重的是他的原理和最后黑盒的表达罢了。据个例子就像开车,你大概知道了内燃机、电动机原理,那么你踩了油门车子起步,踩了刹车车子停止。这个技能就算学会了。至于汽车内部有多么复杂,用了什么黑科技保驾护航,研究他们那是另一个课题了。

类似,我这几天要么在入门书籍里面的一些不严谨的比喻、概念里面建立抽象的心理模型、抑或是进入《剖析》这种又是大量细节的内容里。最终有一种迷失感。

但是这件事其实是没那么复杂的。只需要跳出来思考。

我沉淀的一个观点是 —— 我们应该理解他的意图。

Ruby的设计再复杂也伴随着一个目的。从Ruby的表现和应用实际上他是想表达一个简洁、又自动化搜索(深邃)的东西。

其他的语言诸如JS是一种很混乱的存在。Ruby的作者Matz是语言的狂热粉,他写过很多书表达自己的设计思考还有对其他语言的观察。

Ruby的东西是可以跳出来思考的——把你自己站在设计者的角度、抑或是使用者的角度,去思考合理性。

比如可调用对象这部分。后面会总结,也是书本的摘要。我这里就直接简化的理解:

Ruby里面 Class 其实是一种封装结构,是为了一种面向对象的风格。

Ruby里面 承担起主要逻辑(就像JS里面的function)其实是 块(Block)

块 就是 do ~ end 这部分代码。他像代码一样工作。在定义的词法作用域里工作。

Proc是底层支持块的更朴素的类,相对来说一切更加宽松(或者说方法少。越继承方法约多约束也越多)。他也是在词法作用域里工作。

块和Proc是相似的。

其实就可以理解为 块 是我们书写的代码。 Proc是一段代码对象。

Lambda就是 Proc的对象,表现的就像匿名函数。更确定的存在。

Method可以说就是一个具名函数。

Lambda和Method是相似的。

其实就可以理解为 Lambda是匿名函数,Method是具名函数。


可调用对象总结

  • 代码块

代码块不是真正的对象,但是他们是 可调用的,在定义他们的作用域中执行。

  • proc

Proc类跟代码块一样,也在定义自身的作用域中执行

  • lambda

也是Proc对象,但是他跟普通的proc有细微差别,他跟块和proc一样都是闭包,因此也在自身的作用域中执行

  • 方法

绑定于一个对象,在所绑定的对象作用域中执行。他们也可以和这个作用于解除绑定,然后再重新绑定到另一个对象的作用域。

在方法和lambda中,return 语句从可调用对象中返回。(自身可作为函数看待)

在块和proc中,return从定义可调用对象的原始上下文中返回。(词法代码)

lambda、方法对参数严格。proc宽松。

Proc 和 Lambda 的差异性

1. return 之处返回

Lambda中 return仅仅表示从这个 lambda 中返回

其实 Lambda 就是一个匿名函数(类比JS里面的function(){} 行为保持一致)


lambda_obj = lambda { return 10 }

def double(lambda_obj)
  puts lambda_obj.call * 2
end

double(lambda_obj)

# output >>>
# 20

Proc是在定义Proc的作用域中返回(词法位置返回)。简单的说 Proc像一个代码一样,嵌入上下文中。

def double()
  p_obj = Proc.new { return 10 }
  result = p_obj.call
  return result * 2 # 这端代码不可到达
end

puts double()

# output >>>
# 10

Lambda and non-lambda semantics¶

TODO直接中断?????

def double(p_obj)
  puts '--in double-'
  result = p_obj.call
  puts '--result---'
  return result * 2 # 这端代码不可到达
end

p_obj = Proc.new { puts '--in proc--'; return 10 }
puts p_obj

result = double(p_obj)

puts result

# output >>>>>
# #<Proc:0x00007fece8912468 call.rb:9>
# --in double-
# --in proc--

对这个结果直接中断我表示不太理解 TODO

2. 参数差别

lambda对参数数量有严格限制。

理解 lambda是一个匿名函数就能很好的理解这个了。函数就要有函数的样子嘛。

Proc则无所谓,参数都会变成数组,超出定义会裁减掉,少于定义会变成 nil

总结

Lambda是你的第一选择,他工作起来就像一个匿名函数。严格的像一个方法。

除非你需要Proc的特性,再去用Proc

Method对象

Ruby的方法貌似可以裁剪。

如果说 JavaScript 其实是可以围绕着函数去做完所有事情的(本质是函数,类只是 数据+函数的封装而已,最本质还是执行栈的函数)。Ruby其实也在围绕函数。

Ruby给我的感觉是,它已经准备好了很多方法(函数)绑定在语言基础上。可以供我们搜寻使用。我们自己建立起来的对象、类的方法,也可以搜寻使用。我们不光要关心我们要写的方法,也可以最大限度组合和利用已经存在的方法。一个不恰当的比喻,C语言是拿着一个镐子去干活,一切都要靠自己写或者引用过来联系,Ruby就是背了一个工具箱。工具箱比较智能,还会根据名字匹配工具(方法)。


class A
  def initialize(name)
    @name = name
  end
  def speak_name
    puts @name
  end
end

obj = A.new('AAA')

m = obj.method :speak_name

m.call

# output >>>>

# AAA

Kernel#method 方法可以获得一个用Method对象表示的方法,可以在以后使用 Method#call来调用。

Kernel#singleton_method 方法可以把单例方法转成Method对象。

Method对象类似 代码块或者lambda,可以通过 Method#to_proc 方法将Method转为 Proc。

另外也可以通过 define_method 方法把代码块转化为方法。

他们之间有个重要区别:

  1. lambda 在定义他的作用域中执行(闭包)
  2. Method对象会在他自身所在对象的作用域中执行

自由方法(unbound method)

他跟普通方法类似,不过他从最初定义它的类或者模块中脱离了。

Module#unbind 方法,可以把一个方法变成自由方法。

也可以直接调用 Module#instance_method 获得一个自由方法

module A
  
  def my_method
    'hello'
  end
end

unbound = A.instance_method :my_method

puts unbound.class

unbound.call

# output >>>>>

# UnboundMethod
# Traceback (most recent call last):
# call.rb:12:in `<main>': undefined method `call' for #<UnboundMethod: A#my_method() call.rb:3> (NoMethodError)

UnboundMethod是无法调用的

虽然不能调用,但是可以把它绑到一个对象上,再次成为一个Method对象


module A
  
  def my_method
    puts 'hello'
  end
end

unbound = A.instance_method :my_method

puts unbound.class


class B

end

B.send :define_method, :new_my_method, unbound

B.new.new_my_method


# output >>>>
# UnboundMethod
# hello

自由方法,意味着我们连方法都可以进行 “裁剪”操作了。

使用的场景可能很特殊。举个例子比如 我们想要很精细的 调整方法的顺序的时候。可以派上用场。

(JS也可以通过 call、apply来转移方法的执行。很相似)

Mark24

Everything can Mix.