1. Pyret comes out of Shriram's group's expertise with pinning down exactly what Python and other dynamic scripting languages do right, and (mostly) do wrong. Check out their recent paper Python: The Full Monty: A Tested Semantics for the Python Programming Language http://cs.brown.edu/~sk/Publications/Papers/Published/pmmwpl... for more context.
2. Ruby is far and away not the originator of 'end' to end blocks -- this comes from Pascal and is in other languages that have nothing to do with Ruby, like Lua.
3. Languages that aren't Haskell have ADTs too; it happens that they've lifted an ML-style syntax for defining them.
4. Python is widely used pedagogically, so for better or for worse, students are already being familiarized with the colon, which I agree is a bit anomalous.
The use of 'end' comes from Algol, not Pascal.
data BinTree: | leaf | node(value, left, right);
We use "end" or ";" in order to have unambiguous delimiters for the ends of syntactic forms and avoid needing to depend on whitespace (we added some discussion on pyret.org about our philosophy on indentation and why we don't want to depend on whitespace).Making that leading pipe optional is a good idea. I made an issue for it, we'll think about if it'll break or confuse anything and add it if it doesn't:
https://github.com/brownplt/pyret-lang/issues/106 check:
4 + 5 is 9
1 / 3 is 2 / 6
9 - 3 is 6
5 > 4 is true
end
Then becomes, not wrapped in end-check, but: check(arithmethic-test):
4 + 5 is 9
1 / 3 is 2 / 6
9 - 3 is 6
5 > 4 is true
end(arithmethic-test)
Or something similiar. This makes mis-matching "end"s (either typos or
artifacts from cut'n'paste coding) explicit errors that are easy to
spot, and identify.It would add a lot of verbosity, of course. As for ";"/"end", consider (the presumably valid):
check:
4 + 5 is 9
1 / 3 is 2 / 6
9 - 3 is 6
5 > 4 is true
;
That trailing ";" is going to trip someone up. Also consider
(cut'n'paste-with-quick-edit): check:
4 + 5 is 9
1 / 3 is 2 / 6;
9 - 3 is 6
5 > 4 is true;
end
Is this valid?So e12e is making an argument for XML-y over Lispy. However, choosing good words is really, really hard. If you pick a different word for each construct, there's a needless mental burden; if you pick a uniform strategy ("end-<kwd>") you potentially get less readable code. And either way it's far more verbose, as you point out.
Some of our target audiences are middle- and high-school students, some of whom have weak typing skills (we know from numerous workshops we run). Increasing even the raw number of characters is a real problem for them.
My preference is to use ; for one-liners, but end where you have a multi-clause entity and you want to be able to clearly tell where it ends. If we find that there is general agreement on this, we can make it a context-sensitive check, à la indentation. Then, a program generated by a tool can do something consistent and ignore all these checks, whereas human-facing environments would enforce them (by checking or correcting).
As for your examples, to my Pyretical eye, the third of your examples (with the dangling ";") looks just wrong. However, your fourth example is syntactically incorrect.
I certainly understand this argument, and I generally favour simple syntax over smart tools -- but how much of a difference would this make in an editor/IDE that automatically inserts the closing-tag? (you type case, the editor appends esac (but below the point you're typing)):
1: | #your cursor at |
2: case|
3: case:
| # you hit enter or something, ready to fill in
esac # editor has closed block/statement
I imagine typing-skills isn't much of an issue when editing/copying text -- as opposed to typing in new code?When editing/copying, it's not typing skills but editing skills, which are arguably subtler and even harder.
It would be a shame to compromise the design of an interesting language just because the target audience is not clearly defined.
All of these are educational audiences. I don't see how one can define the audience more clearly than that, given that I have no control over any of these audiences.
So, for example, you can move chunks of code from one place to another and select it all and re-indent - something that you can't do (in general) in Python. It also makes it easier to programatically generate code.
In Python, on the other hand, the information only exists in one place: the indentation. This is read both my machines and humans. There is no "re-indent" operation as the indentation is the only place the information exists, so there is nothing to sync.
I have heard people bring up programatically generated code before, but as another commenter put it "where Ruby usually denotes code blocks with a `do` and an `end`, Python denotes code blocks with a `:` and an outdent" – so is that really a problem? Isn't it just a slightly different way of encoding the same information?
(As to moving chunks of code in the editor: If the editor has a command to re-indent a selected block, it probably also has a command to shift it left or right.)
def embiggen(x): return x * 2
for i in range(10): print(embiggen(x))
Also, it just makes things read more naturally to us humans, I think.Anyway, it seems to me that the only real difference between your code examples is that the second one lacks an ending designator and uses an equals-sign instead of a colon. However, in order to make the lack of an ending designator work, you need a more complex parser (it needs to infer block-ends from indention or some other context). Leaving off ending designators also increases mental overhead for the user, as they must keep block-end inference rules in mind in writing code. Using colons versus using equals-signs is simply a matter of taste (not weight, as you seem to claim).
That said, I do see some waste in their datatype definition syntax. First, I'd prefer curly braces over the colon/end pairs that they went with, as that saves a few characters. Second, newlines are a fine separator, so why also require pipes? This requires the user to type three characters (newline, pipe, space after pipe) when just one would have done fine.
I admit to also being a brace weenie; I would very much prefer if all the languages I had to use had them. However, Pyret exists in a tradition of many successful, braceless languages like Python, ML, and Lua.
data Color = Red | BlackThat said, please consider the following:
data Color = Red | Black
data Color { Red; Black }
data Color = Red | Black | Green
data Color { Red; Black; Green }
I think the brace + semicolon-newline style actually compares pretty favorably on single-line width (it wins out on line width to an increasing degree as the line gets longer, which is important). However, it is visually more complex, which matters a lot for shorter lines. For this reason, I think that allowing both syntaxes would be ideal. The pipe-based syntax could be encouraged for single lines (newline-termination would sidestep block-end inference issues), with the curly brace based syntax being encouraged for multi-line definitions. There is a slight disadvantage in that now a user would have to know both syntaxes, of course.
edit:
Of course, since Pyret's syntax requires that ADT parameters be specified in parentheses, you can actually omit the pipes in single-line mode, too, and opt to use space-juxtaposition.
We don't EVER use = for assignment. For us, = is binding. If you write
fun f(x):
y = x * x
y = x + 2
x - y
end
Pyret will say I'm confused: y is defined twice
and point to the two bindings.The goal here is to make the common case work fine, where you create a bunch of distinct local bindings; but when you try to mutate, you have to do it explicitly:
fun f(x):
var y = x * x
y := x + 2
x - y
where:
f(10) is -2 # the test passes
end
The first line inside f says "y is _currently_ this, but it's variable, so look out!", and subsequent mutations use := to change y. Unlike JavaScript (say), mutating a variable that hasn't previously been defined is an error, rather than quietly adding the variable to the set of defined names.- We want := to mean "change". The initial binding is not a change. This is precisely the "bindings and assignments" distinction you're talking about.
- A variable may in fact not be mutated.
- A small point is that we want to localize editing when making a change.
So, the way I read
var y = x * x
is, "y is currently bound to x * x. However, y is a variable, so this binding is not guaranteed to last. Be careful about assuming you know the value of y." If you then see y := x * z
it reads as "y's value changes to that of x * z".So: "=" means "introduced a name with this value"; "var" means "but this binding may change"; ":=" means "and yup, it really _did_ change".
data Color: Red | Black | Green;
We did also initially discuss using = instead of : in places where we were defining things (functions, data). That's still not completely out of the question. That would get you to
data Color = Red | Green | Black;
We need a closing delimiter to avoid creating huge ambiguities in the grammar.
The syntax is basically Ruby + Python + Haskell. Each of those languages has a lighter, more intuitive and memorable syntax.
Why would the syntax be:
Instead of just The whole colon thing in Python is a mistake, it should have never been in Python, and it definitely not be repeated in other languages..