Replies: 3 comments 4 replies
-
Thanks for this proposal, Luca! I'll have a deeper think about it later, here are some initial questions: 1. Moved argumentsHow would you handle this function: def foo(q: qubit):
r = q
h(r) Or this one: def foo(q1: qubit, q2: qubit, b: bool):
if b:
q1, q2 = q2, q1
cx(q1, q2) I assume this would require an explicit return? Current Guppy rejects the assignments since borrowed values cannot be moved. 2. Classical returnsAm I right in assuming that returning classical data from a function always requires an explicit return, threading through all linear inputs (see your last example)? 3. Infectious explicit returnsIf (1) requires an explicit return, then it seems like explicity is "infectious": As soon as one function is explicit, all functions that call this function must be explicit as well: def foo(q: qubit) -> (qubit, float):
x: float = ... # Some classical computation
return q, x # Return must be explicit?
def bar(q: qubit) -> qubit:
q, x = foo(q) # Call must be explicit
# Do something with `x`, then throw it away
return q # Now, bar also needs an explicit return :(
def baz(q: qubit) -> qubit:
q = bar(q) # Call must be explicit :(
return q # Return must be explicit :( In current Guppy, taking ownership is infectious as well, but imo it's useful there since it tells us about the lifetime of qubits. Here, I don't see the benefit of |
Beta Was this translation helpful? Give feedback.
-
Regarding the other points you made, I don't fully agree with
The current system can also very easily be understood as syntactic sugar: Every function implicitly returns all linear arguments that are not
Hmm would it be that bad to do import guppylang.std.quantum_functional as F
def plus_state() -> qubit:
return F.h(qubit()) This is a common pattern in e.g. pytorch.
That's a fair point though! |
Beta Was this translation helpful? Give feedback.
-
Viewed this way, I think my proposal does not change the semantics of My proposal changes two things
@guppy
def wrong_h(q: qubit) -> qubit:
q2 = qubit()
h(q2)
return q2
q = qubit()
qq = wrong_h(q) # Now this looks like a functional call...
# ...but in fact we now have two qubits alive
I claim that because only a restricted subset of functions may use the implicit return call style, it is never ambiguous or confusing what the program does. |
Beta Was this translation helpful? Give feedback.
-
It is no secret, I have never liked having two functions for every gate type. But today is different -- I come with arguments and a concrete proposal that I hope you find convincing.
Argument
Most often, the borrowing signatures are most useful. However, there are cases where their functional equivalent would be nicer to use. I would much rather write:
rather than
Starting to mix two functions for the same gate within my code is so ugly I would refuse to do so. But even discarding this subjective choice: using the two versions is not even an option for user-defined code: if I were to write my custom
def my_hadamard
, I would have to pick my poison and stick with it.Concrete proposal
I propose to change the semantics of function definition and function calls in guppy as follows:
Every function is functional (i.e. we only keep
quantum_functional
)However, we keep the current convention in the type signature: linear types have borrow-like semantics by default and must be tagged with
@owned
to take ownership.When no linear resource is either created or destroyed, the return type can be easily inferred from the function arguments. In such cases specifying the return type and the return statement is optional (this is syntactic sugar). Specifically, an explicit return type and return statement are required if one of the following applies
@owned
Whenever none of these apply, the return type is inferred to be all linear typed arguments, in the same order.
We introduce the equivalent syntactic sugar also at the call site: whenever a function is called and both
are satisfied, we assign the output to the list of linear resources passed to the function.
Linear resources passed as function arguments not marked as
@owned
may not be discarded.Advantages
def f(q: qubit) -> qubit
are just plain confusing. Whenever linear resources are created or discarded, the user is encouraged to think and express explicitly where the resources go.Examples
would be read as
FAQ
Isn't @owned redundant?
It is true that
@owned
no longer changes the type of the argument -- this is the type simplification that is mentioned earlier. However, it signals the user's intention to consume the qubit. Concretely, it serves three purposesBeta Was this translation helpful? Give feedback.
All reactions