Wait the light to fall

改造 tie 以支持可扩展性

焉知非鱼

2000年9月7日提出,2000年9月20日冻结,取决于 RFC 159: True Polymorphic Objects 2000年8月25日提出,2000年9月16日冻结,也是由Nathan Wiger提出的,之前已经在博客中提到过。

tie 到底是什么? #

RFC 200 是关于扩展 Perl 提供的 tie 函数。

Perl 中的这个功能允许人们将程序逻辑注入到系统对标量、数组和哈希等的处理中。这是通过给数组等数据结构分配一个包的名称来实现的(也就是绑定)。然后,该包要提供一些子程序(如 FETCHSTORE),这些子程序将被系统调用,以实现对给定数据结构的某些效果。

因此,它被 Perl 的一些核心模块(如线程)和 CPAN 上的许多模块(如 Tie::File)所使用。Perl 的 tie 函数仍然存在 RFC 中提到的问题。

都是绑定的 #

在 Raku 中,所有的东西都是一个对象,或者可以被认为是一个对象。系统需要对一个对象做的一切事情,都是通过它的方法来完成的。在这个意义上,你可以说,Raku 中的所有东西都是一个绑定的对象。幸运的是,Rakudo(Raku 编程语言最先进的实现)可以识别出对象上的某些方法实际上是系统提供的方法,并在编译时实际创建捷径(例如,当分配给一个有标准容器的变量时:它实际上不会调用 STORE 方法,而是使用一个内部子程序来达到预期的效果)。

但除此之外,Rakudo 还具有在程序执行过程中识别热点代码路径的能力,并对这些路径进行实时优化。

Jonathan Worthington 就这个过程做了两场非常精彩的演讲。从2017年的去优化如何帮助我们更快,以及2019年的性能更新

因为在 Raku 中所有的东西都是一个对象,并且通过这些对象的类的方法进行访问,这使得编译器和运行时能够更好地掌握程序中实际发生的事情。从而获得更好的优化能力,甚至在某些时候优化到机器语言级别。

因为在 Raku 中一切都被 “绑定"了(用 Perl 过滤过的眼镜来看),将程序逻辑注入到系统对数组和哈希的处理中,可以简单到只需子类化系统的类,并提供一个系统使用的标准方法的特殊版本。假设你想在你的程序中看到当一个元素从一个数组中获取时,只需要添加一个自定义的 AT-POS 方法:

class VerboseFetcher is Array {    # subclass core's Array class
    method AT-POS($pos) {           # method for fetching an element
        say "fetching #$pos";        # tell the world
        nextsame                     # provide standard functionality
    }
}
 
my @a is VerboseFetcher = 1,2,3;   # mark as special and initialize
say @a[1];  # fetching #1␤2

Raku 文档中包含了一个概述,说明要模拟一个 Array模拟一个 Hash 需要提供哪些方法。顺便说一下,关于通过索引或键来访问数据结构元素的整个词条是推荐给想要了解 Raku 内部的这些方面的人阅读的。

没什么特别的 #

在一篇关于 RFC 168 的博文中,关于让事情变得不那么特别,已经提到了在 Raku 中真的没有什么特别的。而且(几乎)语言的所有方面都可以在一个词法范围内被改变。所以,上面的例子对 Array 类所做的事情,也可以做给任何一个 Raku 的核心类,或者任何其他从生态系统中安装的类,或者你自己编写的类。

但是,要提供完全模拟数组或哈希所需的所有逻辑,可能会让人不知所措。特别是当你第一次尝试这样做的时候。因此,生态系统其实有两个模块的作用,可以帮助你解决这个问题。

这两个模块只需要你在一个类中实现5个方法,完成这些作用,就可以得到数组或哈希的全部功能,完全可以根据自己的喜好进行定制。

事实上,Raku 对语言可定制性方法的灵活性,实际上允许在 Raku 中实现 Perl 的 tie 内置函数。所以,如果你要将 Perl 的代码移植到 Raku 中,而且相关代码使用了 tie,你可以使用这个模块作为快速的中间解决方案。

问题已经解决了吗? #

让我们看看 RFC 200 中提到的 tie 的问题。

  1. 它是不可扩展的,你只能使用那些已经用 tie 钩子实现的函数。

Raku 是完全可扩展的,在其实现的(几乎)所有方面都是可插拔的。对于哪些类可以扩展,哪些类不能扩展,没有任何限制。

  1. 任何额外的功能都需要混合调用绑定接口和 OO 接口,这就破坏了一个主要目标:透明度。

在 Raku 中,所有的接口都使用方法,因为所有的东西都是一个对象,或者可以被认为是一个对象。类和方法的使用对任何使用 Raku 的程序员来说都应该是清楚的。

  1. 它的速度很慢。事实上,非常慢。

在 Raku 中,执行过程中都是一样的速度。而且每一个自定义都会像 Raku 中的其他代码一样,从同样的优化功能中获利。并会在最后,尽可能的优化到机器代码。

  1. 你不能轻易地把 tie 和运算符重载整合在一起。

在 Raku 中,operator 是多重分派子程序,允许添加自定义类的额外候选。

  1. 如果定义绑定和 OO 接口,你必须定义重复的函数或使用 typeglobs。

在 Raku 中不存在 typeglobs。在 Raku 中所有的接口都是通过提供额外的方法(或者在运算符的情况下提供子程序)来完成的。不需要重复劳动,所以没有这样的问题。

  1. 语法的某些部分是,嗯,笨拙的。

也许有人会说,Perl 的笨拙语法在 Raku 中已经被另一种笨拙语法所取代。这可能是看人的眼光。事实上,在 Raku 中注入程序逻辑的语法,与在 Raku 中做的任何其他子类或角色混合没有什么不同。

结论 #

RFC 159 中的任何内容实际上都没有按照最初建议的方式实现。但是,上述问题的解决方案都在 Raku 中得到了实现。