erg/doc/zh_TW/compiler/inference.md
2022-10-15 16:18:18 +08:00

440 lines
No EOL
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 類型推斷算法
[![badge](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Fgezf7g7pd5.execute-api.ap-northeast-1.amazonaws.com%2Fdefault%2Fsource_up_to_date%3Fowner%3Derg-lang%26repos%3Derg%26ref%3Dmain%26path%3Ddoc/EN/compiler/inference.md%26commit_hash%3D00350f64a40b12f763a605bc16748d09379ab182)](https://gezf7g7pd5.execute-api.ap-northeast-1.amazonaws.com/default/source_up_to_date?owner=erg-lang&repos=erg&ref=main&path=doc/EN/compiler/inference.md&commit_hash=00350f64a40b12f763a605bc16748d09379ab182)
> __Warning__: 此部分正在編輯中,可能包含一些錯誤
顯示了下面使用的符號
```python
Free type variables (type, unbound): ?T, ?U, ...
Free-type variables (values, unbound): ?a, ?b, ...
type environment (Γ): { x: T, ... }
Type assignment rule (S): { ?T --> T, ... }
Type argument evaluation environment (E): { e -> e', ... }
```
我們以下面的代碼為例:
```python
v = ![]
v.push! 1
print! v
```
Erg 的類型推斷主要使用 Hindley-Milner 類型推斷算法(盡管已經進行了各種擴展)。具體而言,類型推斷是通過以下過程執行的。術語將在后面解釋
1. 推斷右值的類型(搜索)
2. 實例化結果類型
3. 如果是調用,執行類型替換(substitute)
4. 解決已經單態化的Trait
5. 如果有類型變量值,求值/歸約(eval)
6. 刪除鏈接類型變量(deref)
7. 傳播可變依賴方法的變化
8. 如果有左值并且是Callable則泛化參數類型(generalize)
9. 如果有左值,對(返回值)類型進行泛化(generalize)
10. 如果是賦值,則在符號表(`Context`)中注冊類型信息(更新)
具體操作如下
第 1 行。Def{sig: v, block: ![]}
獲取塊類型:
獲取 UnaryOp 類型:
getArray 類型: `['T; 0]`
實例化: `[?T; 0]`
(替代,評估被省略)
更新: `Γ: {v: [?T; 0]}`
表達式 返回`NoneType`: OK
第 2 行 CallMethod {obj: v, name: push!, args: [1]}
獲取 obj 類型: `Array!(?T, 0)`
搜索: `Γ Array!(?T, 0).push!({1})`
得到: `= Array!('T ~> 'T, 'N ~> 'N+1).push!('T) => NoneType`
實例化: `Array!(?T, ?N).push!(?T) => NoneType`
替代(`S: {?T --> Nat, ?N --> 0}`): `Array!(Nat ~> Nat, 0 ~> 0+1).push!(Nat) => NoneType`
評估: `Array!(Nat, 0 ~> 1).push!({1}) => NoneType`
更新: `Γ: {v: [Nat; 1]}`
表達式 返回`NoneType`: OK
第 3 行。調用 {obj: print!, args: [v]}
獲取參數類型: `[[Nat; 1]!]`
獲取 obj 類型:
搜索: `Γ print!([Nat; 1]!)`
得到: `= print!(...Object) => NoneType`
表達式 返回`NoneType`: OK
## 類型變量的實現
類型變量最初在 [ty.rs] 的 `Type` 中表示如下。它現在以不同的方式實現,但本質上是相同的想法,所以我將以更天真的方式考慮這種實現
`RcCell<T>``Rc<RefCell<T>>` 的包裝類型
```rust
pub enum Type {
...
Var(RcCell<Option<Type>>), // a reference to the type of other expression, see docs/compiler/inference.md
...
}
```
類型變量可以通過將實體類型保存在外部字典中來實現,并且類型變量本身只有它的鍵。但是,據說使用 `RcCell` 的實現通常更有效(需要驗證,[來源](https://mobile.twitter.com/bd_gfngfn/status/1296719625086877696?s=21))
類型變量首先被初始化為 `Type::Var(RcCell::new(None))`
當分析代碼并確定類型時,會重寫此類型變量
如果內容直到最后都保持為 None ,它將是一個無法確定為具體類型的類型變量(當場)。例如,具有 `id x = x``x` 類型
我將這種狀態下的類型變量稱為 __Unbound 類型變量__(我不知道確切的術語)。另一方面,我們將分配了某種具體類型的變量稱為 __Linked 類型變量__
兩者都是自由類型變量(該術語顯然以"自由變量"命名)。這些是編譯器用于推理的類型變量。它之所以有這樣一個特殊的名字,是因為它不同于程序員指定類型的類型變量,例如 `id: 'T -> 'T` 中的 `'T`
未綁定類型變量表示為`?T``?U`。在類型論的上下文中,經常使用 α 和 β,但這一種是用來簡化輸入的
請注意,這是出于一般討論目的而采用的表示法,實際上并未使用字符串標識符實現
進入類型環境時,未綁定的類型變量 `Type::Var` 被替換為 `Type::MonoQuantVar`。這稱為 __quantified 類型變量__。這類似于程序員指定的類型變量,例如"T"。內容只是一個字符串,并沒有像自由類型變量那樣鏈接到具體類型的工具
用量化類型變量替換未綁定類型變量的操作稱為__generalization__(或泛化)。如果將其保留為未綁定類型變量,則類型將通過一次調用固定(例如,調用 `id True` 后,`id 1` 的返回類型將是 `Bool`),所以它必須是概括的
以這種方式,在類型環境中注冊了包含量化類型變量的通用定義
## 概括、類型方案、具體化
讓我們將未綁定類型變量 `?T` 泛化為 `gen` 的操作表示。令生成的廣義類型變量為 `|T: Type| T`
在類型論中,量化類型,例如多相關類型 `α->α`,通過在它們前面加上 `?α.` 來區分(像 ? 這樣的符號稱為(通用)量詞。)
這樣的表示(例如`?α.α->α`)稱為類型方案。Erg 中的類型方案表示為 `|T: Type| T -> T`
類型方案通常不被認為是一流的類型。以這種方式配置類型系統可以防止類型推斷起作用。但是在Erg中在一定條件下可以算是一流的類型。有關詳細信息請參閱 [rank2 類型](../syntax/type/advanced/_rank2type.md)
現在,當在使用它的類型推斷(例如,`id 1``id True`)中使用獲得的類型方案(例如`'T -> 'T(id's type scheme)`)時必須釋放generalize。這種逆變換稱為 __instantiation__。我們將調用操作`inst`
```python
gen ?T = 'T
inst 'T = ?T (?T ? Γ)
```
重要的是,這兩個操作都替換了所有出現的類型變量。例如,如果你實例化 `'T -> 'T`,你會得到 `?T -> ?T`
實例化需要替換 dict但為了泛化只需將 `?T``'T` 鏈接以替換它
之后,給出參數的類型以獲取目標類型。此操作稱為類型替換,將用 `subst` 表示
此外,如果表達式是調用,則獲取返回類型的操作表示為 `subst_call_ret`。第一個參數是參數類型列表,第二個參數是要分配的類型
類型替換規則 `{?T --> X}` 意味著將 `?T``X` 重寫為相同類型。此操作稱為 __Unification__`X` 也可以是類型變量
[單獨部分] 中描述了詳細的統一算法。我們將統一操作表示為"統一"
```python
unify(?T, Int) == Ok(()) # ?T == (Int)
# S為類型分配規則T為適用類型
subst(S: {?T --> X}, T: ?T -> ?T) == X -> X
# Type assignment rules are {?T --> X, ?U --> T}
subst_call_ret([X, Y], (?T, ?U) -> ?U) == Y
```
## 半統一(semi-unification)
統一的一種變體稱為半統一(__Semi-unification__)。這是更新類型變量約束以滿足子類型關系的操作
在某些情況下,類型變量可能是統一的,也可能不是統一的,因此稱為"半"統一
例如,在參數分配期間會發生半統一
因為實際參數的類型必須是形式參數類型的子類型
如果參數類型是類型變量,我們需要更新子類型關系以滿足它
```python
# 如果形參類型是T
f(x: T): T = ...
a: U
# 必須為 U <: T否則類型錯誤
f(a)
```
## 泛化
泛化不是一項簡單的任務。當涉及多個作用域時,類型變量的"級別管理"就變得很有必要了
為了看到層級管理的必要性,我們首先確認沒有層級管理的類型推斷會導致問題
推斷以下匿名函數的類型
```python
x ->
y = x
y
```
首先Erg 分配類型變量如下:
y 的類型也是未知的,但暫時未分配
```python
x(: ?T) ->
y = x
y
```
首先要確定的是右值 x 的類型。右值是一種"用途",因此我們將其具體化
但是 x 的類型 `?T` 已經被實例化了因為它是一個自由變量。Yo`?T` 成為右值的類型
```python
x(: ?T) ->
y = x (: inst ?T)
y
```
注冊為左值 y 的類型時進行泛化。然而,正如我們稍后將看到的,這種概括是不完善的,并且會產生錯誤的結果
```python
x(: ?T) ->
y(:gen?T) = x(:?T)
y
```
```python
x(: ?T) ->
y(: 'T) = x
y
```
y 的類型現在是一個量化類型變量"T"。在下一行中,立即使用 `y`。具體的
```python
x: ?T ->
y(: 'T) = x
y(: inst 'T)
```
請注意,實例化必須創建一個與任何已經存在的(自由)類型變量不同的(自由)類型變量(概括類似)。這樣的類型變量稱為新類型變量
```python
x: ?T ->
y = x
y(: ?U)
```
并查看生成的整個表達式的類型。`?T -> ?U`
但顯然這個表達式應該是`?T -> ?T`,所以我們知道推理有問題
發生這種情況是因為我們沒有"級別管理"類型變量
所以我們用下面的符號來介紹類型變量的層次。級別表示為自然數
```python
# 普通類型變量
?T<1>, ?T<2>, ...
# 具有子類型約束的類型變量
?T<1>(<:U) or ?T(<:U)<1>, ...
```
讓我們再嘗試一次:
```python
x ->
y = x
y
```
首先,按如下方式分配一個 leveled 類型變量: toplevel 級別為 1。隨著范圍的加深級別增加
函數參數屬于內部范圍,因此它們比函數本身高一級
```python
# level 1
x (: ?T<2>) ->
# level 2
y = x
y
```
首先,實例化右值`x`。和以前一樣,沒有任何改變
```python
x (: ?T<2>) ->
y = x (: inst ?T<2>)
y
```
這是關鍵。這是分配給左值`y`的類型時的概括
早些時候,這里的結果很奇怪,所以我們將改變泛化算法
如果類型變量的級別小于或等于當前范圍的級別,則泛化使其保持不變
```python
gen ?T<n> = if n <= current_level, then= ?T<n>, else= 'T
```
```python
x (: ?T<2>) ->
# current_level = 2
y(: gen ?T<2>) = x(: ?T<2>)
y
```
That is, the lvalue `y` has type `?T<2>`.
```python
x (: ?T<2>) ->
# ↓ 不包括
y(: ?T<2>) = x
y
```
y 的類型現在是一個未綁定的類型變量 `?T<2>`。具體如下幾行: 但是 `y` 的類型沒有被概括,所以什么也沒有發生
```python
x (: ?T<2>) ->
y(: ?T<2>) = x
y (: inst ?T<2>)
```
```python
x (: ?T<2>) ->
y = x
y (: ?T<2>)
```
我們成功獲得了正確的類型`?T<2> -> ?T<2>`
讓我們看另一個例子。這是更一般的情況,具有函數/運算符應用程序和前向引用
```python
fx, y = id(x) + y
id x = x
f10,1
```
讓我們逐行瀏覽它
`f` 的推斷過程中,會引用后面定義的函數常量 `id`
在這種情況下,在 `f` 之前插入一個假設的 `id` 聲明,并為其分配一個自由類型變量
注意此時類型變量的級別是`current_level`。這是為了避免在其他函數中泛化
```python
id: ?T<1> -> ?U<1>
f x (: ?V<2>), y (: ?W<2>) =
id(x) (: subst_call_ret([inst ?V<2>], inst ?T<1> -> ?U<1>)) + y
```
類型變量之間的統一將高級類型變量替換為低級類型變量
如果級別相同,則無所謂
類型變量之間的半統一有點不同
不同級別的類型變量不得相互施加類型約束
```python
# BAD
f x (: ?V<2>), y (: ?W<2>) =
# ?V<2>(<: ?T<1>)
# ?T<1>(:> ?V<2>)
id(x) (: ?U<1>) + y (: ?W<2>)
```
這使得無法確定在何處實例化類型變量
對于 Type 類型變量,執行正常統一而不是半統一
也就是說,統一到下層
```python
# OK
f x (: ?V<2>), y (: ?W<2>) =
# ?V<2> --> ?T<1>
id(x) (: ?U<1>) + y (: ?W<2>)
```
```python
f x (: ?T<1>), y (: ?W<2>) =
(id(x) + x): subst_call_ret([inst ?U<1>, inst ?W<2>], inst |'L <: Add('R)| ('L, 'R) -> 'L .AddO)
```
```python
f x (: ?T<1>), y (: ?W<2>) =
(id(x) + x): subst_call_ret([inst ?U<1>, inst ?W<2>], (?L(<: Add(?R<2>))<2>, ?R<2 >) -> ?L<2>.AddO)
```
```python
id: ?T<1> -> ?U<1>
f x (: ?T<1>), y (: ?W<2>) =
# ?U<1>(<: Add(?W<2>)) # Inherit the constraints of ?L
# ?L<2> --> ?U<1>
# ?R<2> --> ?W<2> (not ?R(:> ?W), ?W(<: ?R))
(id(x) + x) (: ?U<1>.AddO)
```
```python
# current_level = 1
f(x, y) (: gen ?T<1>, gen ?W<2> -> gen ?U<1>.AddO) =
id(x) + x
```
```python
id: ?T<1> -> ?U<1>
f(x, y) (: |'W: Type| (?T<1>, 'W) -> gen ?U<1>(<: Add(?W<2>)).AddO) =
id(x) + x
```
```python
f(x, y) (: |'W: Type| (?T<1>, 'W) -> ?U<1>(<: Add(?W<2>)).AddO) =
id(x) + x
```
定義時,提高層次,使其可以泛化
```python
# ?T<1 -> 2>
# ?U<1 -> 2>
id x (: ?T<2>) -> ?U<2> = x (: inst ?T<2>)
```
如果已經分配了返回類型,則與結果類型統一(`?U<2> --> ?T<2>`)
```python
# ?U<2> --> ?T<2>
f(x, y) (: |'W: Type| (?T<2>, 'W) -> ?T<2>(<: Add(?W<2>)).AddO) =
id(x) + x
# current_level = 1
id(x) (: gen ?T<2> -> gen ?T<2>) = x (: ?T<2>)
```
如果類型變量已經被實例化為一個簡單的類型變量,
依賴于它的類型變量也將是一個 Type 類型變量
廣義類型變量對于每個函數都是獨立的
```python
f(x, y) (: |'W: Type, 'T <: Add('W)| ('T, 'W) -> 'T.AddO) =
id(x) + x
id(x) (: |'T: Type| 'T -> gen 'T) = x
```
```python
f x, y (: |'W: Type, 'T <: Add('W)| ('T, 'W) -> 'T.AddO) =
id(x) + y
id(x) (: 'T -> 'T) = x
f(10, 1) (: subst_call_ret([inst {10}, inst {1}], inst |'W: Type, 'T <: Add('W)| ('T, 'W) -> 'T .AddO)
```
```python
f(10, 1) (: subst_call_ret([inst {10}, inst {1}], (?T<1>(<: Add(?W<1>)), ?W<1>) -> ? T<1>.AddO))
```
類型變量綁定到具有實現的最小類型
```python
# ?T(:> {10} <: Add(?W<1>))<1>
# ?W(:> {1})<1>
# ?W(:> {1})<1> <: ?T<1> (:> {10}, <: Add(?W(:> {1})<1>))
# serialize
# {1} <: ?W<1> or {10} <: ?T<1> <: Add({1}) <: Add(?W<1>)
# Add(?W)(:> ?V) 的最小實現Trait是 Add(Nat) == Nat因為 Add 相對于第一個參數是協變的
# {10} <: ?W<1> or {1} <: ?T<1> <: Add(?W<1>) <: Add(Nat) == Nat
# ?T(:> ?W(:> {10}) or {1}, <: Nat).AddO == Nat # 如果只有一個候選人,完成評估
f(10, 1) (: (?W(:> {10}, <: Nat), ?W(:> {1})) -> Nat)
# 程序到此結束,所以去掉類型變量
f(10, 1) (: ({10}, {1}) -> Nat)
```
整個程序的結果類型是:
```python
f|W: Type, T <: Add(W)|(x: T, y: W): T.AddO = id(x) + y
id|T: Type|(x: T): T = x
f(10, 1): Nat
```
我還重印了原始的、未明確鍵入的程序
```python
fx, y = id(x) + y
id x = x
f(10, 1)
```