-
Notifications
You must be signed in to change notification settings - Fork 55
[WIP]: Proposal for allocatable types and generalization of initializers #41
base: main
Are you sure you want to change the base?
Conversation
| Built-in Initializer | Description | Allocated Type | Initializer Argument Type | Initializer Expression | | ||
| --- | --- | --- | --- | --- | | ||
| Qubit | allocates a single qubit | `Qubit` | `Unit` | `Qubit()` | | ||
| Qubits | allocates a 1D array of qubits | `Qubit[]` | `Int` | `Qubits(4)` | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changing arrays allocation syntax from Qubit[4] to Qubits(4) is significant enough to have a separate proposal. And looks like this can be done independently from the initializers proposal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I understand correctly, this does not modify array allocation (see microsoft/qsharp-compiler#589 for that proposal), but only the syntax for initializing registers of qubits.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good to me. Since we're discussing it here, I would like to better understand advantage of Qubits(4) over Qubit[4].
| `H` | `Qubit` | `Unit` | `init within H` | Yes | | ||
| `Reset` | `Qubit` | `Int` | `init then Reset` | No | | ||
| `ApplyToEachA(H, _)` | `Qubit[]` | `Int` | `init(3) within ApplyToEachA(H, _)` | Yes | | ||
| `ApplyToEach(H, _)` | `Qubit[]` | `Int` | `init(3) then ApplyToEach(H, _)` | No | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do I get it right that these two code snippets are the same?
using (a = Qubit[3]) { ApplyToEach(H, a); }
using (b = init(3) then ApplyToEach(H, _)) { }
If so, how much value do we get from the 'then' keyword?
Initializers built by elevating operations: | ||
| Operation Valued Expression | Allocated Type | Initializer Argument Type | Initializer Expression | Uncompute upon Release | | ||
| --- | --- | --- | --- | --- | | ||
| `H` | `Qubit` | `Unit` | `init within H` | Yes | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To me, it's not obvious from this statement that we allocated qubits and not something else. I think that it is important to keep 'Qubit' in the syntax. May be:
using (q = Qubit()) { } // Exists today
using (qh = Qubit(H)) { } // One qubit in the H state, uncompute upon release.
using (r = Qubit[3]) { } // Exists today
using (rh = Qubit(H)[3]) { } // A register of 3 qubits in the H state, uncompute upon release.
using (r2 = [Qubit(H), Qubit(X)]) { } // Array of two qubits in different states.
A type is allocatable if it either is one of the built-in allocatable types, or if it contains one or more items which are allocatable. There are two built-in allocatable types: the type Qubit and the type Qubit[], or more generally n-dimensional rectangular Qubit arrays (as suggested [here](https://github.com/microsoft/qsharp-language/issues/39)). | ||
|
||
### *Initializers for allocatable types*: | ||
Initializers are special callables that return values of an allocatable type. Initializers are *neither* operations nor functions. They are their own distinct type with no subtyping relation to operation or function types; they cannot be used when a value of operation or function type is required. Initializers can only be used as part of [qubit allocation statements](https://github.com/microsoft/qsharp-language/blob/main/Specifications/Language/2_Statements/QuantumMemoryManagement.md#quantum-memory-management). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are initializers first-class values (i.e.: can I assign an initializer to a variable binding)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So far I was not planning to support that - they could literally only be used within allocation statements. I don't see a reason to support it at this time. You can always pass an operation that is elevated though. Do you have a use case in mind where you would want to be able to pass an initializer?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense, wasn't trying to suggest that we need first-class initializers per se so much as to understand the scope of the proposal. Thanks for the clarification!
| --- | --- | --- | --- | --- | | ||
| Qubit | allocates a single qubit | `Qubit` | `Unit` | `Qubit()` | | ||
| Qubits | allocates a 1D array of qubits | `Qubit[]` | `Int` | `Qubits(4)` | | ||
| Qubits | allocates a [2D array](https://github.com/microsoft/qsharp-language/issues/39) of qubits | `Qubit[,]` | `(Int, Int)` | `Qubits(2,4)` | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Effectively, it seems this is a kind of overloading, in that Qubits
can either have type Int ~> Qubit[]
or (Int, Int) ~> Qubit[,]
. Is that represented by a type parameter to Qubits
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The overloading on tuple arity seems weird to me - it's compiler magic that can't be expressed in the language itself. I don't think it would not be possible to write a custom initializer that does this. A type parameter would allow any type, not just tuples of Int
s.
| Built-in Initializer | Description | Allocated Type | Initializer Argument Type | Initializer Expression | | ||
| --- | --- | --- | --- | --- | | ||
| Qubit | allocates a single qubit | `Qubit` | `Unit` | `Qubit()` | | ||
| Qubits | allocates a 1D array of qubits | `Qubit[]` | `Int` | `Qubits(4)` | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I understand correctly, this does not modify array allocation (see microsoft/qsharp-compiler#589 for that proposal), but only the syntax for initializing registers of qubits.
| --- | --- | --- | --- | --- | | ||
| `H` | `Qubit` | `Unit` | `init within H` | Yes | | ||
| `Reset` | `Qubit` | `Int` | `init then Reset` | No | | ||
| `ApplyToEachA(H, _)` | `Qubit[]` | `Int` | `init(3) within ApplyToEachA(H, _)` | Yes | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it's in scope, it may be helpful to use ApplyXorInPlace
as an example, since that demonstrates how init
/then
can be used to really help out with preparing structured data. In particular, I think that builds well on the example at line 68.
|
||
### *Custom initializers for user defined types:* | ||
Support for defining custom initializers for user defined types is intended to be added if and when custom constructors can be defined. Important considerations for what can and cannot be supported for such custom initializers, however, are part of this proposal to ensure that the proposed additions can be extended to cover that scenario as well. | ||
In contrast to operations, initializers allocate and return qubits that remain live after their execution terminates. Such qubits are allocated via a dedicated statement, consisting of the keyword `initialize` followed by a symbol or symbol tuple, an equals sign `=`, and either an initializer or an initializer tuple. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having two keywords init
and initialize
that mean different things seems confusing to me. It's similar to the fun
and function
distinction in F#, which I also think is not very intuitive.
1. One is that an operation should be invoked on the allocated qubits after initialization, followed by further computations before the qubits are measured and released. In this case, it is not necessary or desirable that the operation performed upon initialization is un-computed upon release. | ||
2. In the second case, the computations following the initialization lead to the qubits being in the same state as they were initialized. In this case, the adjoint of the operation used for initialization needs to be applied upon releasing the qubits. | ||
|
||
The proposal is to syntactically distinguish the two cases. The table below contains examples for both cases. In the first case, the initializer consists of the keyword `init`, followed by the initializer argument tuple, the keyword `then` and an operation-valued expression. The initializer argument tuple may be omitted if the required argument is of type `Unit`. The second case follows the same pattern with the exception that instead of the keyword `then`, then keyword `within` should be used, and the operation-valued expression needs to support the `Adjoint` functor. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The initializer argument tuple may be omitted if the required argument is of type
Unit
.
We don't allow omitting ()
when calling functions or operations. What is the reason to support it here?
1. One is that an operation should be invoked on the allocated qubits after initialization, followed by further computations before the qubits are measured and released. In this case, it is not necessary or desirable that the operation performed upon initialization is un-computed upon release. | ||
2. In the second case, the computations following the initialization lead to the qubits being in the same state as they were initialized. In this case, the adjoint of the operation used for initialization needs to be applied upon releasing the qubits. | ||
|
||
The proposal is to syntactically distinguish the two cases. The table below contains examples for both cases. In the first case, the initializer consists of the keyword `init`, followed by the initializer argument tuple, the keyword `then` and an operation-valued expression. The initializer argument tuple may be omitted if the required argument is of type `Unit`. The second case follows the same pattern with the exception that instead of the keyword `then`, then keyword `within` should be used, and the operation-valued expression needs to support the `Adjoint` functor. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The proposal is to syntactically distinguish the two cases. The table below contains examples for both cases. In the first case, the initializer consists of the keyword `init`, followed by the initializer argument tuple, the keyword `then` and an operation-valued expression. The initializer argument tuple may be omitted if the required argument is of type `Unit`. The second case follows the same pattern with the exception that instead of the keyword `then`, then keyword `within` should be used, and the operation-valued expression needs to support the `Adjoint` functor. | |
The proposal is to syntactically distinguish the two cases. The table below contains examples for both cases. In the first case, the initializer consists of the keyword `init`, followed by the initializer argument tuple, the keyword `then` and an operation-valued expression. The initializer argument tuple may be omitted if the required argument is of type `Unit`. The second case follows the same pattern with the exception that instead of the keyword `then`, the keyword `within` should be used, and the operation-valued expression needs to support the `Adjoint` functor. |
A type is allocatable if it either is one of the built-in allocatable types, or if it contains one or more items which are allocatable. There are two built-in allocatable types: the type Qubit and the type Qubit[], or more generally n-dimensional rectangular Qubit arrays (as suggested [here](https://github.com/microsoft/qsharp-language/issues/39)). | ||
|
||
### *Initializers for allocatable types*: | ||
Initializers are special callables that return values of an allocatable type. Initializers are *neither* operations nor functions. They are their own distinct type with no subtyping relation to operation or function types; they cannot be used when a value of operation or function type is required. Initializers can only be used as part of [qubit allocation statements](https://github.com/microsoft/qsharp-language/blob/main/Specifications/Language/2_Statements/QuantumMemoryManagement.md#quantum-memory-management). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having three different types of callables in Q# is a lot, especially since initializer names overlap with both type names and type constructor names. The automatic conversion from e.g. LittleEndian(Qubit[])
to LittleEndian(Int)
is also a little confusing - when I see LittleEndian(3)
, I have to remember that:
- It's in an initializer expression, so
LittleEndian
refers to the initializer, not the constructor, which takes a different argument type. - The
Int
maps back to the length of aQubit
array, even thoughQubit
doesn't appear anywhere in the expression.
I think there is a lot of cognitive overhead needed to interpret this syntax, and I wonder if making the Qubit
array explicit would be better, such as LittleEndian(Qubits(3))
.
This proposal is work in progress. I am putting up a draft for early feedback and discussion.