跳至内容
GitHub 仓库 论坛 RSS 新闻提要

自动

Ary Borenzweig

注意:这是一篇愚人节文章。但是,使用 Crystal 宏,你可以做到这一点。

我们 Crystal 开发者认为编译器应该很智能。您不需要在所有地方添加类型注解:只有在需要时,或者当您想要它们时。我们可以让编译器更智能吗?

在过去的一个月里,我们一直在思考,由于 Crystal 是一种相对年轻的语言,标准库和生态系统尚未完善,还有很多东西需要编写代码。不幸的是,许多这些算法和数据结构已经存在于其他语言中。不仅如此,这些其他语言已经使用了很多年,因此它们的实现非常健壮且没有错误。我们将在 Crystal 中走上相同的道路。还是我们会?

嗯,现在不会了。Crystal 的下一个版本将增加一个很小但强大的功能:**auto** 关键字。要了解它是如何工作的,让我们看看它的实际应用。

class String
  auto def succ
  end
end

"hello".succ #=> "hellp"

首先需要知道的是,String#succ 不在 Crystal 的标准库中。在上面的代码中,我们使用 **auto** 关键字定义它,并让主体为空。然后,我们在某个字符串上调用该方法,它给出了正确的值。太棒了!Crystal 不仅推断出 succ 的返回类型,而且还推断出它的行为

**auto** 是如何实现的

当我们说 **auto** 是一个关键字时,我们撒谎了:它是一个宏。Crystal 中的宏接收 AST 节点,即它们接收语法。**auto** 然后接收一个方法定义,并在编译时处理它以生成一个实现所需功能的方法定义。

macro auto(method)
  ...
end

宏可以检查参数:它们可以询问方法的名称、参数或方法定义的位置(在上面的示例中为 String)。如果您需要做更复杂的事情,您可以在宏中调用 **run**,就像这样

macro auto(method){{ run("auto/process", @type, method.name, *method.args) }}
end

这将调用程序 auto/process.cr,并将类型名称、方法名称和展开的方法参数传递给程序。然后,该程序在通常的 ARGV 数组中接收这些参数,处理它们并输出一个方法定义,该方法定义将被嵌入到我们最初的程序中。很不错,对吧?我们对 ECR(类似于 ERB)使用了类似的技术:ECR 模板在编译时进行处理。

auto/process.cr 程序执行了一些操作:它在互联网上搜索相关的函数定义以及它们的源代码,以及可能相关的测试/规范。现在,这仅针对 Ruby 代码执行,因为 Ruby 与 Crystal 类似,但对其他语言的支持即将到来。然后,它处理代码并生成 Crystal 代码。

现在,这可能非常慢。实际上,它需要几秒钟(在我们的一台机器上需要 5 秒钟)。幸运的是,生成的代码缓存在通常的“.crystal”目录中,因此下次对相同类型的相同方法使用 **auto** 时,它将重用缓存的版本。但即使有了这种代价,想想使用 **auto** 节省的时间:您不必编写函数,而且您重用了现有的健壮且经过良好测试的代码!

**auto** 类型

您甚至可以在类型上使用 **auto**

auto class LinkedList(T)
end

list = LinkedList(Char).new
list.push 'a'
list.push 'b'
list.push 'c'
puts list.size #=> 3
puts list        #=> ['a', 'b', 'c']

因此,**auto** 宏实际上会检查接收到的 AST 节点是类还是函数。对于类的情况,auto/process.cr 将在互联网上搜索该类名称,并为它生成一个定义,以及它可以为它找到的每个函数,并重用之前的逻辑。

尝试它

您可以通过查看我们 GitHub 仓库中的 auto 分支 来尝试所有这些,但您需要编译一个新的编译器,因为我们为此功能添加了一些宏方法。请理解,这还很新,因此您遇到的任何错误,请报告!