1 个不稳定版本
0.0.0 | 2021年6月2日 |
---|
#19 in #方言
10KB
Monty
一个强类型 Python 方言
索引
简介
Monty (/ˈmɒntɪ/)
是尝试提供一个完全有机的 Python 替代方言,它配备了一个更强大、更安全、更智能的类型系统。
从高层次来看,Monty 可以与 TypeScript 对 JavaScript 所做的工作进行密切比较。然而,Monty 和 TypeScript 之间的核心区别在于,TS 是 JS 的严格语法的超集,Monty 是 Python 的严格语法的子集;这意味着 TS 向 JS 添加了不兼容的语法,而 Monty 以向后兼容的方式禁止了现有的 Python 语法和语义。
Monty 的目的是通过使用 cranelift(以及如果支持的话,llvm)编译成本地可执行二进制文件或 WASM。
构建编译器
为了构建,您需要一个最新的 nightly 版本的 rust (1.53.0-nightly
或更高版本)。
之后,只需运行 cargo build
使用编译器
强烈建议使用 clang
并启用 mold
链接器。我建议您下载并安装它们,因为(尤其是 mold)将提高编译时性能的最后一步。
请确保使用 --help
检查编译器的帮助命令,因为它将比这个示例更更新。
编译器旨在易于使用,运行以下命令将生成一个主要静态链接的二进制文件 ./file
(如果使用 Windows,则为 ./file.exe
)
./montyc ./path/to/file.py
您还可以通过 --cc="path/to/cc"
指定本地 C 编译器的路径,并通过 --ld="path/to/ld"
指定链接器
关于如何保持动态感的思考。
除了解释器 Python 的常规语义外,Monty 还将选择性禁止语言的部分(取决于该功能转换为编译代码的难度)。
"自动联合"
在单个对象中能够表示多种类型的值是非常有用的。咳嗽 咳嗽 多态性 咳嗽 咳嗽
在Monty中,变量在每个作用域内只能有一个类型。您不能将不同类型的值重新分配给变量。
def badly_typed():
this = 1
this = "foo"
但是,您可以有类型联合,这就像C中的标记联合或Rust中的枚举。下面是Python显式注释联合的方式:typing.Union[T, ...]
,但在Monty中,您可以使用来自PEP604的新字面量语法:T | U
def correctly_typed():
this: int | str = 1
this = "foo"
但是等等!假设你有以下代码
def foo() -> int:
return 1
def bar() -> str:
return "foo"
def baz(control: bool):
x = foo() if control else bar()
现在函数baz
中的x
的类型是什么?
有些人可能会期待这会是一个类型错误,因为foo
和bar
返回了不兼容的类型,他们试图将它们与x
相关联,但这并不是问题,除非你显式地将x
注释为int
或str
之一。
相反,编译器会为你“合成”一个联合类型,所以x
的类型将是
int | str
或联合[int, str]
.
"类型缩窄"
类型缩小 不是一个新概念,它已经在类型检查器中存在了一段时间。
大致的想法是,你可以通过类型守卫将联合类型分解为其变体之一。
x: int | str | list[str]
if isinstance(x, int):
# x is now considered an integer in this branch of the if statement
elif isinstance(x, str):
# x is now considered a string here.
else:
# exhaustive-ness checks will allow `x` to be treated as a list of strings here.
"偏离实例类型"
受到RPython文档的这一部分的启发。偏离实例类型的工作方式非常相似。例如,考虑以下类
class Thing:
attr1: int
attr2: list[str]
类Thing
的内存布局,我们称之为“布局1”,将包含一个整数和一个列表,默认情况下,这就是对这个类的所有说明,因此目前Monty无法对此做任何事情,其他属性访问(无论是获取还是设置)将引发类型错误并报告给用户。
这就是“偏离”一个“实例”的“类型”的想法所在
THING = Thing()
THING.attr3 = "blah blah"
这个值在运行时会保持不变,但在编译时会延迟初始化,但这不是重点。
THING
是Thing
的一个实例,这意味着常量的布局由类定义指定。但我们尝试在实例上设置一个属性,乍一看这好像是一个错误,但实际上它让我们能够用Monty非常巧妙地处理值和类型。
THING
的内存布局现在是“布局1, 0”(读作从1开始的第一个偏离布局)并在粗略的C伪代码中将结构组织为
struct Thing_Layout_1 {
integer_type attr1;
list_str_type attr2;
}
struct Thing_Layout_1_0 {
Thing_Layout_1 head;
string_type attr3;
}
如果THING
不是一个常量,而是一个静态模块级别变量,那么您也可以像下面这样设置和修改THING.attr3
的值
thing = Thing()
def whatever(blah: str, n: int):
thing.attr3 = blah * n
编译时(或“comptime”)执行
常规Python和Monty之间最大的区别是模块级的评估方式。
Python是惰性的,所有内容都是在访问时运行的,毕竟模块的作用域仍然是一个大块可执行代码,可以将其视为一个操作隐式模块对象的函数。
Monty 将模块的全局作用域视为一个常量声明的巨大池子。但出于明显的原因,这并不适用于现有的代码和语义。为了弥合这一差距,montyc 内部有一个基于 AST 的解释器,用于在模块的全局作用域内执行代码。
假设大多数全局作用域级别的逻辑都充当一种“初始化粘合例程”,那么用户可以随心所欲,只要
-
执行在已知数量的“ticks”内完成(这样我们就不至于意外进入一个永远不会结束的无限循环。)
-
模块的全局作用域状态在语义上是正确的(类型检查器将在模块的 comptime 执行完成后验证模块。)
当然,在完全动态的环境中,我们不需要像常规编译代码那样限制用户,因此在这种情况下,大多数在正常情况下会被拒绝的东西都是完全可以接受的,例如:exec
、eval
、globals
、locals
、动态类创建以及无类型参数的函数。