Method syntax for Roto types

We cannot currently create methods for types defined in Roto.

This poses some questions:

  • Where and how do we write these methods?
    • Rust: In impl blocks.
    • Java/Python/C++ in the type declaration.
    • Go: as separate functions.
  • How do we access self/this? And how do we distinguish a static method from an instance method?
    • Rust: explicit self keyword
    • Python: explicit but only conventional self
    • Go: receiver argument (note: I dislike Go’s syntax because it makes searching functions harder)
    • Java/JS: implicit this that can be elided.

I’m inclined to go with a mix of Rust and Go (never thought I’d say these words), where there’s no impl block, but there is an explicit self.

record Foo { ... }

# Static method
fn Foo.new() -> Foo { ... }

# Instance method
fn Foo.get_bar(self) -> Bar { ... }

There’s another option, which is to adopt Universal Function Call Syntax, which doesn’t seem too bad for a scripting language.

It would look like this:

record Foo { ... }

# Static method could be done like this:
fn Foo.new() -> Foo { ... }

# or
mod Foo {
    fn new() -> Foo { ... }
}

fn get_bar(foo: Foo) -> Bar { ... }

One downside is that static methods are not supported in UFCS, so we have to bolt that on somehow. Another, probably more problematic downside is that it requires function overloading.

There are also plenty of languages that don’t feature methods and instead have some pipe operator, like Gleam:

fn spawn_task(i) {
  task.async(fn() {
    let n = int.to_string(i)
    io.println("Hello from " <> n)
  })
}

pub fn main() {
  // Run loads of threads, no problem
  list.range(0, 200_000)
  |> list.map(spawn_task)
  |> list.each(task.await_forever)
}

It’s a bit verbose, but that’s not too bad. I’m more worried about familiarity.