nac3-spec/README.md

139 lines
4.4 KiB
Markdown
Raw Normal View History

2020-12-15 11:27:47 +08:00
# NAC3 Specification
2020-12-15 13:09:32 +08:00
Specification and discussions about language design.
## Referencing Python Variables
The kernel is allowed to read Python variables.
2020-12-15 17:33:31 +08:00
* Unbounded identifiers would be considered as Python variables, no object is
allowed, only primitive types and tuple/list of allowed types are allowed.
(not sure how to express the recursive concept neatly in English...)
* The value would be evaluated at compile time, subsequent modification
in the host would not be known by the kernel.
* Modification of global variable from within the kernel would be considered as
error.
* Calling non-RPC host function would be considered as an error. (RPC functions
must be annotated.)
Example code that would be disallowed:
2020-12-15 13:09:32 +08:00
```py
from artiq.experiment import *
counter = 0
def get_id():
counter += 1
return counter
class Foo(EnvExperiment):
@kernel
def run(self):
param = get_id()
# do something...
result = param
return result
```
2020-12-15 17:33:31 +08:00
## Class and Functions
* Class fields must be annotated:
```py
class Foo:
a: int
b: int
def __init__(self, a: int, b: int):
self.a = a
self.b = b
```
* Functions require full type signature, including type annotation to every
parameter and return type.
```py
def add(a: int, b: int) -> int:
return a + b
```
* No implicit coercion, require implicit cast. Integers are int32 by default,
floating point numbers are double by default.
* RPCs: optional parameter type signature, require return type signature.
* Function pointer is supported.
* Method pointer is a fat pointer (function pointer + object pointer), and
subject to lifetime check.
## Generics
We use [type variable](https://docs.python.org/3/library/typing.html#typing.TypeVar) for denoting generics.
2020-12-15 13:09:32 +08:00
2020-12-15 17:33:31 +08:00
Example:
2020-12-15 13:09:32 +08:00
```py
2020-12-15 17:33:31 +08:00
from typing import TypeVar
T = TypeVar('T')
2020-12-15 13:09:32 +08:00
class Foo(EnvExperiment):
@kernel
2020-12-15 17:33:31 +08:00
# type of a is the same as type of b
def run(self, a: T, b: T) -> bool:
return a == b
2020-12-15 13:09:32 +08:00
```
2020-12-15 17:33:31 +08:00
* Type variable can be limited to a fixed set of types. A shorthand for one-time
type variable limited to a fixed set of types is [union type & optional type](https://docs.python.org/3/library/typing.html#typing.Union).
e.g. `def run(self, a: Union[int, str])`
* Type variables support bounded generic, so we can access attributes of the
variable that are present in the boundary type, the type instantiated must be
the subtype of the boundary type. See [PEP484](https://www.python.org/dev/peps/pep-0484/#type-variables-with-an-upper-bound) for details.
* Type variables are invariant, same as the default in Python. We disallow
covariant or contravariant. The compiler should mark as error if it encounters
a type variable used in kernel that is declared covariant or contravariant.
* Code region protected by a type check, such as `if type(x) == int:`, would
treat `x` as `int`, similar to how [typescript type guard](https://www.typescripttutorial.net/typescript-tutorial/typescript-type-guards/) works.
```py
def add1(x: Union[int, bool]) -> int:
if type(x) == int:
# x is int
return x + 1
else:
# x must be bool
return 2 if x else 1
```
* Generics are instantiated at compile time, all the type checks like
`type(x) == int` would be evaluated as constants. Type checks are not allowed
in area outside generics.
## Dynamic Dispatch
Type annotations are invariant, so subtype (derived types) cannot be used
when the base type is expected. Example:
2020-12-15 13:09:32 +08:00
```py
class Base:
2020-12-15 17:33:31 +08:00
def foo(self) -> int:
2020-12-15 13:09:32 +08:00
return 1
2020-12-15 17:33:31 +08:00
class Derived(Base):
def foo(self) -> int:
2020-12-15 13:09:32 +08:00
return 2
2020-12-15 18:01:01 +08:00
def bar(x: list[Base]) -> int:
2020-12-15 17:33:31 +08:00
sum = 0
for v in x:
sum += v.foo()
return sum
2020-12-15 13:09:32 +08:00
2020-12-15 18:01:01 +08:00
# incorrect, the type signature of the list is `list[virtual[Base]]`
2020-12-15 17:33:31 +08:00
bar([Base(), Derived()])
2020-12-15 13:09:32 +08:00
```
2020-12-15 17:33:31 +08:00
Dynamic dispatch is supported, but requires explicit annotation, similar to
[trait object](https://doc.rust-lang.org/book/ch17-02-trait-objects.html) in rust.
This is mainly for performance consideration, as virtual method table that is
required for dynamic dispatch would penalize performance, and prohibits function
inlining etc.
2020-12-15 13:09:32 +08:00
Example:
```py
2020-12-15 18:01:01 +08:00
def bar2(x: list[virtual[Base]]) -> int:
2020-12-15 17:33:31 +08:00
sum = 0
for v in x:
sum += v.foo()
return sum
# correct
bar([Base(), Derived()])
2020-12-15 13:09:32 +08:00
```
2020-12-15 18:01:10 +08:00
Structural subtyping support is not determined yet. Attractive, but probably
harder and slower.
2020-12-15 11:27:47 +08:00