介绍
通常是创建有用的软件的人,我们倾向于将自己视为为神话“用户”编写软件的人。 “用户”点击一个按钮,
发生了一些神奇的事情。这通常是作为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操作的事情。
前奏,为什么要元?
为了说明这个概念,让我们看看如何添加语法以创建constructor的dynamic 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
有很多事情发生,但要注意的重要一点是, 在像Ruby一样,我们拥有 现在,让我们继续前进。这里的一切都生活在一个名为koude20的混合项目中,并定义了宏的公共API: 通过实施这些规格,我们可以安全地使用stdlib等诸如 不幸的是,截至撰写本文时, 因此,这个玩具示例的一个严重限制是它仅适用于 您可以找到此示例here的完整来源,请告诉我如果您有评论,找到了错误或错字。感谢您的阅读! [1] Python3的优秀 [2] rubyvm :: 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] [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 p>
[11]正好汇编:https://en.wikipedia.org/wiki/Just-in-time_compilation RSpec::Core::ExampleGroup
是一个在测试跑步者的运行时修改的对象,它指定了describe
含义的域特定语言的语言结构。<<<<<<<<<<<<<<<< /p>
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
}
}
在长生不老药中构建(动态的)阵列“构造函数”
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
,它是围绕koude23和ExVec.Vector
的薄包装纸,它是一款NIF包装器,它利用Rustler的Encoder
和Decoder
来编码Elixiir List
作为List
作为Vec
,然后将Elixir类似于Elixir的eLixir,然后在Array上实现Elixir,通过定义:
Access
行为Enumerable
的协议实施
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
整数:)我还掩盖了一些默认的行为和协议。
参考
ast
库:https://docs.python.org/3/library/ast.html hook pattern
的真棒示例陷入Ruby的对象生命周期:https://github.com/rspec/rspec-core/blob/main/lib/rspec/core/hooks.rb