As a PL designer, there is a lot to like here; the design seems quite reasonable and thought out.
But what I really like is the presentation: I like how all features are summarized, pointing out what is novel, and then everything is just discussed using examples and comparisons to other languages. This is something I'll have to try when I finally get my own language off the ground :).
I wonder if the author is interested in engaging with researchy PL designers. E.g. the testability orientation of the language would make for a good Onward paper, or maybe a Future of Programming workshop presentation.
The Onward! deadline is early April (SPLASH will be in Pittsburgh in late October); the future of programming workshop (or whatever we call it) deadline will be sometime much later than that (maybe July?) and the presentation format will probably be flexible (video, paper, ...) with tracks at both strangeloop and SPLASH.
I'm not sure I like the closure syntax, with the explicit return. Most modern languages (Swift, Rust, Python (lambdas), Lisp if I can call it modern) support implicit returns and it looks much better. Maybe it's just the juxtaposition with the JS code, but I was confused for a second by the word "return" inside the closure, thinking that it returned from the original function with just the first result.
(I really like Rust's take on this with the semicolons—just leave off the semicolon to return—it makes the semicolon actually useful.)
Arbitrary two cents: Rust's approach to this bothers me a lot. A missing semicolon having such a weighty meaning strikes me as one of the worst warts to survive Rust's rapid evolution. It forces you to scan very carefully to determine outputs.
The rule only applies at the ends of blocks/functions. The semicolon can only be omitted on trailing expressions. And, when it's omitted, the block/function takes on the value of that trailing expression. E.g. this is not valid Rust:
fn foo(x: bool) -> i32 {
if x {
1 // no semicolon on the 1
}
let a = 2 + 3;
return a * 4;
}
It's not implicitly returning the 1 from `foo`. An early return would need to be written `if x { return 1 }`. On the other hand, the `return a * 4;` could just be written `a * 4` since it's at the end of the function.
This rule means the scanning required is just looking at the end of the function.
In any case, having a static type system means I have never personally encountered a bug caused by this sort of implicit return in Rust.
I can imagine so; But as a counterpoint, isn't it worse with forced implicit returns, where you never have a choice but to return something, and you have to scan the code carefully to see if it was required?
Maybe I'll add a 'noreturn' keyword; single expresions are automatically returned, and you can use a 'noreturn' statement to explicitly return void despite running a non-void expression.
Sadly I don't really have a perfect answer to that. I'm inclined to think making all returns explicit is the right choice, and is only really a problem in a language where long returns are common (specifically in my mind is ruby, where being able to return from the enclosing function is crucial to the common uses of blocks). Arguments against in languages that don't feature this sort of thing seem to be against the length/weight of the word return.
Something I've played with in languages I've worked on is named returns [1], which I like for that case.
[1] https://github.com/stormbrew/channel9/blob/master/sample/c9s... -- see the get function definition, '-> return' names the return 'channel' and 'return <- val' calls it (which returns). Now I might consider having an implicit '<- val' with no lhs that calls the current function's return, I think.
I do think a special case for single statements makes sense, though.
Which begs the question, why even have the trick of the missing semicolon in that case? Why rely on one missing 5 pixel character to say that "this function defies your expectations"?
If it's not the norm, I'd rather have an explicit "nil" or something at the end.
The trick is that now the keyword return marks an early return. And as early returns are often exceptional, I find the added visibility nice. On the other hand, every function in Rust, except those that contain an infinite loop, returns something so the keyword at the end of the function body is redundant in my opinion.
Also, because function signatures are never infered in Rust, mistakenly adding or forgetting the last semicolon will always result in a type error.
Well, if you add a semi-colon where it's not supposed to be you'll get a compile error. If you omit a semi-colon, you'll also get a compile error. They're just there to separate statements (Rust is not whitespace-sensitive).
The most common pattern in my Rust code is:
`statement; statement; expression` where the expression is what I actually want to return from this function
it seems more noisy to say
`statement; statement; return expression;`
It'd be `()` and make it so that every side effecting function would be at minimum two lines long, and be a visible wart. It's pretty easy to tell that if the function returns something, the last line evaluates to that expression even if there's no `return` since if it didn't, it wouldn't compile (or there'd be a dead code warning).
To me there is nothing major about a function returning. Every function does that. Implicit early returns on the other hand could be deceiving, but I don't know any languages that would support those.
Agreed that it's a matter of taste. After working with Ruby and Clojure I don't even want statements or void functions to be a thing. I prefer the "everything is an expression" paradigm.
The Javascript-for-comparison would help the case better if it didn't look unfairly poorly-coded. (And it doesn't express quite the same logic, though I don't think that makes it look worse here.) I'd have written
function RecordValidator(sessionHolder) {
function validateRecords(records) {
return records.every(validate);
}
function validateRecord(record) {
if (record.lastRevision !== undefined
&& record.lastRevision.user !== undefined) {
return record.lastRevision.user.getAccounts().every(function(account) {
return sessionHolder.hasAccount(account);
});
}
return record.revisions.map(usesLimits);
}
function usesLimits(revision) {
return sessionHolder.hasUser(revision.user);
}
return {
validateRecords: validateRecords,
validateRecord: validateRecord,
usesLimits: usesLimits,
};
}
i like the idea of letting a type do double duty as a variable name if it's obvious in context, and being aliased by an explicit name if wanted/needed, but letting the order be irrelevant sounds like unnecessary flexibility to me - it makes writing no easier, and reading harder.
This is covered in the FAQ. Partly, I allow it because there are no parsing ambiguities with it, so why not. But moreover, depending on type name & variable names, intelligently beginning or ending with an alias can lead to extremely fluent APIs. "that(expected Bool)Equals(actual Bool)" vs "writeTo(Linkable output)". This freedom can lead to more readable APIs in any language, not just English.
Of course, I do see where the complaints come from, and a part of me fears the day that someone releases a wake linter which requires aliases to follow the type. If that were to happen I think I'd nix the idea in favor of unity in the community.
that's a good point, and the line of code does read fluently. i'd still argue that blurring the "alias = noun, type = adjective" metaphor is a bad idea; you couldn't write
that(expected Bool, expected Int)Equals(actual Bool, actual Int)
to match on two values, for instance. on the other hand, constraining the adjective to come before the noun is an english-based convention, so you're probably right about seeing which way the community leans before doing anything.
I think the top blurb would benefit from specifying what modern problems Wake is good at solving. Certainly, the rest of the docs give a better idea, but most programmers have a short attention span regarding brand new languages and won't get beyond the first paragraph.
Agreed, I feel like every JavaScript framework of the week should have this as well. What advantages does this tool have over everything else that is similar to it?
> We've both created and borrowed some cutting edge ideas.
What ideas are new? I'm not sure about provisions because the examples aren't really clear, but all the other features I've seen in other languages before.
> 3. Shadowing (concise, safe)
It doesn't seem like shadowing is the feature here, rather the use of '$' to prevent shadowing.
> Lists (called Arrays in many other languages)
There's a very good reason for this. Lists and arrays are very different data structures. Why create confusion by calling them lists?
> Since Wake blurs the line between variables and types, we can boast the smallest foreach loop of any language.
Really, the smallest foreach loop of any language? Come on.
> Self Executing
Why is this called 'self executing'? Also, I'd expect any language with lambdas to support calling a lambda right after it's created.
I've typically found PL related articles do much better on Hacker News than Reddit, I think perhaps because of differing (but overlapping) target audiences. Always found it interesting what articles work here but not on Reddit (the converse isn't as interesting...)
IMHO the website tries to hard to portrait wake as a better language than others. Its ok to compare yourself to other languages, but the bytecount (which doesnt really say much about a language) and the coloring of the boxes leave a bad taste with me.
Same in the "What Wake did without" section. Yes, you do not have those features, or bugs, but have you really "FIXED" a bug? There is no bugs you have fixed, just different design decisions.
edit:
The language may or may not be actually worth something, but those things make it look like they try to hard to me.
> Code as concise as javascript, with inheritance and typesafety and more
Extremely minor potential nitpick: correct me if I'm wrong, but I think this sentence implies that Javascript is not type-safe, when it is indeed type-safe; it's just dynamically typed, and its type system gives meaning to all expressions (where that meaning is to signal an error in some cases).
"But wait a minute, Assembly is not type safe!"
But yes it is, just every type is a binary number. It's not Assembly's fault that you're trying to use binary numbers to print characters to the screen or point to regions of memory and somehow get the two mixed up.
I wouldn't call JavaScript type-safe by any type-theoretic definition. It allows operations that really make no sense for the types involved ([]+{} ??).
I think that whether the semantics of the language make sense to a particular person isn't really the criterion for type-safety. The language does define what should happen in the case of the expression ([]+{}), so no violation of the type system occurs and the behaviour of the program is well defined.
How about {} + [], is that deliberately zero, and why is addition not commutative in this case if it is allowed? Or {} + {}?
I think most people would infer type safety to mean that attempts to coerce types which are meaningless result in an error, not meaningless output, like the result of "string" - 1, or {} + "string" or {} + []. Otherwise your type system isn't going to stop you doing something insane by mistake, and is therefore not adding any safety.
{} + "string" or similar just shouldn't be allowed, and in most languages (let alone type safe ones), it is not.
Correct, there is runtime typesafety in javascript. However, type-safety is a spectrum; truthiness and falsiness for instance is not very typesafe. I will revise this to be clearer (your nitpick is valid) but I think the heart of the issue is still 100% correct.
Sometimes I just have to remind myself that not everyone is from academia. It seems like a great language but where is its charm? It does lot's of small things correct but will it make me think differently? If not it has to get a huge following with every library under the sun (think python or C#) for me to even consider it.
Types-as-variables is a very good insight. When I'd write C++ function headers where I could omit parameter names without losing any clarity, it'd feel really nice, but the implication never rose to consciousness.
Thanks! I think with programming languages its important that it looks just strange enough. Luckily I have a few simple ideas that drive most of those unique syntaxes, hopefully making the uniqueness both justifiable and attention-grabbing. Otherwise, its just the language that I want to program in every day, and I hope others feel similarly.
Been there, done that --- my own pet programming language (we all do one, or several) is Cowbel, at http://cowbel.sf.net, and it's now on its third incarnation, generating C. The second used LLVM.
The reasons I switched away from LLVM are:
- the LLVM API is huge, not very well documented, and changes radically from version to version.
- LLVM library consumers are not well supported by the project. Frequently what you get when install it is a set of static libraries, which take forever to link against and lead to 150MB binaries.
- LLVM's supported architectures set isn't great yet. C provides a portable, vendor-neutral intermediate language which works everywhere and is easy to read and therefore debug.
- targeting C makes library integration trivial (as your output program can use the library's own headers, which means ABI issues become nonexistent).
I did contemplate a partial switch, where my compiler didn't link against LLVM at all but instead just spat out LLVM bitcode assembly --- but emitting C was the same amount of work and so much more flexible. The only downsides is that C can't do tail calls, and getting the debug information to match up is more work, but that's about it.
(For the interested: Cowbel is an experiment at producing a minimal static duck-typed language --- all types are anonymous; you refer to objects only by their interfaces. The compiler than uses type inference to determine the actual concrete type of the object. This allows it to, e.g. use a single machine word to represent a number if you're never going to do dynamic dispatch on it, which means you don't end up with the weird schizophrenia of C++ and Java where some types are scalars and some are objects and they have different semantics.
It's also an attempt at minimalism; I wanted to remove as many features as possible and still end up with a expressive language with Javascript-ish syntax. I'm really proud of the way I managed to unify scope blocks and objects...
It works beautifully, and produces tight, fast code, but the compiler became unmaintainably complex and needs to be rewritten from scratch, which I haven't done yet.)
But what I really like is the presentation: I like how all features are summarized, pointing out what is novel, and then everything is just discussed using examples and comparisons to other languages. This is something I'll have to try when I finally get my own language off the ground :).
I wonder if the author is interested in engaging with researchy PL designers. E.g. the testability orientation of the language would make for a good Onward paper, or maybe a Future of Programming workshop presentation.