tj / luna

luna programming language - a small, elegant VM implemented in C
2.45k stars 147 forks source link

Dot syntax sugar and modules. #84

Open glhrmfrts opened 8 years ago

glhrmfrts commented 8 years ago

27 also talks about this but I think this issue is more specific. Take this as example:

use "mylibrary/users"

# defined in "mylibrary/users"
let user = get({
  "name": "Foo Bar",
})

# also defined in "mylibrary/users"
let foo = User{name: "Luna"}
foo.bar()

Even though this syntax sugar is great for methods defined for a given type, I think it becomes really weird when it comes to functions that are not "meant" to be methods. And the (lack of) qualification of the exported symbols confuses me about the types defined in those modules (what if two different modules had a type with the same name?). This adds an extra layer of complexity for the user to deal with and drastically reduces the readability of the code for other users.

If we add the possibility to optionally "qualify" the module exports like this:

use "foo" as foo

Then it becomes really unclear how the methods for the types defined in that module would be called.

use "mylibrary/users" as users

let foo = users.User{name: "Anonymous"}
foo.method() 
# the call above would parse as "method(foo)" but "method" is unresolved because it's inside the
# "users" module

Also, if we could specify what would be exported (like in ES6), but all the rest would be auto-exported as well (see #27), then what's the point?

use "mylibrary/users" (get, User)

This would work if we only export what's specified, but if one uses too much symbols from that module it adds a lot of polution to the code (imagine a file with 10 or more imports and each one with a lot of symbols).

Possible solution

First, we would need to get rid of the foo.bar() syntax sugar (multiple-dispatch still remains). Instead of parsing it as bar(foo) it would remain as it is, and then we lookup method bar on the type of foo. And how methods would be defined? I've come up with a couple of syntaxes.

type Foo
  name: string
  x, y: float

  def bar(name: string)
    self.name = name
  end
end

# or
def Foo.bar(f:, name: string)
  # 'f' would be automatically of type "Foo"
  f.name = name
end

# or even
def Foo.bar(f | name: string)
  # 'f' would be automatically of type "Foo"
  f.name = name
end

The first one doesn't really looks like something you would find in a Luna file, so I think the second or the last one is a better fit.

As for modules, the last part of the module name would be what Luna would use to qualify it's exported symbols, for example:

use "mylibrary/users"

let user = users.get({
  "name": "Foo",
})

let foo = users.User{name: "foo"}
foo.bar()

I think this fits better with luna's goals (with emphasis on the 'explicit' part) and as a consequence, improves readability of the code. Even if this doesn't get implemented the discussion might lead to something which can benefit from both models.

PS: this would bring pipes back to replace the current syntax sugar (#35)