nac3-spec/README.md

184 lines
5.6 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.
2020-12-22 16:56:40 +08:00
A toy implementation is in [`toy-impl`](./toy-impl), requires python 3.9.
2020-12-17 14:52:51 +08:00
2020-12-15 13:09:32 +08:00
## 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.
2020-12-17 09:48:58 +08:00
* Function default parameters are not allowed, as changes to the default value
would not be kept across kernel calls, and that is a potential source of
confusion.
2020-12-15 17:33:31 +08:00
2020-12-17 10:22:46 +08:00
## Built-in Types
* Primitive types include:
* `bool`
2020-12-17 14:52:24 +08:00
* `byte`
2020-12-17 10:22:46 +08:00
* `int32`
* `int64`
2020-12-17 10:27:11 +08:00
* `uint32`
* `uint64`
2020-12-17 10:22:46 +08:00
* `float`
* `str` (note: fixed length, provide builtin methods?)
2020-12-17 14:52:24 +08:00
* `bytes` (a list of `byte`, but with more convenient syntax)
2020-12-17 10:22:46 +08:00
* Collections include:
* `list`: homogeneous (elements must be of the same type) fixed-size (no
append) list.
* `tuple`: inhomogeneous fixed-size list, only pattern
matching (e.g. `a, b, c = (1, True, 1.2)`) and constant indexing
is supported:
```
t = (1, True)
# OK
a, b = t
# OK
a = t[0]
# Not OK
i = 0
a = t[i]
```
2020-12-17 10:27:11 +08:00
* `range` (over numerical types)
2020-12-17 10:22:46 +08:00
2020-12-17 14:52:24 +08:00
### Numerical Types
* All binary operations expect the values to have the same type, no implicit
coercion would be performed, explicit casting is required.
* Casting can be done by `T(v)` where `T` is the target type, and `v` is the
original value. Examples: `int64(123)`
* Constant integers are treated as `int32` by default. If the value cannot
be stored in `int32`, `uint64` would be used if the integer is non-negative,
and `int64` would be used it the integer is negative.
* Only `uint32`, `int32` (and range of them) can be used as index.
2020-12-15 17:33:31 +08:00
## 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
```
* Type variables can only be used in functions/methods, but not in classes.
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 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.
* Type variable and union cannot occur alone in the result type.
2020-12-15 17:33:31 +08:00
## 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.
2020-12-16 15:19:35 +08:00
`virtual[T]` is the type for `T` and its subtypes(derived types).
2020-12-15 17:33:31 +08:00
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
2020-12-16 15:19:35 +08:00
Type variables cannot be used inside `virtual[...]`, and type variables would not
range over `virtual[...]`.
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 11:27:47 +08:00