You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Nov 3, 2021. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 3, 2021. It is now read-only.
Text format of function calls with multi-value arguments #18
Is there a proposal for extending the notation for function calls to account for the possibility that an argument call is 'supplying' multiple arguments to the call.
E.g., imagine an allocator that returns two values: the base address of an allocated string (say), and the size of the string. And suppose that such an allocated value needs to be passed to a function that needs those two values as different arguments.
The signatures of utf8-count and munge-text might be:
On Tue, Jul 16, 2019 at 12:51 PM Andreas Rossberg ***@***.***> wrote:
The following works perfectly fine in the text format:
(func $f (result i32 i32) ...)
(func $g (param i32 i32) ...)
(func $h
(call $g (call $f))
)
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
<#18?email_source=notifications&email_token=AAQAXUGZ7ZXW33KGFNGAHY3P7YRFHA5CNFSM4IEGT6M2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD2B6NOI#issuecomment-511960761>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAQAXUH4MXJJMRCYGZ3S3ITP7YRFHANCNFSM4IEGT6MQ>
.
In my mind I was modeling multi-value results as tuples and therefore they would need to be opted into being splatted into separate values, such as here. If they automatically splat, how do you store the multi-value result in a single variable? Or is that not a thing?
A follow up that may explain why it's confusing:
in most languages, including LISP, a term of the form:
(t1 ... tn)
is interpreted as the sequence of elements ti: t1:t2: ... :nil
The 'other' way is effectively a multi-cat: t1+t2+ ... +tn
On Tue, Jul 16, 2019 at 12:55 PM Francis McCabe ***@***.***> wrote:
That is a matter of opinion. Personally, I find it confusing.
On Tue, Jul 16, 2019 at 12:51 PM Andreas Rossberg <
***@***.***> wrote:
> The following works perfectly fine in the text format:
>
> (func $f (result i32 i32) ...)
> (func $g (param i32 i32) ...)
> (func $h
> (call $g (call $f))
> )
>
> —
> You are receiving this because you authored the thread.
> Reply to this email directly, view it on GitHub
> <#18?email_source=notifications&email_token=AAQAXUGZ7ZXW33KGFNGAHY3P7YRFHA5CNFSM4IEGT6M2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD2B6NOI#issuecomment-511960761>,
> or mute the thread
> <https://github.com/notifications/unsubscribe-auth/AAQAXUH4MXJJMRCYGZ3S3ITP7YRFHANCNFSM4IEGT6MQ>
> .
>
--
Francis McCabe
SWE
@KronicDeth, multivalue results cannot be stored in a single value. Think of it as multiple individual values being separately pushed onto the stack one after another. So they're not really tuples.
@KronicDeth, tuples do not exist as first-class values in Wasm. It is a stack machine.
OTOH, the folded form of the text format seems perfectly in line with how languages with first-class tuples would write the analogous.
SML:
fun f() = (1, 2)
fun g(x, y) = x + y
val h = g(f())
Haskell:
f () = (1, 2)
g (x, y) = x + y
h = g (f ())
Ocaml:
let f () = (1, 2)
let g (x, y) = x + y
let h = g (f ())
etc.
The need for splatting in some languages is merely a consequence of an artificial distinction between tuples and multiple arguments. In languages like the above, all functions have exactly one argument, which may be a tuple (though some prefer currying). So for example,
fun g(x, y) = x + y
val p = (1, 2)
val h = g(p)
works perfectly fine in good old SML, similarly in others.
@fgmccabe, not sure what you mean, inconsistent arities are simply a type error in any of these languages.
Edit: Or are you talking about Wasm? There, the folded form does not imply any check that the nesting aligns with types. But that's already the case, with or without multi values.
More generally, this is a case of "types driving syntax" (in order to understand the syntax I need to know the types). Ergonomically, it is better to have it the other way around -- e.g., syntax directed type rules.
So, if you are right, the original question stands. How do you invoke a function of three arguments where two of the arguments are the tuple result of another function call?
Incidentally, the 'pair' interpretation of tuples is exactly how Prolog handles tuples (which is why I am so strong in my ergonomic opinion).
Furthermore, the type of a tuple in ML takes the cross product notation integer * integer * string; so your assertion leaves me even more confused.
The type T * U * V is not the same as T * (U * V). The * is not a binary type constructor but a (family of) mixfix type constructors. (In fact, it SML it is even just shorthand for the record type {1 : T, 2 : U, 3 : V})
where string-to-linear returns a pair of numbers: the address of the string and its length.
in the binding expression.
It is quite important to be able to strongly limit the allowed forms in a binding expression; it is also fairly important that the syntax is not overly complex.
Since we are also constrained to use wasm's s-expression notation, I am looking for a way to write an extended form of s-expression to handle this common case. This is not an issue for the binary form; there we can do what we please.
Those all seem reasonable to me. Not sure in what sense syntax "follows" types here, there's simply no relation between syntactic arity and number of values. An expression can produce and consume any number of values (including 0). But that's just natural in a stack machine.
(local-fun (string-to-linear S1) end (string-to-linear S2))
(local-fun (first (string-to-linear S1)) 23 end (string-to-linear S2))
you will see the type of the local-fun is actually identical even though the number of arguments supplied is different. So, in order to know that the syntax is valid, (i.e., not an arity error), you have to know the types of the sub-expressions.
Which is why it is potentially confusing.
(Note: 'first' returns the first of two arguments)
The need for splatting in some languages is merely a consequence of an artificial distinction between tuples and multiple arguments
Some of those languages need such a distinction, though, because they have both tuples and multiple arguments. For example python does, and so it has * to make it clear when a tuple is being turned into separate arguments. Are we sure wasm will never have tuples?
I understand the reasoning why pair != tuple when multi-value results are defined in terms of the stack machine @rossberg, so thanks for that clarification.
I think the main issue we’re having is that the s-expression syntax hides the stackiness of the architecture and that it means that the function call lexical order doesn’t match evaluation order and a lot of us are too used to languages that are single return or only multi-return through tuples.
Reminding ourselves of the mismatch between s-expressions and the stack is just something we need to do when reading the s-expression form unless we want to have splatting as an human-only syntax to remind us about the equivalent of splatting that happen on the stack.
The need for splatting in some languages is merely a consequence of an artificial distinction between tuples and multiple arguments
Some of those languages need such a distinction, though, because they have both tuples and multiple arguments.
Um, didn't you just give a circular reason? They need it because they have it? ;)
Of course, there are good reasons to make the distinction, esp. in lower-level languages. But notably, they all have to do with implementation concerns, not with semantic necessity.
Are we sure wasm will never have tuples?
Well, a stack machine is strictly more expressive than second-class tuples, i.e., already subsumes them. As for first-class tuples, the GC proposal introduces structs, which are essentially just that. In-between the two, what's left?
So, in order to know that the syntax is valid, (i.e., not an arity error), you have to know the types of the sub-expressions.
"Valid" here means well-typed, so all you're observing is that in order for this to be well-typed, you have to know the types -- but that's always true. What's actually going on is that syntactic arity does not correlate with semantic arity -- neither implies the other. Whether one finds that confusing is, as you said, subjective, or perhaps just a matter of getting used to.
I apologize if my wording was unclear, but basically I was saying: Python has tuples and multiple arguments, and I believe it could not manage without splat.
Well, a stack machine is strictly more expressive than second-class tuples, i.e., already subsumes them. As for first-class tuples, the GC proposal introduces structs, which are essentially just that. In-between the two, what's left?
Will a GC struct be splat-able into multiple arguments?
Python has tuples and multiple arguments, and I believe it could not manage without splat.
No disagreement. When designed around that choice, a language needs extra constructs to convert between the two.
Will a GC struct be splat-able into multiple arguments?
There is no specific support for that, the code will just have to project the fields manually. Is there reason to believe that a built-in primitive would be more efficient?
I think we should avoid introducing any notion of tuples, splatting, destructuring, etc to the text format of Wasm. Such concepts won't ever map to the binary format, where everything is flat. As Luke mentioned, we just have to remind ourselves that the S-expression syntax is just shorthand for the stack machine ((func x y) is shorthand x y call[func]). Also I'm not sure why multi-return values is any more confusing than functions that return no result, as they can be syntactically nested but do not produce values.
Thanks @rossberg , now I see what you mean. Makes sense.
Yeah, as @titzer just said, really the issue is that the s-expression format is just shorthand for the stack machine. I think that has confusing aspects - as s-expressions it doesn't look like a stack machine. I still wish we had a better text format :) but it works.
Activity
KronicDeth commentedon Jul 16, 2019
Alternatives from other languages:
*(prefix) for splat..(postfix) for expandrossberg commentedon Jul 16, 2019
The following works perfectly fine in the text format:
fgmccabe commentedon Jul 16, 2019
KronicDeth commentedon Jul 16, 2019
In my mind I was modeling multi-value results as tuples and therefore they would need to be opted into being splatted into separate values, such as here. If they automatically splat, how do you store the multi-value result in a single variable? Or is that not a thing?
fgmccabe commentedon Jul 16, 2019
tlively commentedon Jul 16, 2019
@KronicDeth, multivalue results cannot be stored in a single value. Think of it as multiple individual values being separately pushed onto the stack one after another. So they're not really tuples.
rossberg commentedon Jul 16, 2019
@KronicDeth, tuples do not exist as first-class values in Wasm. It is a stack machine.
OTOH, the folded form of the text format seems perfectly in line with how languages with first-class tuples would write the analogous.
SML:
Haskell:
Ocaml:
etc.
The need for splatting in some languages is merely a consequence of an artificial distinction between tuples and multiple arguments. In languages like the above, all functions have exactly one argument, which may be a tuple (though some prefer currying). So for example,
works perfectly fine in good old SML, similarly in others.
fgmccabe commentedon Jul 16, 2019
This form of tuple syntax leads to real world confusion. In particular, it conflates
f(x,y)
with
f(u,v,w)
by binding x->u but y->(v,w)
rossberg commentedon Jul 16, 2019
@fgmccabe, not sure what you mean, inconsistent arities are simply a type error in any of these languages.
Edit: Or are you talking about Wasm? There, the folded form does not imply any check that the nesting aligns with types. But that's already the case, with or without multi values.
fgmccabe commentedon Jul 16, 2019
It is not actually an 'arity error'. If I define
f (x,y) = ...
I can call it with
f (u,v,w)
with the above binding.
This is because in ML et al, tuples are pairs.
But if you have genuine tuples, then (x,y) != (u,v,w) for any x,y,u,v,w
rossberg commentedon Jul 16, 2019
@fgmccabe, no you cannot, n-ary tuples are not pairs in either ML or Haskell. Nor in any other language except Lisp/Scheme, as far as I am aware.
fgmccabe commentedon Jul 16, 2019
More generally, this is a case of "types driving syntax" (in order to understand the syntax I need to know the types). Ergonomically, it is better to have it the other way around -- e.g., syntax directed type rules.
fgmccabe commentedon Jul 16, 2019
So, if you are right, the original question stands. How do you invoke a function of three arguments where two of the arguments are the tuple result of another function call?
Incidentally, the 'pair' interpretation of tuples is exactly how Prolog handles tuples (which is why I am so strong in my ergonomic opinion).
Furthermore, the type of a tuple in ML takes the cross product notation integer * integer * string; so your assertion leaves me even more confused.
rossberg commentedon Jul 16, 2019
If you cannot pass through the tuple then you need to deconstruct it, typically with a pattern-matching let:
Of course, you can also write a function:
rossberg commentedon Jul 16, 2019
The type
T * U * Vis not the same asT * (U * V). The*is not a binary type constructor but a (family of) mixfix type constructors. (In fact, it SML it is even just shorthand for the record type{1 : T, 2 : U, 3 : V})4 remaining items
fgmccabe commentedon Jul 16, 2019
For bindings, I would like to be able to write
(local-fun (string-to-linear S))
where string-to-linear returns a pair of numbers: the address of the string and its length.
in the binding expression.
It is quite important to be able to strongly limit the allowed forms in a binding expression; it is also fairly important that the syntax is not overly complex.
Since we are also constrained to use wasm's s-expression notation, I am looking for a way to write an extended form of s-expression to handle this common case. This is not an issue for the binary form; there we can do what we please.
rossberg commentedon Jul 16, 2019
I guess I don't see what's wrong with the very syntax you just gave?
fgmccabe commentedon Jul 16, 2019
because it's confusing. because of cases like
(local-fun (string-to-linear S1) offset)
(local-fun start (string-to-linear S1) end (string-to-linear S2))
(local-fun (first (string-to-linear S1)) 23 end (string-to-linear S2))
Understood that 'confusing' is a matter of opinion. But, my earlier comment stands: types should follow syntax, not the other way around.
rossberg commentedon Jul 16, 2019
Those all seem reasonable to me. Not sure in what sense syntax "follows" types here, there's simply no relation between syntactic arity and number of values. An expression can produce and consume any number of values (including 0). But that's just natural in a stack machine.
fgmccabe commentedon Jul 16, 2019
If you look carefully at
(local-fun (string-to-linear S1) end (string-to-linear S2))
(local-fun (first (string-to-linear S1)) 23 end (string-to-linear S2))
you will see the type of the local-fun is actually identical even though the number of arguments supplied is different. So, in order to know that the syntax is valid, (i.e., not an arity error), you have to know the types of the sub-expressions.
Which is why it is potentially confusing.
(Note: 'first' returns the first of two arguments)
kripken commentedon Jul 16, 2019
@rossberg
Some of those languages need such a distinction, though, because they have both tuples and multiple arguments. For example python does, and so it has
*to make it clear when a tuple is being turned into separate arguments. Are we sure wasm will never have tuples?KronicDeth commentedon Jul 16, 2019
I understand the reasoning why pair != tuple when multi-value results are defined in terms of the stack machine @rossberg, so thanks for that clarification.
I think the main issue we’re having is that the s-expression syntax hides the stackiness of the architecture and that it means that the function call lexical order doesn’t match evaluation order and a lot of us are too used to languages that are single return or only multi-return through tuples.
Reminding ourselves of the mismatch between s-expressions and the stack is just something we need to do when reading the s-expression form unless we want to have splatting as an human-only syntax to remind us about the equivalent of splatting that happen on the stack.
rossberg commentedon Jul 17, 2019
@kripken:
Um, didn't you just give a circular reason? They need it because they have it? ;)
Of course, there are good reasons to make the distinction, esp. in lower-level languages. But notably, they all have to do with implementation concerns, not with semantic necessity.
Well, a stack machine is strictly more expressive than second-class tuples, i.e., already subsumes them. As for first-class tuples, the GC proposal introduces structs, which are essentially just that. In-between the two, what's left?
rossberg commentedon Jul 17, 2019
@fgmccabe:
"Valid" here means well-typed, so all you're observing is that in order for this to be well-typed, you have to know the types -- but that's always true. What's actually going on is that syntactic arity does not correlate with semantic arity -- neither implies the other. Whether one finds that confusing is, as you said, subjective, or perhaps just a matter of getting used to.
kripken commentedon Jul 17, 2019
I apologize if my wording was unclear, but basically I was saying: Python has tuples and multiple arguments, and I believe it could not manage without splat.
Will a GC struct be splat-able into multiple arguments?
rossberg commentedon Jul 17, 2019
No disagreement. When designed around that choice, a language needs extra constructs to convert between the two.
There is no specific support for that, the code will just have to project the fields manually. Is there reason to believe that a built-in primitive would be more efficient?
titzer commentedon Jul 17, 2019
I think we should avoid introducing any notion of tuples, splatting, destructuring, etc to the text format of Wasm. Such concepts won't ever map to the binary format, where everything is flat. As Luke mentioned, we just have to remind ourselves that the S-expression syntax is just shorthand for the stack machine (
(func x y)is shorthandx y call[func]). Also I'm not sure why multi-return values is any more confusing than functions that return no result, as they can be syntactically nested but do not produce values.kripken commentedon Jul 17, 2019
Thanks @rossberg , now I see what you mean. Makes sense.
Yeah, as @titzer just said, really the issue is that the s-expression format is just shorthand for the stack machine. I think that has confusing aspects - as s-expressions it doesn't look like a stack machine. I still wish we had a better text format :) but it works.
binji commentedon Jul 17, 2019
Since it's purely syntactic sugar, it can manifest in particularly odd ways too, like
or with multi value:
Both of these are valid, but pretty bizarre as written.
fgmccabe commentedon Jul 17, 2019
Fun fact: the specification references S-Expressions in its section on Text format:
https://webassembly.github.io/spec/core/bikeshed/index.html#text-format%E2%91%A0
The reference it quotes (wikipedia) does NOT conflate multiple arguments and it has a . operator.