一直是乐高积木
#go #rust #ruby #elixir

介绍

通常是创建有用的软件的人,我们倾向于将自己视为为神话“用户”编写软件的人。 “用户”点击一个按钮,
发生了一些神奇的事情。这通常是作为abstraction的倒数。
摘要在我们的软件中都在我们周围,聪明的程序员通常为其他程序员创建良好的抽象来管理复杂性。

一个非常普遍的例子是一个Application Programming Interface,它允许两个“应用程序”在某些传输中共享有用的数据,而对于如何使用此数据,则可以通过平台无关。像API一样,还有其他有趣的抽象 - 让我们通过发明语法来剥夺语言创建者和语言用户之间的抽象

这涉及用于理解计算的范式的微妙转变,在 core 上是将计算视为数据的想法。我猜对大多数人
首先阅读时典型的心理模型主要是 pressation ,这是一种自上而下的扫描,熟悉语法和语义,然后在
中发生了另一个重要的转变 通过引入并发和并行性了解运行时执行,在这里,我们将剥落 compile time runtime

之间的层

编译时间会在程序文本被“解析和转换”为多种形式的过程中,一直到零件和运行时
是当该程序实际上执行IE“运行”时,在将程序视为对其他程序的文本输入和运行时的程序本身的范式中,被称为元编程。

 This distinction between what is "compile" and "runtime" is
 a useful mental model illustrated here for simplicity, odds
 are what's happening in your favorite language is probably 
 more interesting![11]

在我们开始之前,请注意。尽管该技术广泛适用于大多数现代语言 - 实现的特征均等,但我将主要尝试与Go的reflection和Rust的macro system一起提供替代示例,同时向Cpython[1]点头向Cpython[1],Ruby Mri[2]和一些Javascript [3],但note obiaoqian7) p>

计算是数据

考虑例如谦虚的eval()函数:

// javascript
console.log(eval('2 + 2'));
# python
print(eval('2 + 2'))
# ruby
puts eval('2 + 2')

计算2 + 2表示为数据,在这种情况下为a string。这有点整洁吗?但是我们可以采取这么多的效果。
如果您对这里发生的事情的更多详细信息感兴趣,请查看crafting interpreters

由于编程语言包含各种元素,例如表达式语句,我们需要一些
保留有关程序或计算要做什么的信息,此内部表示最常被称为抽象语法树。

冒着过度简化的风险,将AST视为有意义地表示程序的文本源的一种方式,有时您可以在程序的文本源上做类似string interpolation操作的事情。

前奏,为什么要元?

为了说明这个概念,让我们看看如何添加语法以创建constructordynamic array中的dynamic array

首先,一些背景。长生不老药是一种(主要是)功能性语言,具有(主要)不变的数据架构,它不鼓励使用
或像大多数功能性语言一样提供动态数组,作为一个实现一个
需要随机访问通过可变状态读/写入。它也没有“构造函数”,一个典型的模式正在创建从
返回的结构化数据 功能和"piping"通过其他几个功能:

defmodule MyApp.Array do
  alias MyApp.Array

  defstruct field: nil

  def new(options \\ []) do
    %Array{field: options}
  end
end

iex(1)> MyApp.Array.new() |> do_stuff() |> do_other_stuff()

在此示例中,我们将通过
来摆脱Rust Standard Library的Vector 在长生不老药中创建foreign function interface,并在erlang stdlib中实现的数据结构,以重新创建类似vec!

之类的东西

我们会看到数据结构的“后端”实现并不重要,因此它在Rust或Erlang中无关紧要,我们专注的是提供易于使用的语法抽象
公共数据结构。

这是一个简化的版本。

#[macro_export]
macro_rules! vec {
    ( $( $x:expr ),* ) => {
        {
            let mut temp_vec = Vec::new();
            $(
                temp_vec.push($x);
            )*
            temp_vec
        }
    };
}

在这里,我们看到了一个模式匹配的( $( $x:expr ),* ),就像我们的谦虚的eval('2 + 2')一样,而不是将计算表示为字符串,而是像数据结构一样的树
我们可以在编译时间断言的地方,如果某些代码看起来像是我们认为的样子,请用匹配臂中的内容替换它,
一个称为macro expansion的过程。

在长生不老药中,我们可以写一些类似的东西,图案匹配是三元素样式的tuple[5]

{node, execution_context or meta_data, arguments}

GO和Ruby具有一些表面的相似性,因为它们的元编程API并不能直接访问AST。在像RSpec这样的红宝石库中,rails路由器和erb HTML模板经常通过“ Monkey Patching”使用元编程技术 - 在 Run> Runtime 上修改
Object[6]的属性以及由于Ruby的极度动态键入 [7]解释世界,没有“编译时间扩展”的概念,因此您在运行时具有强大的内省和锻造性,从而产生了几乎像hooks[8]的模式通过对象属性(语法与否)关于语言的任何内容。

describe公共API的rspec-core中获取这款小excerpt[9]

# @private
def self.expose_example_group_alias(name)
  return if example_group_aliases.include?(name)

  example_group_aliases << name

  (class << RSpec; self; end).__send__(:define_method, name) do |*args, &example_group_block|
    group = RSpec::Core::ExampleGroup.__send__(name, *args, &example_group_block)
    RSpec.world.record(group)
    group
  end

  expose_example_group_alias_globally(name) if exposed_globally?
