wren-lang / wren

The Wren Programming Language. Wren is a small, fast, class-based concurrent scripting language.
http://wren.io
MIT License
6.9k stars 552 forks source link

Would it be possible to drop the 'new' when instantiating? #209

Closed bjorn closed 9 years ago

bjorn commented 9 years ago

Coming from a C++ and Lua background, I rarely use new when creating new instances and I love the way it makes the code shorter and in my opinion nicer to read.

Table of points in Lua (with Point being either a constructor function or table with __call metamethod):

local points = { Point(1, 2), Point(2, 4), ... }

Array of points in C++:

Point points[] = { Point(1, 2), Point(2, 4), ... };

List of points in Wren:

var points = [ new Point(1, 2), new Point(2, 4), ... ]

This is just an example. My main use-case for scripting currently is UI and in my opinion instantiating large hierarchies of UI elements is really more comfortable without the new everywhere.

So my question is, could it be made optional or removed entirely? And if not, why not?

bjorn commented 9 years ago

Hmm, I guess this has to do with the leaving out of the parenthesis when there are no parameters, which would lead to problems like:

var foo = Bar.foo    // does this call a static method or non-static method?

Without the parenthesis left out it would be clear, since only var foo = Bar().foo would mean calling the instance method... well and of course by relying on new it is clear.

bjorn commented 9 years ago

Hmm, conceived a potential workaround (though I guess this won't work):

class Widget {
  new(children) {
    _children = children
  }

  static Label(text) { new Label(text) }
  static Image(source) { new Image(source) }
  static Widget(children) { new Widget(children) }
}

class MyWidget is Widget {
  new {
    super([
      Label("foo"),
      Image("bar.png"),
      Widget([
        Label("fornicate"),
        Label("reverberate"),
      ])
    ])
  }
}

Sorry about rambling a bit here. I'm just considering how to do what I'm doing in Lua in Wren. Lua has been really flexible so that I could set it up exactly like I wanted to, but on the other hand it's all based on magic that I'd rather replace with something more structured like Wren.

munificent commented 9 years ago

This touches on probably the biggest syntactic bump in Wren: it doesn't really have bare functions where you just have a name that is resolved lexically to a function which you can then invoke. I need to document this more prominently, but the rationale is here.

In most cases, not having this works out fine and leads to a simpler, easier to reason about language. It also allows method sends to this without having to do it explicitly, unlike Python and JS, which is a big readability win, I think.

But it's a drag in the places where you do want to call something "function"-like where there's no natural receiver. Constructors are one example.

(With constructors in particular, there is also a reason Wren has an explicit new keyword that has to do with the details of how the construction process is implemented. Making construction syntactically explicit simplifies compiling the right bytecode for a constructor sequence. This might be fixable, but that's why it's there now.)

What curious to see is if people can come up with idioms in Wren-as-it-is that work around this, or we just sort of get used to it once we think in Wren. For example, your workaround would work if you made the builder methods instance instead of static:

class Widget {
  new(children) {
    _children = children
  }

  label(text) { new Label(text) }
  image(source) { new Image(source) }
  widget(children) { new Widget(children) }
}

class MyWidget is Widget {
  new {
    super([
      label("foo"),
      image("bar.png"),
      widget([
        label("fornicate"),
        label("reverberate"),
      ])
    ])
  }
}

Is this an idiom we want to encourage? Not sure, but it's worth exploring. :)

bjorn commented 9 years ago

My workaround was an attempt to get at a terse syntax for constructing object hierarchies declaratively, like in Qt Quick. In Lua I've managed to get quite close by using the __call metamethod:

local imagesWidget = core.Widget {
    scale = 0.5,
    core.Image { texture = TexDb.some_image, x = 10, y = 0 },
    core.Image { texture = TexDb.another_image, x = 0, y = 50 },
}

This relies on the fact that the instance needs a table anyway, so that table is created and then passed as first argument to the __call metamethod of core.Widget, which is the constructor and sets the metatable on the instance. Children of the object can be specified inline along with the properties thanks to Lua tables being both hashes and lists.

So, since this is my main use-case and Wren looks very interesting I was considering whether I could achieve a similar syntax, but actually the "workaround" I had so far does not make me happy at all. Mainly because of the additional syntax required like ([ ... ]) and because there's no way to pass in named properties to the constructor.

Nothing tragic though. Maybe declarative syntax and Wren don't go together well. And maybe I should anyway consider separating UI 'data' and 'code' better and use for example JSON to describe the hierarchy instead.

Thanks a lot for your thorough explanation! I'll close this issue since I don't consider it a shortcoming of Wren.

munificent commented 9 years ago

Mainly because of the additional syntax required like ([ ... ]) and because there's no way to pass in named properties to the constructor.

Nothing tragic though. Maybe declarative syntax and Wren don't go together well.

Right. While Wren is dynamically typed, it's still pretty statically structured. Where languages like Lua are data languages with a bit of behavior bolted on, Wren is much more "you're going to make some real classes and methods". It's not just poking stuff into property bags.

This is good in lots of ways: you can catch bugs earlier, performance is better, etc. But in cases where you're trying to do DSL-like stuff, or just build a blob of data, it's a bit harder.

There are ways to do it, I think. For example, you could describe your UI as a raw map and then have some code that "objectifies" it as a second step:

var imagesWidget = new Widget({
  "scale": 0.5,
}, [
  { "type": "image", "texture": TexDb.some_image, "x": 10, "y": 0 },
  { "type": "image", "texture": TexDb.another_image, "x": 10, "y": 50 },
])

Whether this is a good idiom or not... I'm not sure. :)

MarcoLizza commented 9 years ago

Fells pretty like JSON, indeed... :wink: