diff --git a/specification/hugr.md b/specification/hugr.md index 25a6eedac..3ba44269f 100644 --- a/specification/hugr.md +++ b/specification/hugr.md @@ -829,7 +829,7 @@ See [Type System](#type-system) for more on Resources. # Import other header files to use their custom types # TODO: allow qualified, and maybe locally-scoped -imports: [Quantum] +imports: [Quantum, Array] resources: - name: MyGates @@ -908,8 +908,17 @@ The declaration of the `params` uses a language that is a distinct, simplified form of the [Type System](#type-system) - writing terminals that appear in the YAML in quotes, the value of each member of `params` is given by the following production: ``` -TypeParam ::= "Type" | "ClassicType" | USize | "List"(TypeParam) +TypeParam ::= "Type"("Any"|"Copy"|"Eq") | "USize" | "Resources" | "List"(TypeParam) | "Tuple"([TypeParam]) | Opaque + +Opaque ::= string<[TypeArgs]> + +TypeArgs ::= Type(Type) | USize(u64) | Resources | List([TypeArg]) | Tuple([TypeArg]) + +Type ::= Name<[TypeArg]> ``` +(We write `[Foo]` to indicate a list of Foo's; and omit `<>` where the contents is the empty list). + +To use an OpDef as an Op, or a TypeDef as a type, the user must provide a type argument for each type param in the def: a type in the appropriate class, a constant usize, a set of resources, a list or tuple of arguments. **Implementation note** Reading this format into Rust is made easy by `serde` and [serde\_yaml](https://github.com/dtolnay/serde-yaml) (see the @@ -980,97 +989,45 @@ indices after the list of node indices? ## Type System -The type system will resemble the tierkreis type system, but with some -extensions. Namely, the things the tierkreis type system is missing are: +There are three classes of type: Any $\supset$ Copyable $\supset$ Equatable. Types in these classes are distinguished by the operations possible on (runtime) values of those types: + - For the broadest class ("Any" type), the only operation supported is the identity operation (aka no-op, or `lift` - see [Resource Tracking](#resource-tracking) below). Specifically, we do not require it to be possible to copy or discard all values, hence the requirement that outports of linear type must have exactly one edge. (That is, a type not known to be in the copyable subset). All incoming ports must have exactly one edge. + + In fully qubit-counted contexts programs take in a number of qubits as input and return the same number, with no discarding. See [quantum resource](#quantum-resource) for more. + + - The next class are "Copyable" types, aka "Classic" types, where values can be copied (and discarded, the 0-ary copy). This allows multiple (or 0) outgoing edges from an outport; also these types can be sent down Static edges. - - User-defined types - - Resource management - knowing what plugins a given graph depends on + - The final class are "Equatable" types: these are Copyable types where additionally there is a well-defined notion of equality between values. (While *some* notion of equality is defined on + any type with a binary representation, that if the bits are equal then the value is, the converse is not necessarily true - values that are indistinguishable can have different bit representations.) -A grammar of available types is defined below. +For example, a `float` type (defined in an extension) would be Copyable, but not Equatable. Also, Hugr "classes" loosely correspond to Tierkreis' notion of "constraints". + +**Row Types** The `#` is a *row type* which consists of zero or more types. Types in the row can optionally be given names in metadata i.e. this does not affect behaviour of the HUGR. + +The Hugr defines a number of type constructors, that can be instantiated into types by providing some collection of types as arguments. The constructors are given in the following grammar: ```haskell -Type ::= [Resources]SimpleType --- Rows are ordered lists, not sets -# ::= #(SimpleType) -#(T) ::= (T)* - -Resources ::= (Resource)* -- set not list - -Container(T) ::= Tuple(#(T)) - | Array(T) - | NewType(Name, T) - | Sum (#(T)) -ClassicType ::= USize - | Var(X) - | String - | Graph[R](#, #) - | Opaque(Name, #) - | Container(ClassicType) -SimpleType ::= Qubit - | QPaque(Name, #) - | Container(SimpleType) -- Sometimes called 'Qontainer' - | ClassicType + +Resources ::= (Resource)* -- a set, not a list + +Type ::= Tuple(#) -- fixed-arity, heterogenous components + | Sum(#) -- disjoint union of other types, ??tagged by unsigned int?? + | Graph[Resources](#, #) -- monomorphic + | Opaque(Name, TypeArgs) -- a (instantiation of a) custom type defined by an extension ``` + + +The majority of types will be Opaque ones defined by extensions including the [standard library](#standard-library). However a number of types can be constructed using only the core type constructors: for example the empty tuple type, aka `unit`, with exactly one instance (so 0 bits of data); the empty sum, with no instances; the empty Graph type (taking no arguments and producing no results - `void -> void`); and compositions thereof. -SimpleTypes are the types of *values* which can be sent down wires, -except for type variables `Var`. All of the ClassicTypes can also be -sent down Static edges. - -Function signatures are made up of *rows* (\#), which consist of an -arbitrary number of SimpleTypes, plus a resource spec. - -The `USize` type represents 64-bit unsigned integers. - -ClassicTypes such as `USize` are fixed-size, as is Qubit. -`Sum` is a disjoint union tagged by unsigned int; `Tuple`s have -statically-known number and type of elements, as does `Array` (where -N is a static constant). These types are also fixed-size if their -components are. - -Container types are defined in terms of statically-known element types. -Besides `Array`, `Sum` and `Tuple`, these also include the variable-sized -types: `Graph`. `NewType` -allows named newtypes to be used. Containers are classic (copyable) only -if all of their components are classic. - -Note: any array can be turned into an equivalent tuple, but arrays also -support dynamically-indexed `get`. (TODO: Indexed by u64, with panic if -out-of-range? Or by known-range `Sum( ()^N )`?) - -**Row Types** The `#` is a *row type* which consists of zero or more -simple types. Types in the row can optionally be given names in metadata -i.e. this does not affect behaviour of the HUGR. Row types are used - - - in the signatures for `Graph` inputs and outputs, and functions - - Tuples - the 0-ary Tuple `()` aka `unit` takes no storage - - Sums - allowing a bounded nat: `Sum((),(),())` is a ternary value; - the 2-ary version takes the place of a boolean type - - Arguments to `Opaque` types - where their meaning is - extension-defined. - -### Linearity - -For expressing and rewriting quantum programs we draw a distinction between -arbitrary `SimpleType`s and the subset of `ClassicType`s. Only `ClassicType`s -may be used more than once or discarded; the former must always be used -exactly once. This leads to a constraint on the HUGR that outgoing ports -must have exactly one edge leaving them unless they are `ClassicType`, where -outgoing ports may have any number of connected edges (0 is equivalent -to a discard). All incoming ports must have exactly one edge connected to them. - -In fully qubit-counted contexts programs take in a number of qubits -as input and return the same number, with no discarding. See -[quantum resource](#quantum-resource) for more. - -### Resources - -The type of `Graph` has been altered to add -*R*: a resource requirement. -The *R* here refer to a set -of [resources](#resources) which are required to produce a given type. -Graphs are annotated with the resources that they need to run and, when -run, their outputs are annotated with those resources. Keeping track of -the resource requirements of graphs allows plugin designers and backends +Graphs are Copyable, but not Equatable (as they represent functions: it is undecidable whether two functions produce the same result for all possible inputs, or similarly whether one computation graph can be rewritten into another by semantic-preserving rewrites). + +Tuples and Sums are Copyable (or Equatable) if all their components are, also are fixed-size if their components are. + +### Resource Tracking + +The type of `Graph` includes a set of resources (that is, [Extensions](#extension-implementation)) which are required to execute the graph. Every node in the HUGR is annotated with the set of resources required to produce its inputs, and the set of resources required to execute the node; the union of these two must match the set of resources on each successor node. + +Keeping track of the resource requirements like this allows extension designers and backends (like tierkreis) to control how/where a module is run. Concretely, if a plugin writer adds a resource @@ -1085,33 +1042,8 @@ running different resources. By the same mechanism, Tierkreis can reason about where to run different parts of the graph by inspecting their resource requirements. -### Type Constraints - -We will likely also want to add a fixed set of attributes to certain -subsets of `TYPE`. In Tierkreis these are called “type constraints”. For -example, a potential `HashMap` type can only be constructed when the type that we -map from is `Hashable`. For the Hugr, we will need this `Hashable` -constraint, as well as a `Classic` constraint. - - -### Resources - -On top of the Tierkreis type system, will be a system of Resources. A -resource is a collection of operations which are available to use in -certain graphs. The operations must be callable at the point of -execution, but otherwise only signatures need to be known. Edges contain -information on the resources that were needed to produce them, and -Functions note their resource requirements in their `Graph` type. All -operations declared as part of a resource interface implicitly have the -resource they pertain to as a requirement. - -Resources can be added and used by plugin writers. We will also have -some built in resources, see -[standard library](#standard-library) - -Unification will demand that resource constraints are equal and, to make -it so, we will have an operations called **lift** and **liftGraph** -which can add a resource constraints to values. +To allow resource annotations on nodes to be made equal, we will have operations + **lift** and **liftGraph** which can add resource constraints to values. $\displaystyle{\frac{v : [ \rho ] T}{\textbf{lift} \langle X \rangle (v) : [X, \rho] T}}$