component-model icon indicating copy to clipboard operation
component-model copied to clipboard

Inconsistent wit syntax

Open omersadika opened this issue 2 years ago • 4 comments

I would like to point at some inconsistency I see in wit syntax, It is not clear to me if there is a good reason or I miss something.

When we define a type, interface, world, resource or importing using use we first write the keyword and then the name, for example:

record pair {
    x: u32,
    y: u32,
}
resource blob {
    constructor(init: list<u8>);
    write: func(bytes: list<u8>);
    read: func(n: u32) -> list<u8>;
    merge: static func(lhs: borrow<blob>, rhs: borrow<blob>) -> blob;
}
interface types {
  enum level {
    info,
    debug,
  }
}
interface console {
  log: func(arg: string);
}
world the-world {
  export test: func();
  export run: func();
}
world my-world {
  import host: interface {
    log: func(param: string);
  }

  export run: func();
}

But when it comes to func or inline interface inside world the syntax changes to the name, a colon and then the keyword. I'm not sure why not to keep the same syntax all the time, so for example it will look like this:

resource blob {
    constructor(init: list<u8>);
    func write(bytes: list<u8>);
    func read(n: u32) -> list<u8>;
    static func merge:(lhs: borrow<blob>, rhs: borrow<blob>) -> blob;
}
interface types {
  enum level {
    info,
    debug,
  }
}
interface console {
 func log(arg: string);
}
world the-world {
  export func test();
  export func run();
}
world my-world {
  import interface host {
    func log(param: string);
  }

  export func run();
}

omersadika avatar Sep 26 '23 18:09 omersadika

That's a good question, because it is subtle. I think the justification for the difference is that

interface foo {
  ...
}

defines a new interface named foo that can be used by other interfaces and worlds as a named type whereas:

world w {
  import foo: interface {
    ...
  }
}

is saying that the world w (and only that world) imports a collection of values of an unnamed type where the collection of values are given the name foo. Thus, the former defines a name for a type whereas the latter is more like a parameter name (analogous to the p in func(p: string)).

lukewagner avatar Sep 27 '23 00:09 lukewagner

Thanks for the answer, makes sense. I would like to try to take it to the other way than, based on the same logic, I would rather expect that syntax inside an interface for types or resources:

interface types {
  level: enum {
    info,
    debug,
  };
  pair: record {
      x: u32,
      y: u32,
  };
  blob: resource {
      constructor(init: list<u8>);
      write: func(bytes: list<u8>);
      read: func(n: u32) -> list<u8>;
      merge: static func(lhs: borrow<blob>, rhs: borrow<blob>) -> blob;
  };
  log: func(arg: string);
}

omersadika avatar Sep 27 '23 09:09 omersadika

The subtlety in this direction is that

interface foo {
  level: enum { info, debug }
}

looks like level is a value of an unnamed enum type (e.g., like you can do in C), just like how in:

interface foo {
  bar: func() -> string
}

bar is a (closure) value whose type is an unnamed function type.

Although there are currently bindings-related reasons not to allow unnamed enums, records, variants and most other compound types, in theory we could relax these restrictions in some cases for some types at some point in the future, so it's useful to preemptively put these in separate syntactic categories.

lukewagner avatar Sep 27 '23 17:09 lukewagner

Drive-by-comment with my 2 cent rant: the underlying problem of so many syntax confusions is that all those languages in the C syntax tradition still decide to omit the = for type definitions. The contrast between the relations = (type equals type) and : (expression has type) would make things much clearer. It's unfortunate that we keep replicating this bad design, even though at least the : bit is more widely adopted these days.

rossberg avatar Sep 27 '23 18:09 rossberg