5 ms·
I like the idea very much, and the brevity and clarity of the implementation is pleasant. However, there are a few problems with it: \\/"- \_/ wren v0.0
by tonyg 12y ago
I like the idea very much, and the brevity and clarity of the implementation is pleasant. However, there are a few problems with it:
\\/"-
\_/ wren v0.0.0
> var F = new Fn { | x | F.call(x + 1) }
> IO.print(F.call(10))
Segmentation fault
This shows two things. (1) it's not yet a "safe" language implementation; (2) it doesn't support proper tail calls. Both are fixable, of course, but the lack of proper tail calls is a Bad Language Smell.
Its syntax is also a little strange. The following isn't accepted by the parser:
var F = new Fn { | x | if (x == 0) { return x } else { return F.call(x - 1) }}
IO.print(F.call(10))
but the same file with extra newlines is accepted just fine:
var F = new Fn { | x |
if (x == 0) {
return x
} else {
return F.call(x - 1)
}
}
IO.print(F.call(10))
It also makes me a little sad that
var F = new Fn { 0 }
returns 0, while
var F = new Fn {
0
}
returns null.
- munificent 12y ago> (1) it's not yet a "safe" language implementation; Not yet, but it's getting there! I have to fix this: // TODO: Check for stack overflow. On line 335 of wren_vm.c. The actual fix is pretty straightforward. You just store the maximum stack height with each function and check that that doesn't overflow on a call. I just haven't gotten around to it. Wren should be pretty good about most other boundary case behavior. If not (and like this issue too!) it's a high priority bug. Your scripting language shouldn't crash! > (2) it doesn't support proper tail calls. Both are fixable, of course, but the lack of proper tail calls is a Bad Language Smell. That's correct. I have implemented TCO before. Wren's predecessor Finch supported them. Fibers in Wren already support an analogous feature. My current thoughts are that I likely will support this, but it will be explicitly opt-in. Instead of having the tail call automatically be detected and eliminate the parent stack frame, you'll have some specific syntax to say "this is a tailcall". This is for two reasons: 1. Most of the time, having a useful stack trace is really handy for debugging. For better or worse, stack traces are a standard tool for most users to understand when their code does something wrong. Eliminating all tail calls hurts this with little benefit in the majority of cases where you aren't doing any recursion (mutual or otherwise) and will never blow the stack. Of course, you can do clever things to regenerate stack traces while still doing general TCO, but that adds a lot of complexity and simplicity of implementation is an important feature. 2. With a language that has a non-sexpr syntax, it can be very hard for a user to tell which call actually is the tail call. Consider something like: { return (foo.bar + !baz).call ? some.thing : other / wise } Can you quickly identify if this code won't blow the stack or not? If a user comes along and makes some tiny change to that statement, can they be confident their program won't start failing on larger inputs? Given that, I think it's good to visibly call out "Hey, there's a tail call here. It's important!" in the source code. I'm not sure what the syntax will look like yet, but that's my intent. var F = new Fn { | x | if (x == 0) { return x } else { return F.call(x - 1) }} Designing a syntax that's simple, expressive, and beautiful is tricky! Coming up with one that's easy to compile with a single pass compiler is even more tricky. In this case, because you don't have a newline after the "|", it is looking for an expression body. "if" is a statement, not an expression. There's a bunch of ways this could be improved: 1. Certainly, the error message should be better! 2. We could make "if" an expression. We could make everything an expression for that matter, though there's some pros and cons there. 3. If could allow statement bodies in single-line functions. > but the same file with extra newlines is accepted just fine: Yup. This is probably the most unusual corner of Wren's syntax. It's documented here: http://munificent.github.io/wren/syntax.html#blocks http://munificent.github.io/wren/syntax.html#blocks The constraints I'm working with are: 1. I wanted a terse way to define single-expression members and functions that return their result. 2. I'm not a fan of implicit returns in larger bodies. I don't like the idea of methods implicitly leaking the result of their last expression even when that isn't intentional, and I think it tends to lead to less efficient bytecode. So I tried to come up with a specific syntax for a single-expression body. Other languages use stuff like "=>", but I wanted to try to not add more punctuation. Since newlines are already significant in the language, I figured I'd try using the newline after "|" or "{" as the signal. So far, I personally like how it feels, but feedback from others like you is definitely helpful. If it rubs a lot of people the wrong way, it's easily changed.
- tsuraan 12y ago> 1. Most of the time, having a useful stack trace is really handy for debugging. For better or worse, stack traces are a standard tool for most users to understand when their code does something wrong. > Eliminating all tail calls hurts this with little benefit in the majority of cases where you aren't doing any recursion (mutual or otherwise) and will never blow the stack. In "Finding the Needle"[0], SPJ et al came up with a nice stack trace representation for GHC that handles recursion. Not sure if you've seen the paper, but it might be worthwhile if you haven't (section 4, specifically). [0]: http://research.microsoft.com/en-us/um/people/simonpj/papers/stack-trace/DebugTraces.pdf http://research.microsoft.com/en-us/um/people/simonpj/papers...
- JadeNB 12y ago> 2. We could make "if" an expression. We could make everything an expression for that matter, though there's some pros and cons there. I'm not sure if this is the place for such a far-reaching discussion, but I've never understood this point. What are the cons to making everything an expression?
- tptacek 12y agoHere's a bunch of Pythonistas grappling with this question: https://mail.python.org/pipermail/python-ideas/2008-September/001932.html https://mail.python.org/pipermail/python-ideas/2008-Septembe...
- munificent 12y agoUntil recently I was firmly in the "statements are evil" camp. Now my perspective is a bit more nuanced. There are two facets to "everything is an expression": 1. Any grammatical production can be used in an expression. 2. Function/method bodies implicitly return the result of the last expression they contain. I really like #1, though even expression-oriented languages limit this. There's still something akin to "operator precedence" at the "statement-list" expression level. If, for example, you look at Ruby's grammar, you'll see it has productions called "statements". This is to rule out totally bizarre things like: 1 + while (foo) { ... } for (for (for (...) {}) {}) So, even if you treat everything as an expression, there are still some limitations as to where different grammatical productions can appear. In practice, I think the main thing you gain (in a language with an imperative-style syntax like Wren, Ruby, etc.) is the ability to have the right side of a return or assignment be a branching statement. Stuff like: var fizzBuzz = if (n % 15) { "FizzBuzz" } else if (n % 5) { "Buzz" } else if (n % 3) { "Fizz" } else n The other statements are less useful. I'm not even sure what the result of a looping expression should be, but I'm certain that I don't like CoffeeScript's answer (an array of all of the elements, which often gets discarded!). Break and return statements have no useful results. Variable declaration statements make it really hard to reason about scope when used in the middle of an expression. All this means that I think loosening up the grammar to make every statement an expression is mostly only useful to make branching an expression. The other half is having functions implicitly return the result of the last expression. I really like that in some functions for brevity, and really dislike it in others. This may not be rational, but creeps me out that lots of Ruby methods just leak random values. For me, I'd rather have an explicit form for bodies that implicity return their result and have the default be to not do that. So that's what I did for Wren. A single-line body implicitly returns its result, but a multi-line one does not: class Person { name { "Fred" } // Implicit return. sayHi { IO.write("Hi, I'm Fred!") // Returns null. } } I like this more than always having an implicit return, so this addresses point #2 above. I'm not opposed to allowing branch statements to be expressions, but I worry that the confusion it causes may outweight the benefit, so I conservatively left it out for now. Having a statement/expression distinction also makes the bytecode compilation a bit simpler. It's easier to tell which things emit values and which don't, but that's not hard to fix.