Skip to content
This repository was archived by the owner on Aug 17, 2022. It is now read-only.

Conversation

@fgmccabe
Copy link
Contributor

I have refactored the scenarios into a series of smaller files to make them more digestible.
Also, the directory.md example is kind of interesting...

Copy link
Member

@lukewagner lukewagner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall this looks great. I think the point of the working-notes and scenarios dirs is that they don't have to contain only things we've agreed on every last detail of, so I think it's good to merge with the following requested changes and we should strive to keep these docs updated as we converge on all the details. Thanks!

credit card:

```wat
(@interface datatype @cc
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For symmetry with how we have (func $f)/(@interface func $f), how about (type $T)/(@interface type $T) (instead of datatype @T)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Am open to adjustment. However, when defining types there are two basic scenarios: defining a new algebraic type and type aliasing. These need to be distinguished. (Examples of 'type aliasing' include type imports)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fgmccabe, the only reason to distinguish them would be if you wanted nominal typing for algebraic types. But there is little reason to want that in Wasm interfaces. In fact, there is good reason not to want that because it makes everything more complicated and aversely affects modularity.

Copy link
Contributor Author

@fgmccabe fgmccabe Oct 15, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On second thoughts, I think we need to keep the @ character.
If you have a param specification for an interface function; you need to disambiguate:

(param $cc (resource $connection))

could mean a param block with two types that happen to be by reference or a single parameter called cc.

On the other hand

(param @cc (resource @connection))

is unambiguous (a param block of two types)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, interesting question. If we look at the GC proposal, which similarly introduces type definitions and uses, we can see that, in most cases, a type use occurs as part of a reference type, so you can unambiguously have (ref $T) for some typedef $T. However, there is one exception: when the field of a struct or element of an array is stored inline, not by reference; for this the explainer uses (type $T) which is presumably chosen to be symmetric with the MVP text format, where you can write:

(module
  (type $F (func (param i32) (result i32)))
  (func $f (type $F) ...)
)

as the expanded form of the syntactic sugar:

(module
  (func $f (param i32) (result i32) ...)
)

Thus, I think your final example would be most-symmetrically written:

(param (type $cc) (resource $cc))


```wat
(@interface datatype @cc
(record "cc"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should discuss whether it's useful to add string names to type definitions. I think it's necessary to name fields (since it allows specifying field type compatibility in an order-independent manner, which is useful), but I don't think it buys us anything to name structs, given that we'll already need to check their contents for type-compatibility anyway.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you have alternate cases in an algebraic type then you need to distinguish them. This is going to be an issue especially for C ... to be handled later.
It boils down to a couple of issues: there are certain specific cases which are very common and which are technically instances of algebraic type definitions - do we wish to honor that heritage; and there are also legitimate API scenarios where there is variation in the data being passed.
The first is essentially the option type and also the either type (used to distinguish normal from error cases).
The second includes supporting the kind of polymorphism that is quite common in the wild, and in JS APIs.
Having different cases allows us to capture the variants that show up in APIs without inventing new names where there weren't before: e.g.,

addEventListener('type', function, capture?);
addEventListener('type', function, options?);
addEventListener('type', Object handler, capture?);
addEventListener('type', Object handler, options?);

Copy link
Member

@lukewagner lukewagner Oct 15, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting point. With the current interface types proposal, one never does an "overload resolution" wherein a (name, signature) pair is looked up in some scope, resolving to a function. Rather, the order is inverted: a function is supplied to wasm instantiation and then we ask if that function's type is compatible with the import's declared type. For Web IDL, all declared overloads end up producing a single function that wasm can import and thus the type-compatibility check is against this list of valid signatures. For future APIs (like WASI), I think we'd want to not allow overloading and instead use variant types in the signature (which, of course, have string field names).

(@interface func (export "countCodes")
(param $str string) (result u32)
local.get $str
string-to-memory $memx "malloc"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I think $memx should be "memx", since it's a core export.

(param $str string) (result u32)
local.get $str
string-to-memory $memx "malloc"
call "countCodes_"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: call-export

local.get $ptr
local.get $len
string.copy Mi:"memi" Mx:"memx" Mx:"malloc"
call Mx:"countCodes_"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as above about using memory.copy and local module names instead of Mx:"export".

@fgmccabe
Copy link
Contributor Author

fgmccabe commented Oct 15, 2019 via email

@rossberg
Copy link
Member

@fgmccabe:

The option type is a special case of algebraic type; as is the either type. So, do we wish to honor that or do we make a bunch of special cases? The problem with special cases is that there are so many of them ... including those not yet invented.
In addition, there are enough examples of APIs that are quite polymorphic in nature, and having separate imports for them is a hack necessary for C programming but should not necessarily be reflected in the 'interface'. Some examples: settimeout (string or callback function argument), define_property, canvas get_context, bufferData, etc.

Not sure if this is a reply to my comment? I definitely agree that you want variant types. I was merely saying that there is no reason to make them nominal, and thereofore, no reason to distinguish type from datatype definitions. You can interpret definitions of algebraic data types as aliases for structural variant/sum types just fine, and it's actually much easier to do so in the context of interfaces.

In addition, I do not understand your comment about modularity.

Fair enough. I could go into a long explanation about how nominal types don't work at interface boundaries, esp with the strong kind of modularity provided by Wasm modules (which has no notion of referring to types from other modules). But that may be a bit off-topic.

@fgmccabe
Copy link
Contributor Author

fgmccabe commented Oct 16, 2019 via email

@rossberg
Copy link
Member

@fgmccabe:

The case for nominal types in the interface layer has nothing to do with
wasm core types.

Neither has my argument. ;) They simply won't work in interfaces for a Wasm-like module system. How would you use a nominal datatype from another module?

Put simply, for APIs it is important that a person is never misunderstood
as a chair or a port.

Confusing two structurally equivalent algebraic types is no different from confusing an int denoting a length with an int denoting an index. And that latter confusion is waaaay more likely to happen in practice. Yet, APIs don't tend to define nominal integer types.

@fgmccabe
Copy link
Contributor Author

fgmccabe commented Oct 16, 2019 via email

@rossberg
Copy link
Member

@fgmccabe:

if you have variant types you need to be able to signal which variant you actually have.

Sure, but that is independent from making the type nominal (i.e., not an alias). Variant tags can be treated just as structurally as record labels (in fact, they are the exact dual). They can both be identified purely by syntactic name -- or in Wasm, rather by index. Knowing the structure of the assumed type is enough to interpret the label/index. Really no different from integers ()which are really just a shorthand for peano numbers, so datatypes :)).

@lukewagner lukewagner deleted the branch WebAssembly:master September 21, 2021 19:19
@lukewagner lukewagner closed this Sep 21, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants