#python #方言 #编译 #类型化 #编译器 #cranelift #monty

app montyc

一个强类型、灵活且编译的 Python 方言

1 个不稳定版本

0.0.0 2021年6月2日

#19 in #方言

MIT 许可证

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的类型是什么?

有些人可能会期待这会是一个类型错误,因为foobar返回了不兼容的类型,他们试图将它们与x相关联,但这并不是问题,除非你显式地将x注释为intstr之一。

相反,编译器会为你“合成”一个联合类型,所以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"

这个值在运行时会保持不变,但在编译时会延迟初始化,但这不是重点。

THINGThing的一个实例,这意味着常量的布局由类定义指定。但我们尝试在实例上设置一个属性,乍一看这好像是一个错误,但实际上它让我们能够用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 执行完成后验证模块。)

当然,在完全动态的环境中,我们不需要像常规编译代码那样限制用户,因此在这种情况下,大多数在正常情况下会被拒绝的东西都是完全可以接受的,例如:execevalglobalslocals、动态类创建以及无类型参数的函数。

"先前的艺术"

无运行时依赖