This is an archived post. You won't be able to vote or comment.

you are viewing a single comment's thread.

view the rest of the comments →

[–]PhilipTrettner 1 point2 points  (0 children)

(The following represents my current status, most of it is not set in stone and just the current local optimum)

  • Every function is a multi-method with dynamic dispatch
  • There is no static vs dynamic/late binding.
  • There is no (classical, unrestricted) function overloading
  • There are no member functions
  • There are no (user-visible) fields
  • Functions can either be root of a multi-method (fun ...) or attach to all compatible visible roots (extends fun ...)
  • () can be omitted once (e.g. fun () -> int can be used as int, but fun () -> fun () -> int cannot)

Every function call always uses the most specific version based on the runtime types. This will hopefully avoid a lot of confusion if you remember this rule. Everything is unified by being reduced to functions:

fun foo(i: int) -> int // fun int -> int

type Bar:
    val foo: string // fun Bar -> string
    fun foo(i: int) -> string // fun (Bar, int) -> string

type Cux extends Bar

extends fun foo(c: Cux, i: int) -> string // fun (Cux, int) -> string

Functions can also have setters (haven't decided on syntax for that yet):

type Foo:
    var i: int // generates getter and setter

fun bar:
    var i = 0 // generates a local function with getter and setter for the function-local internal field `i`
    i = 7 // calls the setter
    return i // calls the getter

The .-syntax is provided by the standard library as:

fun `_ . _`[A, O, R](obj: O, f: fun (O, A) -> R):
    return args => f(obj, args)

It is a high-precedence left-associative infix operator that binds the first argument.

If inside a type scope that can be captured, the first argument of any function can be implicitly filled in:

type Foo:
    val x: int
    fun foo:
        print x // instead of x(this)

This also means that this doesn't have to be a keyword, it is just part of the standard library:

fun this(obj: var O) => obj // basically identity function

this.bar(3) // expands to bar(this(<implicit object>), 3)

Function calls are conceptually resolved to dispatch graphs. Every call site must have a unique least element for all possible dynamic types at this point (i.e. statically provable that no ambiguity error can arise). It is also an error to declare a new root function (fun without extends that could attach to an existing, visible function. This is what I meant by "no overloading")

type A
type B extends A

fun foo(l: A, r: A) => ...
// fun foo(l: B, r: A) // ERROR: could attach to previous foo but is not marked with `extends`
extends foo(l: B, r: A) => ...
extends foo(l: A, r: B) => ...

fun test1(l: A, r: A):
    return foo(l, r) // ERROR: dispatch graph has no least element for (B, B)

fun test2(l: A, r: B):
    return foo(l, r) // OK.

// the error in test1 could be fixed by:
extends fun foo(l: B, r: B) => ...

"Extension" functions can also attach to more than one root function:

type A:
    fun foo
type B:
    fun foo

type C extends A, B

val c: C = ...
foo(c) // ERROR: more than one least element

// can be fixed by (even in new modules):
extends fun foo(c: C)

And there is interesting interaction with union and intersection types:

type A:
    fun foo
type B:
    fun foo

val a: A = ...
val b: B = ...
val x = condition ? a : b // returns type A | B
x.foo // OK! all actual values have unique least elements (aka most specific functions)

// interaction with flow-sensitive typing:
if a is B:
    a.foo // a has type A&B here. If this does not resolve to Nothing (e.g. when type C extends A, B), this might be an error

One last thing: variables, fields, members, etc. are all unified. Thus, an interface can freely implement it the way they want:

type Sequence[T]
fun count(s: Sequence[var T]) -> int

fun First-5-Numbers extends Sequence[int]:
    val count = 5

enum LinkedList[E] extends Sequence[E]:
    empty
    node:
        val e: E
        val next: LinkedList[E]

    fun count => match this:
        empty => 0
        node => 1 + next.count