Closed munificent closed 9 years ago
I second the empty arguments parentheses for the function/method calls.
Now it's a bit confusing and visually cumbersome. Moreover it's a benefit to have parentheses to spot the calls better.
I also second to preserve getters and setters (although we all now the can be implemented via method calls).
As I library/module designer I would use empty-arguments-list methods and functions for activities that (could) change the internal state of the class, possibly not returning anything. Getters are only observers for the internal state of the class, and they should not perform complex activities (but they can elaborate the result, of course).
EDSLs can be less clear. Ruby has done quite a bit with optional parentheses. Additionally, Cascading / Method chains can get lumpy.
Now, I'm assuming that this proposal is not suggesting the language forces one to use the () syntax if there is mutation, but rather that it is a convention generally used. I planned on an EDSL for Time similar to http://momentjs.com/ for zeckalpha/wren-io#6
As I library/module designer I would use empty-arguments-list methods and functions for activities that (could) change the internal state of the class, possibly not returning anything. Getters are only observers for the internal state of the class, and they should not perform complex activities (but they can elaborate the result, of course).
Yeah, this lines up with my intuition too. Defining "complex" gets pretty hand-wavey but hopefully most people know it when they see it.
EDSLs can be less clear. Ruby has done quite a bit with optional parentheses. Additionally, Cascading / Method chains can get lumpy.
Ruby also has the feature that parentheses are optional even when you have arguments, like:
foo.bar arg, arg
I like what that does for some DSLs but it makes the grammar really hairy, so I plan to stay away from that for Wren.
Now, I'm assuming that this proposal is not suggesting the language forces one to use the () syntax if there is mutation, but rather that it is a convention generally used. I planned on an EDSL for Time similar to http://momentjs.com/ for zeckalpha/wren-io#6
Right, there's nothing preventing you from doing all sorts of shenanigans in a getter. From looking at that API, I think it would be totally kosher to have fromNow
and calendar
be getters.
Defining "complex" gets pretty hand-wavey but hopefully most people know it when they see it.
Everytime the language does not dictates a way to accomplish something, it's always a fuzzy and questionable matter of individual style.
Over the years I stumbled upon some very "complex" setters, mostly due to inexperience of the programmer. In the majority of the cases getters have simple body structures... otherwise, they are methods.
Clear enough, uh? :)
I like what that does for some DSLs but it makes the grammar really hairy, so I plan to stay away from that for Wren.
Agreed. This is just the empty arguments case.
This is done now! A key motivation (in addition to being able to define methods with empty argument lists) is that Wren now has a well-defined concept of a "call signature":
// call // signature
obj.foo // foo
obj.foo() // foo() <-- note, different from getter
obj.foo(arg) // foo(_)
obj.foo(arg, arg, arg) // foo(_,_,_)
obj.foo = arg // foo=(_)
-obj // -
obj - arg // -(_)
obj[arg, arg] // [_,_]
obj[arg, arg] = arg // [_,_]=(_)
These show up in stack traces and debugger output. But, more importantly, they are used in the embedding API now. I'm hoping to use that in some changes in the embedding API I have in mind, though I'll have to see if it pans out.
I also changed some core methods to use ()
:
Fn.call()
List.clear()
Map.clear()
Fiber.call()
Fiber.run()
Fiber.try()
Fiber.yield()
Horray! Well done! :+1:
:metal:
Right now, if a method takes no arguments, it has no argument list at all.
()
is a syntax error in Wren. That works great for "getters", things that are conceptually properties on an object:It's a little weird, but I think generally good for methods that generate something new but don't modify the underlying object:
Where it feels strange to me is methods that do modify the object:
It's particularly odd when you call one of these methods from inside its class and use an implicit receiver. For example, here's a piece of the Set example:
Seeing a bare identifier on the
// <--
line and having to realize it's there because it causes code with a side-effect feels spooky to me. Now, maybe this is just because I've inherited prejudices from other languages and I just haven't internalized Wren's way of doing things. But, given that Wren is designed to be familiar to people coming from those other languages, I feel this may be confusing.Proposal
My proposed solution is to allow an empty argument list as part of a method's name. So just as you can overload
foo
andfoo(arg)
, you could also definefoo()
. Note that this would not make the parentheses optional.foo
andfoo()
would be two different methods with different signatures.Since this is just another syntax for defining names, it adds little complexity to the language. The compiler has to take
()
into account when building a method name, but the rest of the VM is unaffected.What I am concerned about is whether or not this makes it hard for library designers to know which form to choose. When should you use a getter versus an empty argument list? If the guideline isn't clear here, we'll get frustrated library designers and inconsistent libraries.
I think the reasonable rule is probably, "if it modifies any state (aside from internal-only caches, etc.), don't use a getter". So,
clear
andcleanup
would becomeclear()
andcleanup()
(although the latter is a bit fishy since it doesn't modify anything publicly visible) whileabs
andtoString
remain getters.Thoughts?