Notes
This commit is contained in:
parent
1a3bc19dce
commit
6ec29fd26b
22
TODO.md
22
TODO.md
@ -22,3 +22,25 @@
|
|||||||
- Try to implement the min and max functions using select
|
- Try to implement the min and max functions using select
|
||||||
|
|
||||||
- Read https://bytecodealliance.org/articles/multi-value-all-the-wasm
|
- Read https://bytecodealliance.org/articles/multi-value-all-the-wasm
|
||||||
|
|
||||||
|
|
||||||
|
- GRose :: (* -> *) -> * -> *
|
||||||
|
- skolem => variable that cannot be unified
|
||||||
|
|
||||||
|
|
||||||
|
Limitations (for now):
|
||||||
|
- no type level nats
|
||||||
|
- only support first order kinds
|
||||||
|
Do not support yet:
|
||||||
|
```
|
||||||
|
data Record f = Record {
|
||||||
|
field: f Int
|
||||||
|
}
|
||||||
|
Record :: (* -> *) -> *
|
||||||
|
```
|
||||||
|
(Nested arrows)
|
||||||
|
- only support rank 1 types
|
||||||
|
```
|
||||||
|
mapRecord :: forall f g. (forall a. f a -> f b) -> Record f -> Record g
|
||||||
|
```
|
||||||
|
(Nested forall)
|
||||||
|
|||||||
0
phasm/type4/__init__.py
Normal file
0
phasm/type4/__init__.py
Normal file
131
phasm/type4/classes.py
Normal file
131
phasm/type4/classes.py
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import TypeAlias
|
||||||
|
|
||||||
|
## Kind * (part 1)
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Atomic:
|
||||||
|
"""
|
||||||
|
Atomic (or base or fundamental) type - it doesn't construct anything and wasn't constructed
|
||||||
|
"""
|
||||||
|
name: str
|
||||||
|
|
||||||
|
class Struct(Atomic):
|
||||||
|
"""
|
||||||
|
Structs are a fundamental type. But we need to store some extra info.
|
||||||
|
"""
|
||||||
|
fields: list[tuple[str, Type4]]
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Variable:
|
||||||
|
"""
|
||||||
|
Type variable of kind *.
|
||||||
|
"""
|
||||||
|
name: str
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Nat:
|
||||||
|
"""
|
||||||
|
Some type constructors take a natural number as argument, rather than a type.
|
||||||
|
"""
|
||||||
|
value: int
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
return str(self.value)
|
||||||
|
|
||||||
|
## Kind * -> *
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ConcreteConSS:
|
||||||
|
"""
|
||||||
|
An concrete type construtor of kind * -> *, like dynamic array.
|
||||||
|
"""
|
||||||
|
name: str
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class VariableConSS:
|
||||||
|
"""
|
||||||
|
A type variable of kind * -> *.
|
||||||
|
"""
|
||||||
|
name: str
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class AppliedConSS:
|
||||||
|
"""
|
||||||
|
An application of a type constructor of kind * -> *.
|
||||||
|
|
||||||
|
The result is a type of kind *.
|
||||||
|
"""
|
||||||
|
con: ConcreteConSS | VariableConSS | AppliedConSSS | AppliedConNSS
|
||||||
|
arg: Type4
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
return f'{self.con.name} {self.arg.name}'
|
||||||
|
|
||||||
|
## Kind * -> * -> *
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ConcreteConSSS:
|
||||||
|
"""
|
||||||
|
An concrete type construtor of kind * -> * -> *, like function, tuple or Either.
|
||||||
|
"""
|
||||||
|
name: str
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class VariableConSSS:
|
||||||
|
"""
|
||||||
|
A type variable of kind * -> * -> *.
|
||||||
|
"""
|
||||||
|
name: str
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class AppliedConSSS:
|
||||||
|
"""
|
||||||
|
An application of a type constructor of kind * -> * -> *.
|
||||||
|
|
||||||
|
The result is a type construtor of kind * -> *.
|
||||||
|
"""
|
||||||
|
con: ConcreteConSSS | VariableConSSS
|
||||||
|
arg: Type4
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
return f'{self.con.name} {self.arg.name}'
|
||||||
|
|
||||||
|
## Kind Nat -> * -> *
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ConcreteConNSS:
|
||||||
|
"""
|
||||||
|
An concrete type construtor of kind Nat -> * -> *, like static array.
|
||||||
|
"""
|
||||||
|
name: str
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class VariableConNSS:
|
||||||
|
"""
|
||||||
|
A type variable of kind Nat -> * -> *.
|
||||||
|
"""
|
||||||
|
name: str
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class AppliedConNSS:
|
||||||
|
"""
|
||||||
|
An application of a type constructor of kind Nat -> * -> *.
|
||||||
|
|
||||||
|
The result is a type construtor of kind * -> *.
|
||||||
|
"""
|
||||||
|
con: ConcreteConNSS | VariableConNSS
|
||||||
|
arg: Nat
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
return f'{self.con.name} {self.arg.name}'
|
||||||
|
|
||||||
|
# Kind * (part 2)
|
||||||
|
|
||||||
|
Type4: TypeAlias = Atomic | Variable | AppliedConSS
|
||||||
202
phasm/type4/unify.py
Normal file
202
phasm/type4/unify.py
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from .classes import (
|
||||||
|
AppliedConNSS,
|
||||||
|
AppliedConSS,
|
||||||
|
AppliedConSSS,
|
||||||
|
Atomic,
|
||||||
|
ConcreteConNSS,
|
||||||
|
ConcreteConSS,
|
||||||
|
ConcreteConSSS,
|
||||||
|
Type4,
|
||||||
|
Variable,
|
||||||
|
VariableConNSS,
|
||||||
|
VariableConSS,
|
||||||
|
VariableConSSS,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Failure:
|
||||||
|
"""
|
||||||
|
Both types are already different - cannot be unified.
|
||||||
|
"""
|
||||||
|
msg: str
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Action:
|
||||||
|
pass
|
||||||
|
|
||||||
|
UnifyResult = Failure | list[Action]
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SetTypeForVariable(Action):
|
||||||
|
var: Variable
|
||||||
|
type: Atomic | AppliedConSS
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SetTypeForConSSVariable(Action):
|
||||||
|
var: VariableConSS
|
||||||
|
type: ConcreteConSS | AppliedConSSS | AppliedConNSS
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SetTypeForConSSSVariable(Action):
|
||||||
|
var: VariableConSSS
|
||||||
|
type: ConcreteConSSS
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ReplaceVariable(Action):
|
||||||
|
before: Variable
|
||||||
|
after: Variable
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ReplaceConSSVariable(Action):
|
||||||
|
before: VariableConSS
|
||||||
|
after: VariableConSS
|
||||||
|
|
||||||
|
def unify(lft: Type4, rgt: Type4) -> UnifyResult:
|
||||||
|
"""
|
||||||
|
Tries to unify the given two types.
|
||||||
|
"""
|
||||||
|
if isinstance(lft, Atomic) and isinstance(rgt, Atomic):
|
||||||
|
if lft == rgt:
|
||||||
|
return []
|
||||||
|
|
||||||
|
return Failure(f'Cannot unify {lft.name} and {rgt.name}')
|
||||||
|
|
||||||
|
if isinstance(lft, Atomic) and isinstance(rgt, Variable):
|
||||||
|
return [SetTypeForVariable(rgt, lft)]
|
||||||
|
|
||||||
|
if isinstance(lft, Atomic) and isinstance(rgt, AppliedConSS):
|
||||||
|
return Failure(f'Cannot unify {lft.name} and {rgt.name}')
|
||||||
|
|
||||||
|
if isinstance(lft, Variable) and isinstance(rgt, Atomic):
|
||||||
|
return [SetTypeForVariable(lft, rgt)]
|
||||||
|
|
||||||
|
if isinstance(lft, Variable) and isinstance(rgt, Variable):
|
||||||
|
return [ReplaceVariable(lft, rgt)]
|
||||||
|
|
||||||
|
if isinstance(lft, Variable) and isinstance(rgt, AppliedConSS):
|
||||||
|
return [SetTypeForVariable(lft, rgt)]
|
||||||
|
|
||||||
|
if isinstance(lft, AppliedConSS) and isinstance(rgt, Atomic):
|
||||||
|
return Failure(f'Cannot unify {lft.name} and {rgt.name}')
|
||||||
|
|
||||||
|
if isinstance(lft, AppliedConSS) and isinstance(rgt, Variable):
|
||||||
|
return [SetTypeForVariable(rgt, lft)]
|
||||||
|
|
||||||
|
if isinstance(lft, AppliedConSS) and isinstance(rgt, AppliedConSS):
|
||||||
|
con_res = unify_con_ss(lft.con, rgt.con)
|
||||||
|
if isinstance(con_res, Failure):
|
||||||
|
return Failure(f'Cannot unify {lft.name} and {rgt.name}')
|
||||||
|
|
||||||
|
arg_res = unify(lft.arg, rgt.arg)
|
||||||
|
if isinstance(arg_res, Failure):
|
||||||
|
return Failure(f'Cannot unify {lft.name} and {rgt.name}')
|
||||||
|
|
||||||
|
return con_res + arg_res
|
||||||
|
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def unify_con_ss(
|
||||||
|
lft: ConcreteConSS | VariableConSS | AppliedConSSS | AppliedConNSS,
|
||||||
|
rgt: ConcreteConSS | VariableConSS | AppliedConSSS | AppliedConNSS,
|
||||||
|
) -> UnifyResult:
|
||||||
|
"""
|
||||||
|
Tries to unify the given two constuctors of kind * -> *.
|
||||||
|
"""
|
||||||
|
if isinstance(lft, ConcreteConSS) and isinstance(rgt, ConcreteConSS):
|
||||||
|
if lft == rgt:
|
||||||
|
return []
|
||||||
|
|
||||||
|
return Failure(f'Cannot unify {lft.name} and {rgt.name}')
|
||||||
|
|
||||||
|
if isinstance(lft, ConcreteConSS) and isinstance(rgt, VariableConSS):
|
||||||
|
return [SetTypeForConSSVariable(rgt, lft)]
|
||||||
|
|
||||||
|
if isinstance(lft, ConcreteConSS) and isinstance(rgt, AppliedConSSS):
|
||||||
|
return Failure(f'Cannot unify {lft.name} and {rgt.name}')
|
||||||
|
|
||||||
|
if isinstance(lft, ConcreteConSS) and isinstance(rgt, AppliedConNSS):
|
||||||
|
return Failure(f'Cannot unify {lft.name} and {rgt.name}')
|
||||||
|
|
||||||
|
if isinstance(lft, VariableConSS) and isinstance(rgt, ConcreteConSS):
|
||||||
|
return [SetTypeForConSSVariable(lft, rgt)]
|
||||||
|
|
||||||
|
if isinstance(lft, VariableConSS) and isinstance(rgt, VariableConSS):
|
||||||
|
return [ReplaceConSSVariable(lft, rgt)]
|
||||||
|
|
||||||
|
if isinstance(lft, VariableConSS) and isinstance(rgt, AppliedConSSS):
|
||||||
|
return [SetTypeForConSSVariable(lft, rgt)]
|
||||||
|
|
||||||
|
if isinstance(lft, VariableConSS) and isinstance(rgt, AppliedConNSS):
|
||||||
|
return [SetTypeForConSSVariable(lft, rgt)]
|
||||||
|
|
||||||
|
if isinstance(lft, AppliedConSSS) and isinstance(rgt, ConcreteConSS):
|
||||||
|
return Failure(f'Cannot unify {lft.name} and {rgt.name}')
|
||||||
|
|
||||||
|
if isinstance(lft, AppliedConSSS) and isinstance(rgt, VariableConSS):
|
||||||
|
return [SetTypeForConSSVariable(rgt, lft)]
|
||||||
|
|
||||||
|
if isinstance(lft, AppliedConSSS) and isinstance(rgt, AppliedConSSS):
|
||||||
|
con_res = unify_con_sss(lft.con, rgt.con)
|
||||||
|
if isinstance(con_res, Failure):
|
||||||
|
return Failure(f'Cannot unify {lft.name} and {rgt.name}')
|
||||||
|
|
||||||
|
arg_res = unify(lft.arg, rgt.arg)
|
||||||
|
if isinstance(arg_res, Failure):
|
||||||
|
return Failure(f'Cannot unify {lft.name} and {rgt.name}')
|
||||||
|
|
||||||
|
return con_res + arg_res
|
||||||
|
|
||||||
|
if isinstance(lft, AppliedConSSS) and isinstance(rgt, AppliedConNSS):
|
||||||
|
return Failure(f'Cannot unify {lft.name} and {rgt.name}')
|
||||||
|
|
||||||
|
if isinstance(lft, AppliedConNSS) and isinstance(rgt, ConcreteConSS):
|
||||||
|
return Failure(f'Cannot unify {lft.name} and {rgt.name}')
|
||||||
|
|
||||||
|
if isinstance(lft, AppliedConNSS) and isinstance(rgt, VariableConSS):
|
||||||
|
return [SetTypeForConSSVariable(rgt, lft)]
|
||||||
|
|
||||||
|
if isinstance(lft, AppliedConNSS) and isinstance(rgt, AppliedConSSS):
|
||||||
|
return Failure(f'Cannot unify {lft.name} and {rgt.name}')
|
||||||
|
|
||||||
|
if isinstance(lft, AppliedConNSS) and isinstance(rgt, AppliedConNSS):
|
||||||
|
con_res = unify_con_nss(lft.con, rgt.con)
|
||||||
|
if isinstance(con_res, Failure):
|
||||||
|
return Failure(f'Cannot unify {lft.name} and {rgt.name}')
|
||||||
|
|
||||||
|
if lft.arg.value != rgt.arg.value:
|
||||||
|
return Failure(f'Cannot unify {lft.name} and {rgt.name}')
|
||||||
|
|
||||||
|
return con_res
|
||||||
|
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def unify_con_sss(
|
||||||
|
lft: ConcreteConSSS | VariableConSSS,
|
||||||
|
rgt: ConcreteConSSS | VariableConSSS,
|
||||||
|
) -> UnifyResult:
|
||||||
|
"""
|
||||||
|
Tries to unify the given two constuctors of kind * -> * -> *.
|
||||||
|
"""
|
||||||
|
if isinstance(lft, ConcreteConSSS) and isinstance(rgt, ConcreteConSSS):
|
||||||
|
if lft == rgt:
|
||||||
|
return []
|
||||||
|
return Failure(f'Cannot unify {lft.name} and {rgt.name}')
|
||||||
|
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def unify_con_nss(
|
||||||
|
lft: ConcreteConNSS | VariableConNSS,
|
||||||
|
rgt: ConcreteConNSS | VariableConNSS,
|
||||||
|
) -> UnifyResult:
|
||||||
|
"""
|
||||||
|
Tries to unify the given two constuctors of kind * -> * -> *.
|
||||||
|
"""
|
||||||
|
if isinstance(lft, ConcreteConNSS) and isinstance(rgt, ConcreteConNSS):
|
||||||
|
if lft == rgt:
|
||||||
|
return []
|
||||||
|
return Failure(f'Cannot unify {lft.name} and {rgt.name}')
|
||||||
|
|
||||||
|
raise NotImplementedError
|
||||||
0
phasm/type5/__init__.py
Normal file
0
phasm/type5/__init__.py
Normal file
93
phasm/type5/__main__.py
Normal file
93
phasm/type5/__main__.py
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
from .kindexpr import Star
|
||||||
|
from .record import Record
|
||||||
|
from .typeexpr import AtomicType, TypeApplication, TypeConstructor, TypeVariable
|
||||||
|
from .unify import unify
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
S = Star()
|
||||||
|
|
||||||
|
a_var = TypeVariable(name="a", kind=S)
|
||||||
|
b_var = TypeVariable(name="b", kind=S)
|
||||||
|
t_var = TypeVariable(name="t", kind=S >> S)
|
||||||
|
r_var = TypeVariable(name="r", kind=S >> S)
|
||||||
|
print(a_var)
|
||||||
|
print(b_var)
|
||||||
|
print(t_var)
|
||||||
|
print(r_var)
|
||||||
|
print()
|
||||||
|
|
||||||
|
u32_type = AtomicType(name="u32")
|
||||||
|
f64_type = AtomicType(name="f64")
|
||||||
|
print(u32_type)
|
||||||
|
print(f64_type)
|
||||||
|
print()
|
||||||
|
|
||||||
|
maybe_constructor = TypeConstructor(name="Maybe", kind=S >> S)
|
||||||
|
maybe_a_type = TypeApplication(constructor=maybe_constructor, argument=a_var)
|
||||||
|
maybe_u32_type = TypeApplication(constructor=maybe_constructor, argument=u32_type)
|
||||||
|
print(maybe_constructor)
|
||||||
|
print(maybe_a_type)
|
||||||
|
print(maybe_u32_type)
|
||||||
|
print()
|
||||||
|
|
||||||
|
either_constructor = TypeConstructor(name="Either", kind=S >> (S >> S))
|
||||||
|
either_a_constructor = TypeApplication(constructor=either_constructor, argument=a_var)
|
||||||
|
either_a_a_type = TypeApplication(constructor=either_a_constructor, argument=a_var)
|
||||||
|
either_u32_constructor = TypeApplication(constructor=either_constructor, argument=u32_type)
|
||||||
|
either_u32_f64_type = TypeApplication(constructor=either_u32_constructor, argument=f64_type)
|
||||||
|
either_u32_a_type = TypeApplication(constructor=either_u32_constructor, argument=a_var)
|
||||||
|
print(either_constructor)
|
||||||
|
print(either_a_constructor)
|
||||||
|
print(either_a_a_type)
|
||||||
|
print(either_u32_constructor)
|
||||||
|
print(either_u32_f64_type)
|
||||||
|
print(either_u32_a_type)
|
||||||
|
print()
|
||||||
|
|
||||||
|
t_a_type = TypeApplication(constructor=t_var, argument=a_var)
|
||||||
|
t_u32_type = TypeApplication(constructor=t_var, argument=u32_type)
|
||||||
|
print(t_a_type)
|
||||||
|
print(t_u32_type)
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
shape_record = Record("Shape", [
|
||||||
|
("width", u32_type),
|
||||||
|
("height", either_u32_f64_type),
|
||||||
|
])
|
||||||
|
print('shape_record', shape_record)
|
||||||
|
print()
|
||||||
|
|
||||||
|
values = [
|
||||||
|
a_var,
|
||||||
|
b_var,
|
||||||
|
u32_type,
|
||||||
|
f64_type,
|
||||||
|
t_var,
|
||||||
|
t_a_type,
|
||||||
|
r_var,
|
||||||
|
maybe_constructor,
|
||||||
|
maybe_u32_type,
|
||||||
|
maybe_a_type,
|
||||||
|
either_u32_constructor,
|
||||||
|
either_u32_a_type,
|
||||||
|
either_u32_f64_type,
|
||||||
|
]
|
||||||
|
|
||||||
|
seen_exprs: set[str] = set()
|
||||||
|
for lft in values:
|
||||||
|
for rgt in values:
|
||||||
|
expr = f"{lft.name} ~ {rgt.name}"
|
||||||
|
if expr in seen_exprs:
|
||||||
|
continue
|
||||||
|
|
||||||
|
print(expr.ljust(40) + " => " + str(unify(lft, rgt)))
|
||||||
|
|
||||||
|
inv_expr = f"{rgt.name} ~ {lft.name}"
|
||||||
|
seen_exprs.add(inv_expr)
|
||||||
|
|
||||||
|
print()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
40
phasm/type5/kindexpr.py
Normal file
40
phasm/type5/kindexpr.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import TypeAlias
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Star:
|
||||||
|
def __rshift__(self, other: KindExpr) -> Arrow:
|
||||||
|
return Arrow(self, other)
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return "*"
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Arrow:
|
||||||
|
"""
|
||||||
|
Represents an arrow kind `K1 -> K2`.
|
||||||
|
|
||||||
|
To create K1 -> K2 -> K3, pass an Arrow for result_kind.
|
||||||
|
For now, we do not support Arrows as arguments (i.e.,
|
||||||
|
no higher order kinds).
|
||||||
|
"""
|
||||||
|
arg_kind: Star
|
||||||
|
result_kind: KindExpr
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
if isinstance(self.arg_kind, Star):
|
||||||
|
arg_kind = "*"
|
||||||
|
else:
|
||||||
|
arg_kind = f"({str(self.arg_kind)})"
|
||||||
|
|
||||||
|
if isinstance(self.result_kind, Star):
|
||||||
|
result_kind = "*"
|
||||||
|
else:
|
||||||
|
result_kind = f"({str(self.result_kind)})"
|
||||||
|
|
||||||
|
return f"{arg_kind} -> {result_kind}"
|
||||||
|
|
||||||
|
KindExpr: TypeAlias = Star | Arrow
|
||||||
42
phasm/type5/record.py
Normal file
42
phasm/type5/record.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from .kindexpr import Star
|
||||||
|
from .typeexpr import AtomicType, TypeApplication, TypeExpr
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Record(TypeExpr):
|
||||||
|
"""
|
||||||
|
Records are a fundamental type. But we need to store some extra info.
|
||||||
|
"""
|
||||||
|
fields: list[tuple[str, AtomicType | TypeApplication]]
|
||||||
|
|
||||||
|
def __init__(self, name: str, fields: list[tuple[str, AtomicType | TypeApplication]]) -> None:
|
||||||
|
for field_name, field_type in fields:
|
||||||
|
if field_type.kind == Star():
|
||||||
|
continue
|
||||||
|
|
||||||
|
raise TypeError(f"Record fields must be concrete types ({field_name} :: {field_type})")
|
||||||
|
|
||||||
|
super().__init__(Star(), name)
|
||||||
|
self.fields = fields
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
args = ", ".join(
|
||||||
|
f"{field_name} :: {field_type}"
|
||||||
|
for field_name, field_type in self.fields
|
||||||
|
)
|
||||||
|
return f"{self.name} {{{args}}} :: {self.kind}"
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class RecordConstructor(TypeExpr):
|
||||||
|
"""
|
||||||
|
TODO.
|
||||||
|
|
||||||
|
i.e.:
|
||||||
|
```
|
||||||
|
class Foo[T, R]:
|
||||||
|
lft: T
|
||||||
|
rgt: R
|
||||||
|
"""
|
||||||
|
name: str
|
||||||
52
phasm/type5/typeexpr.py
Normal file
52
phasm/type5/typeexpr.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from .kindexpr import Arrow, KindExpr, Star
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class TypeExpr:
|
||||||
|
kind: KindExpr
|
||||||
|
name: str
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f"{self.name} :: {self.kind}"
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class AtomicType(TypeExpr):
|
||||||
|
def __init__(self, name: str) -> None:
|
||||||
|
super().__init__(Star(), name)
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class TypeVariable(TypeExpr):
|
||||||
|
"""
|
||||||
|
A placeholder in a type expression
|
||||||
|
"""
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class TypeConstructor(TypeExpr):
|
||||||
|
name: str
|
||||||
|
|
||||||
|
def __init__(self, kind: Arrow, name: str) -> None:
|
||||||
|
super().__init__(kind, name)
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class TypeApplication(TypeExpr):
|
||||||
|
constructor: TypeConstructor | TypeApplication | TypeVariable
|
||||||
|
argument: TypeExpr
|
||||||
|
|
||||||
|
def __init__(self, constructor: TypeConstructor | TypeApplication | TypeVariable, argument: TypeExpr) -> None:
|
||||||
|
if isinstance(constructor.kind, Star):
|
||||||
|
raise TypeError("A constructor cannot be a concrete type")
|
||||||
|
|
||||||
|
if constructor.kind.arg_kind != argument.kind:
|
||||||
|
raise TypeError("Argument does match construtor's expectations")
|
||||||
|
|
||||||
|
super().__init__(
|
||||||
|
constructor.kind.result_kind,
|
||||||
|
f"{constructor.name} {argument.name}",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.constructor = constructor
|
||||||
|
self.argument = argument
|
||||||
157
phasm/type5/unify.py
Normal file
157
phasm/type5/unify.py
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from .typeexpr import (
|
||||||
|
AtomicType,
|
||||||
|
TypeApplication,
|
||||||
|
TypeConstructor,
|
||||||
|
TypeExpr,
|
||||||
|
TypeVariable,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Failure:
|
||||||
|
"""
|
||||||
|
Both types are already different - cannot be unified.
|
||||||
|
"""
|
||||||
|
msg: str
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Action:
|
||||||
|
pass
|
||||||
|
|
||||||
|
class ActionList(list[Action]):
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return '{' + ', '.join(map(str, self)) + '}'
|
||||||
|
|
||||||
|
UnifyResult = Failure | ActionList
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ReplaceVariable(Action):
|
||||||
|
var: TypeVariable
|
||||||
|
typ: TypeExpr
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f'{self.var.name} := {self.typ.name}'
|
||||||
|
|
||||||
|
def unify(lft: TypeExpr, rgt: TypeExpr) -> UnifyResult:
|
||||||
|
if lft == rgt:
|
||||||
|
return ActionList()
|
||||||
|
|
||||||
|
if lft.kind != rgt.kind:
|
||||||
|
return Failure("Kind mismatch")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if isinstance(lft, AtomicType) and isinstance(rgt, AtomicType):
|
||||||
|
return Failure("Not the same type")
|
||||||
|
|
||||||
|
if isinstance(lft, AtomicType) and isinstance(rgt, TypeVariable):
|
||||||
|
return ActionList([ReplaceVariable(rgt, lft)])
|
||||||
|
|
||||||
|
if isinstance(lft, AtomicType) and isinstance(rgt, TypeConstructor):
|
||||||
|
raise NotImplementedError # Should have been caught by kind check above
|
||||||
|
|
||||||
|
if isinstance(lft, AtomicType) and isinstance(rgt, TypeApplication):
|
||||||
|
if is_concrete(rgt):
|
||||||
|
return Failure("Not the same type")
|
||||||
|
|
||||||
|
return Failure("Type shape mismatch")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if isinstance(lft, TypeVariable) and isinstance(rgt, AtomicType):
|
||||||
|
return unify(rgt, lft)
|
||||||
|
|
||||||
|
if isinstance(lft, TypeVariable) and isinstance(rgt, TypeVariable):
|
||||||
|
return ActionList([ReplaceVariable(lft, rgt)])
|
||||||
|
|
||||||
|
if isinstance(lft, TypeVariable) and isinstance(rgt, TypeConstructor):
|
||||||
|
return ActionList([ReplaceVariable(lft, rgt)])
|
||||||
|
|
||||||
|
if isinstance(lft, TypeVariable) and isinstance(rgt, TypeApplication):
|
||||||
|
if occurs(lft, rgt):
|
||||||
|
return Failure("One type occurs in the other")
|
||||||
|
|
||||||
|
return ActionList([ReplaceVariable(lft, rgt)])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if isinstance(lft, TypeConstructor) and isinstance(rgt, AtomicType):
|
||||||
|
return unify(rgt, lft)
|
||||||
|
|
||||||
|
if isinstance(lft, TypeConstructor) and isinstance(rgt, TypeVariable):
|
||||||
|
return unify(rgt, lft)
|
||||||
|
|
||||||
|
if isinstance(lft, TypeConstructor) and isinstance(rgt, TypeConstructor):
|
||||||
|
return Failure("Not the same type constructor")
|
||||||
|
|
||||||
|
if isinstance(lft, TypeConstructor) and isinstance(rgt, TypeApplication):
|
||||||
|
return Failure("Not the same type constructor")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if isinstance(lft, TypeApplication) and isinstance(rgt, AtomicType):
|
||||||
|
return unify(rgt, lft)
|
||||||
|
|
||||||
|
if isinstance(lft, TypeApplication) and isinstance(rgt, TypeVariable):
|
||||||
|
return unify(rgt, lft)
|
||||||
|
|
||||||
|
if isinstance(lft, TypeApplication) and isinstance(rgt, TypeConstructor):
|
||||||
|
return unify(rgt, lft)
|
||||||
|
|
||||||
|
if isinstance(lft, TypeApplication) and isinstance(rgt, TypeApplication):
|
||||||
|
con_res = unify(lft.constructor, rgt.constructor)
|
||||||
|
if isinstance(con_res, Failure):
|
||||||
|
return con_res
|
||||||
|
|
||||||
|
arg_res = unify(lft.argument, rgt.argument)
|
||||||
|
if isinstance(arg_res, Failure):
|
||||||
|
return arg_res
|
||||||
|
|
||||||
|
return ActionList(con_res + arg_res)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return Failure('Not implemented')
|
||||||
|
|
||||||
|
def occurs(lft: TypeVariable, rgt: TypeApplication) -> bool:
|
||||||
|
"""
|
||||||
|
Checks whether the given variable occurs in the given application.
|
||||||
|
"""
|
||||||
|
if lft == rgt.constructor:
|
||||||
|
return True
|
||||||
|
|
||||||
|
if lft == rgt.argument:
|
||||||
|
return True
|
||||||
|
|
||||||
|
if isinstance(rgt.argument, TypeApplication):
|
||||||
|
return occurs(lft, rgt.argument)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def is_concrete(lft: TypeExpr) -> bool:
|
||||||
|
"""
|
||||||
|
A concrete type has no variables in it.
|
||||||
|
|
||||||
|
This is also known as a monomorphic type.
|
||||||
|
"""
|
||||||
|
if isinstance(lft, AtomicType):
|
||||||
|
return True
|
||||||
|
|
||||||
|
if isinstance(lft, TypeVariable):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if isinstance(lft, TypeConstructor):
|
||||||
|
return True
|
||||||
|
|
||||||
|
if isinstance(lft, TypeApplication):
|
||||||
|
return is_concrete(lft.constructor) and is_concrete(lft.argument)
|
||||||
|
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def is_polymorphic(lft: TypeExpr) -> bool:
|
||||||
|
"""
|
||||||
|
A polymorphic type has one or more variables in it.
|
||||||
|
"""
|
||||||
|
return not is_concrete(lft)
|
||||||
0
tests/integration/test_type4/__init__.py
Normal file
0
tests/integration/test_type4/__init__.py
Normal file
113
tests/integration/test_type4/test_unify.py
Normal file
113
tests/integration/test_type4/test_unify.py
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from phasm.type4 import classes
|
||||||
|
from phasm.type4 import unify as sut
|
||||||
|
|
||||||
|
|
||||||
|
class TestNamespace:
|
||||||
|
## Kind * (part 1)
|
||||||
|
u32 = classes.Atomic('u32')
|
||||||
|
f64 = classes.Atomic('f64')
|
||||||
|
|
||||||
|
a = classes.Variable('a')
|
||||||
|
b = classes.Variable('b')
|
||||||
|
|
||||||
|
## Kind * -> *
|
||||||
|
dynamic_array = classes.ConcreteConSS('dynamic_array')
|
||||||
|
maybe = classes.ConcreteConSS('maybe')
|
||||||
|
f = classes.VariableConSS('f')
|
||||||
|
t = classes.VariableConSS('t')
|
||||||
|
|
||||||
|
## Kind * -> * -> *
|
||||||
|
function = classes.ConcreteConSSS('function')
|
||||||
|
either = classes.ConcreteConSSS('either')
|
||||||
|
|
||||||
|
## Kind Nat -> * -> *
|
||||||
|
static_array = classes.ConcreteConNSS('static_array')
|
||||||
|
|
||||||
|
## Kind * -> * (part 2)
|
||||||
|
function_u32 = classes.AppliedConSSS(function, u32)
|
||||||
|
function_f64 = classes.AppliedConSSS(function, f64)
|
||||||
|
either_u32 = classes.AppliedConSSS(either, u32)
|
||||||
|
either_f64 = classes.AppliedConSSS(either, f64)
|
||||||
|
static_array_4 = classes.AppliedConNSS(static_array, classes.Nat(4))
|
||||||
|
static_array_8 = classes.AppliedConNSS(static_array, classes.Nat(8))
|
||||||
|
|
||||||
|
## Kind * (part 2)
|
||||||
|
dynamic_array_u32 = classes.AppliedConSS(dynamic_array, u32)
|
||||||
|
dynamic_array_f64 = classes.AppliedConSS(dynamic_array, f64)
|
||||||
|
maybe_u32 = classes.AppliedConSS(maybe, u32)
|
||||||
|
maybe_f64 = classes.AppliedConSS(maybe, f64)
|
||||||
|
t_u32 = classes.AppliedConSS(t, u32)
|
||||||
|
t_f64 = classes.AppliedConSS(t, f64)
|
||||||
|
f_u32 = classes.AppliedConSS(f, u32)
|
||||||
|
f_f64 = classes.AppliedConSS(f, f64)
|
||||||
|
function_f64_u32 = classes.AppliedConSS(function_f64, u32)
|
||||||
|
either_f64_u32 = classes.AppliedConSS(either_f64, u32)
|
||||||
|
static_array_4_u32 = classes.AppliedConSS(static_array_4, u32)
|
||||||
|
static_array_8_u32 = classes.AppliedConSS(static_array_8, u32)
|
||||||
|
|
||||||
|
TEST_LIST: list[tuple[classes.Type4, classes.Type4, sut.UnifyResult]] = [
|
||||||
|
# Atomic / Atomic
|
||||||
|
(u32, u32, []),
|
||||||
|
(u32, f64, sut.Failure('Cannot unify u32 and f64')),
|
||||||
|
# Atomic / Variable
|
||||||
|
(u32, a, [sut.SetTypeForVariable(a, u32)]),
|
||||||
|
# Atomic / AppliedConSS
|
||||||
|
(u32, t_f64, sut.Failure('Cannot unify u32 and t f64')),
|
||||||
|
|
||||||
|
# Variable / Atomic
|
||||||
|
(a, u32, [sut.SetTypeForVariable(a, u32)]),
|
||||||
|
# Variable / Variable
|
||||||
|
(a, b, [sut.ReplaceVariable(a, b)]),
|
||||||
|
# Variable / AppliedConSS
|
||||||
|
(a, t_f64, [sut.SetTypeForVariable(a, t_f64)]),
|
||||||
|
|
||||||
|
# AppliedConSS / Atomic
|
||||||
|
(t_f64, u32, sut.Failure('Cannot unify t f64 and u32')),
|
||||||
|
# AppliedConSS / Variable
|
||||||
|
(t_f64, a, [sut.SetTypeForVariable(a, t_f64)]),
|
||||||
|
|
||||||
|
# AppliedConSS ConcreteConSS / AppliedConSS ConcreteConSS
|
||||||
|
(dynamic_array_u32, dynamic_array_u32, []),
|
||||||
|
(dynamic_array_u32, maybe_u32, sut.Failure('Cannot unify dynamic_array u32 and maybe u32')),
|
||||||
|
# AppliedConSS ConcreteConSS / AppliedConSS VariableConSS
|
||||||
|
(dynamic_array_u32, t_u32, [sut.SetTypeForConSSVariable(t, dynamic_array)]),
|
||||||
|
# AppliedConSS ConcreteConSS / AppliedConSS AppliedConSSS
|
||||||
|
(dynamic_array_u32, function_f64_u32, sut.Failure('Cannot unify dynamic_array u32 and function f64 u32')),
|
||||||
|
# AppliedConSS ConcreteConSS / AppliedConSS AppliedConNSS
|
||||||
|
(dynamic_array_u32, static_array_4_u32, sut.Failure('Cannot unify dynamic_array u32 and static_array 4 u32')),
|
||||||
|
|
||||||
|
# AppliedConSS VariableConSS / AppliedConSS ConcreteConSS
|
||||||
|
(t_u32, dynamic_array_u32, [sut.SetTypeForConSSVariable(t, dynamic_array)]),
|
||||||
|
# AppliedConSS VariableConSS / AppliedConSS VariableConSS
|
||||||
|
(t_u32, f_u32, [sut.ReplaceConSSVariable(t, f)]),
|
||||||
|
# AppliedConSS VariableConSS / AppliedConSS AppliedConSSS
|
||||||
|
(t_u32, function_f64_u32, [sut.SetTypeForConSSVariable(t, function_f64)]),
|
||||||
|
# AppliedConSS VariableConSS / AppliedConSS AppliedConNSS
|
||||||
|
(t_u32, static_array_4_u32, [sut.SetTypeForConSSVariable(t, static_array_4)]),
|
||||||
|
|
||||||
|
# AppliedConSS AppliedConSSS / AppliedConSS ConcreteConSS
|
||||||
|
(function_f64_u32, dynamic_array_u32, sut.Failure('Cannot unify function f64 u32 and dynamic_array u32')),
|
||||||
|
# AppliedConSS AppliedConSSS / AppliedConSS VariableConSS
|
||||||
|
(function_f64_u32, t_u32, [sut.SetTypeForConSSVariable(t, function_f64)]),
|
||||||
|
# AppliedConSS AppliedConSSS / AppliedConSS AppliedConSSS
|
||||||
|
# (function_f64_u32, function_f64_u32, []),
|
||||||
|
(function_f64_u32, either_f64_u32, sut.Failure('Cannot unify function f64 u32 and either f64 u32')),
|
||||||
|
# AppliedConSS AppliedConSSS / AppliedConSS AppliedConNSS
|
||||||
|
(function_f64_u32, static_array_4_u32, sut.Failure('Cannot unify function f64 u32 and static_array 4 u32')),
|
||||||
|
|
||||||
|
# AppliedConSS AppliedConNSS / AppliedConSS ConcreteConSS
|
||||||
|
(static_array_4_u32, dynamic_array_u32, sut.Failure('Cannot unify static_array 4 u32 and dynamic_array u32')),
|
||||||
|
# AppliedConSS AppliedConNSS / AppliedConSS VariableConSS
|
||||||
|
(static_array_4_u32, t_u32, [sut.SetTypeForConSSVariable(t, static_array_4)]),
|
||||||
|
# AppliedConSS AppliedConNSS / AppliedConSS AppliedConSSS
|
||||||
|
(static_array_4_u32, function_f64_u32, sut.Failure('Cannot unify static_array 4 u32 and function f64 u32')),
|
||||||
|
# AppliedConSS AppliedConNSS / AppliedConSS AppliedConNSS
|
||||||
|
(static_array_4_u32, static_array_4_u32, []),
|
||||||
|
(static_array_4_u32, static_array_8_u32, sut.Failure('Cannot unify static_array 4 u32 and static_array 8 u32')),
|
||||||
|
]
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('lft,rgt,exp_out', TestNamespace.TEST_LIST)
|
||||||
|
def test_unify(lft: classes.Type4, rgt: classes.Type4, exp_out: sut.UnifyResult) -> None:
|
||||||
|
assert exp_out == sut.unify(lft, rgt)
|
||||||
Loading…
x
Reference in New Issue
Block a user