end

有很多事情发生,但要注意的重要一点是,RSpec::Core::ExampleGroup是一个在测试跑步者的运行时修改的对象,它指定了describe含义的域特定语言的语言结构。<<<<<<<<<<<<<<<< /p>

在像Ruby一样,我们拥有reflection,可以允许运行时内省,与Ruby不同,它是静态键入和编译的。反射给出了刚性类型系统的临时“逃生舱口”,并允许基于动态interfaces进行修改。我可以找到的最惯用的例子是打印family[10]函数,例如fmt.Sprintf


func (p *pp) doPrint(a []any) {
 prevString := false
 for argNum, arg := range a {
  isString := arg != nil && reflect.TypeOf(arg).Kind() == reflect.String
  // Add a space between two non-string arguments.
  if argNum > 0 && !isString && !prevString {
   p.buf.writeByte(' ')
  }
  p.printArg(arg, 'v')
  prevString = isString
 }
}

在长生不老药中构建(动态的)阵列“构造函数”

现在,让我们继续前进。这里的一切都生活在一个名为koude20的混合项目中,并定义了宏的公共API:

defmodule ExVec do
  alias ExVec.{Array, Vector}

  defmacro __using__(implementation: impl) do
    quote do
      import unquote(__MODULE__)
      Module.put_attribute(__MODULE__, :implementation, unquote(impl))
    end
  end

  defmacro vec!([_h | _t] = args) do
    quote bind_quoted: [args: args] do
      dispatch_constructor(@implementation, args)
    end
  end

  defmacro vec!({:.., _, [first, last]}) do
    args = Range.new(first, last) |> Enum.to_list()

    quote bind_quoted: [args: args] do
      dispatch_constructor(@implementation, args)
    end
  end

  def dispatch_constructor(impl, args) do
    case impl do
      :rust -> Vector.new(args)
      :erlang -> Array.new(args)
      _ -> raise "invalid constructor type, did you mean :rust?"
    end
  end
end

ex_vec库有两个后端ExVec.Array,它是围绕koude23ExVec.Vector的薄包装纸,它是一款NIF包装器,它利用Rustler的EncoderDecoder来编码Elixiir List作为List作为Vec,然后将Elixir类似于Elixir的eLixir,然后在Array上实现Elixir,通过定义:

  1. Access行为
  2. Enumerable的协议实施

通过实施这些规格,我们可以安全地使用stdlib等诸如Enum甚至Stream的事物,就像在任何其他Elixir Project中一样
并让客户在保持宏的语法时选择后端:

defmodule MyApp.DoStuff do
  alias ExVec.Vector
  use ExVec, implementation: :rust


  def len do
    # serialised as a rust Vec<i32>
    vec!(1..4) |> Enum.count()
    vec!([1, 2, 3, 4]) |> Enum.count()

    # plain old linked-list
    [1, 2, 3, 4] |> Enum.count()
  end

  def random_access do
    # O(1) read
    my_array = vec!(0..10)
    my_array[5]

     # serialised random write access
    Vector.get_and_update(my_array, 0, fn n -> {n, 42} end)
  end
end

defmodule MyApp.DoOtherStuff do
  use ExVec, implementation: :erlang

  def len do
    # this is an erlang :array!
    vec!([1, 2, 3, 4]) |> Enum.count()
  end
end

不幸的是,截至撰写本文时,rustler does not support通用类型,所以i
猜猜这是不可能的吗?

#[derive(Debug, NifStruct)]
#[module = "ExVec.Vector"]
pub struct Vector<T> {
   fields: Vec<T>,o
   size: isize
}

因此,这个玩具示例的一个严重限制是它仅适用于i32整数:)我还掩盖了一些默认的行为和协议。

您可以找到此示例here的完整来源,请告诉我如果您有评论,找到了错误或错字。感谢您的阅读!

参考

[1] Python3的优秀ast库:https://docs.python.org/3/library/ast.html

[2] ruby​​vm :: ast:https://ruby-doc.org/core-trunk/RubyVM/AST.html

[3] javascript(自ecmascript6):https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect

[4]打字稿:https://basarat.gitbook.io/typescript/overview

[4] go as as:https://pkg.go.dev/go/ast

[5] elixir的ast:https://github.com/elixir-lang/elixir/blob/d8f1a5d6b653c14ae44c6eacdbc8e9df7006d284/lib/elixir/pages/syntax-reference.md#the-elixir-ast

[6]一个true(有用)对象全部统治它们:https://ruby-doc.org/3.2.1/Object.html

[7]红宝石扩展:https://docs.ruby-lang.org/en/master/extension_rdoc.html#label-Basic+Knowledge

[8] hook pattern的真棒示例陷入Ruby的对象生命周期:https://github.com/rspec/rspec-core/blob/main/lib/rspec/core/hooks.rb

[9] RSPEC公共DSL模块:https://github.com/rspec/rspec-core/blob/main/lib/rspec/core/dsl.rb

[10] doprint https://cs.opensource.google/go/go/+/refs/tags/go1.20:src/fmt/print.go;drc=261fe25c83a94fc3defe064baed3944cd3d16959;l=1204

[11]正好汇编:https://en.wikipedia.org/wiki/Just-in-time_compilation