GorillaScript
ckknight.github.io
ckknight.github.io
As a small complaint: After seeing about a dozen compile-to-JS languages, I don't care about the full list of features anymore. Please add a big note at the top saying how this is different from other compile to JS languages. Why save all the good stuff (typing, promises, currying) for the last half of this massive page?
As a guy who built his own npm-installable macros.coffee, [0][1][2] I am torn.
I love macros enough that I felt I needed to have them in JS-family languages, which have few metaprogramming options outside of tricks on 'this', which is why, when I saw that CoffeeScript had clean syntax and a relatively clean AST, I made it my "learning node/npm" project to add them.
On the other hand, my project is simple because it depends on a fragile hack: running a deep-copy on a chunk of AST for 'quote'. It's a fragile hack that jashenkas could break with a single well-placed closure, and as I promised on my project page, "If you want to ... build a substantially more 'heavy-duty' implementation, I'll probably end up using yours." This isn't an implementation in CoffeeScript, but it's better -- it's actually built into the language (as macros work best as a core language component; once you have 'em, it's easiest to write your language with them).
I'll have to try out GorillaScript and see what it actually feels like to write code in the language, allowing for the fact that its syntax may not be as pretty as coffeescript yet. But I wouldn't be surprised to find myself using GorillaScript this time next year.
Macros++.
-----
0) github.com/mrluc/macros.coffee
In it, I discuss some of the design decisions. Sadly, there wasn't any video, so I don't actually go in-depth in the slides themselves.
Please somebody...
Requires a runtime, though.
> The code generated by Traceur depends on a small runtime library containing Javascript code for things like creating classes, handling spread arguments, etc. To use code compiled from Traceur, you'll need to include that too.
My main concerne is with optionnal typing ( which is my only real concern with javascript at the moment ) : it says that types are checked at runtime.
Does this mean i still have to run unit tests that goes into all my code before i can be sure i don't have a type error ?
const DISABLE_TYPE_CHECKING = true
at the top of your file. It's handy to have in development vs. production.
Thus, the following would compile without warning:
let double(x as Number) x * 2
let repeat(x as String) x & x
let fun(x as Number|String)
if is-number! x
double x
else
repeat x
let fail(x as Boolean)
// this should fail at compile-time
double x
I could write the compiler to be a lot smarter and have it track types through conditional branches (which would be very hard, but possible), or allow it in those cases where it is possible that the types overlap.I also might add a "cast", i.e. a runtime type check. I'm not sure what the best syntax for it would be, but it would act like the following:
CAST(x, Number) // validate that x is a Number or throw a TypeError
CAST(x, Number|String) // validate that x is either a number or a string, otherwise throw TypeError
CAST(x, Number, 0) // validate that x is a Number, otherwise evaluate to 0.
Of course, in the cases where `x` could not fit the type requested, then that could error at compile-time.I would love some feedback on this, especially from the perspective of "what would I use to help me develop", not "I need my program to be 100% statically type-safe without having to do any testing".
I will end up sticking with TypeScript though for anything at work since the type system has more capabilities right now and it is just Javascript with types so it is very simple to use for those with Javascript experience.
BTW, this got me thinking: are the Javascript envine vendors (V8, etc.) doing anything to make it easier to write languages like GorillaScript or CoffeeScript? Or is it all up to the language designers?
Though this is definitely the best compile-to-JS language and I'd choose to use it if I'll ever feel safe enough using anything else than plain Javascript.
Yes an no. YOu can't access it directly, but if you call toString() on a function you'll get is source code as a string so you can parse it.
Prototype do that to handle the special $super parameter [0]. But most modern minifier rename function parameters and local variables, so it's not a good idea.
[0] https://github.com/sstephenson/prototype/blob/master/src/pro...
let use-named({ name, age }) ->
use-named(name: "ckknight", age: 25)
For refinements, what are you referring to?
You're right, it's no real need for it and it's unfeasible because you need to either (a) fully static type analyze everything or (b) what anonymous just explained... and they're both out of the question.
The refinements thing was a joke, in the spirit of what else can one add: it's a controlled form of monkeypatching / classboxing about which a lot of fuss was made in the Ruby community after Matz decided to add it to the language (http://timelessrepo.com/refinements-in-ruby), mainly because it's very hard to implement without creating more problems, even if you have your own interpreter to hack on (short answer: don't bother looking about it and even if you're smart enough to figure out a way to add it to a compile-to-JS language, don't waste your effort on it - they're not an anti-feature and they could make sense in languages that allow monkeypatching, like JS and Ruby, but they're too much work for too little benefit and too much possibility for bad use and unneeded complexity).
Also nice is that it doesn't have to create a full defaults object at runtime, and each binding would only be evaluated if individually needed:
let func-with-named-params({
id as String // TypeError if not a String
metadata // don't care about type, defaults to void
seed as Number = get-random-seed() // requires a Number, only calls function if not provided.
}) ->
You can do all sorts of things with object destructuring.Yeah, Refinements seem like a similar thing to extension classes in .NET where methods defined somewhere else affect a class but only if included, without polluting the globals.
The only possible way I could see supporting something like this is with a different method call operator, so something like:
NOTE: The following is theoretical code. It does not work in GorillaScript 0.9.x, but might be implemented if there is enough positive feedback for it.
refinement String
def repeat(count as Number)
if count < 1
""
else if count < 2
this
else if count bitand 0b1 // if it's odd
this->repeat(count - 1) & this
else
(this & this)->repeat(count bitrshift 1)
// and used as such:
"Hello "->repeat(5) == "Hello Hello Hello Hello Hello "
// would turn into this code:
String_$_repeat("Hello ", 5)
let unknown(x)
// This would throw a compile-time error, as x's type
// would be unknown. It would have to be a single
// named type that has a refinement defined, either at
// the top of the file or with an import.
x->repeat(5)
Also, refinements could have macros on them, so one could do "Hello"->for-code-point point, index
// This is a body in a macro defined as "for-code-point" on a String refinement.
do-something(point)
So, this would be a nice way to have refinements, but have the terrible downside of requiring the type to be statically known.You could also define a refinement on a union type or even an object type, such as the following:
refinement Array|{ length: Number }
// define a getter, why not?
def get median()
this[this.length \ 0]
macro loop
syntax item as Identifier, index as (",", index as Identifier)?, body as Body
index ?= @tmp \i
AST
let mutable $index = 0
while $index < @length, $index += 1 // it knows that `@length` is a Number, no need to type checking
let $item = this[$index]
$body
def each(callback)
// look, we're using the macro we just defined!
this->loop value, index
callback value, index
The refinement namespace would not conflict with the normal invocation namespace, so you could have `"Hello".same()` and have it be exactly as expected, but `"Hello"->same()` not, because it would turn into `String_$_same("Hello")` (or something like it).To clarify before, the following would work:
refinement Number
// automatically knows it returns a Number
def log(base as Number = Math.E)
Math.log(this) / Math.log(base)
let alpha = 1e100->log(10) // knows that 1e100 is a Number, obviously, and since ->log returns a Number, alpha is automatically known to be a Number.
let bravo = alpha->log(10) // knows alpha is a Number, etc.
let charlie = Math.pow(2, 10)->log(2) // since we know Math.pow returns a Number, we're good.
let delta as Number = some-library.some-unknown-method()
let echo = delta->log() // we specifically declared delta as a Number, so we can use ->log
let foxtrot = [1, 2, 3]->median // Array subtypes from { length: Number }
let golf = "hotel"->median // Hey, so does String!
let india = arguments->median // And so does Arguments
let juliet = { length: 4, 2: \kilo } // And so does this custom type
let lima as { length: Number } = some-outside-source()
let mike = lima->median
// assuming I come up with a "cast" operator, i.e. type-
// check with error or default value
// this would allow a String, Array, etc., would check
// at runtime (but not in production, with
// DISABLE_TYPE_CHECKING set to true)
let november = (some-outside-source() as { length: Number })->median
// require that the result is an array, otherwise
// evaluate to []. The default could be an expensive
// operation that is only executed when necessary.
let oscar = (some-outside-source() as Array = [])->median
// unknown is of the "any" type, representing all
// possible values, including null and void.
let unknown = some-library.some-other-method()
// Since unknown isn't at least { length: Number }, we
// don't know that ->median should map to our refinement
// { length: Number }->median and thus cannot be used
// without casting.
let wrong = unknown->median
I dunno, something to think about. Would people want to have this feature?Interesting language though.
Of course you can say that since it is defined as such, it's not a issue, but then we could say that any Javascript "Wat" behavior is also defined in the spec.
Personally, I like Perl's period(.) for string concatenation, but I think it can too easily get lost in the line, especially for more complex stuff.
$foobar = "foo" . $bar->{'bar'}It more resembles LISP with its macros and closures with nice syntax built around those concepts.
I tried to make all the operators match their closest arithmetic, mathematical equivalents. Thus using `^` for exponent and `+` for addition. Since JavaScript does not support bitwise operators cleanly, needing to cast to int32 and generally being disued, I felt they could be better aliased with `bit`, freeing up those operators. Since string concatenation needed some operator as a building block for string interpolation, the freed-up `&` was a ripe candidate for it.
That all said, string interpolation is the typical use case for creating strings, so feel free to just use that if possible. Anything more than that and you might need a templating engine.
> Thus using `^` for exponent and `+` for addition
That is good in my eyes.
var 1 = 5+5 // I would expect: Error 0-9 cannot be used as a variable
var foo = "Hello " "Bob!" "\nAre you there?"
var bar = "Hello " 1
console.info(1) // throw error and refer to var 1
console.info(foo)
"Hello Bob!
Are you there?"
console.info(bar)
"Hello 1" f "Hello"
If f were a string, how would I know I wanted to concat it instead of calling?There is ZERO technological or efficiency related benefit in making () optional, it just makes your grammar parser more complex and slows down compile/interpretation time.
If I would have time and no specs given, then I would write a compiler for Sanskrit. It's one of the oldest languages and as accurate as AI Agent languages. Really accurate. Just google for "Sanskrit Artificial Intelligence" and you will find very interesting publications. Here's something by NASA: http://www.vedicsciences.net/articles/sanskrit-nasa.html
Btw. I've written a simple language+vm in C already using bison,flex etc. Our Prof and other students made a custom CPU that runs on a custom OS made for this language called Ninja. A friend of mine extended it by Object Orientation later.
I didn't want to have `~` mean string concat because it overloads the semantic meaning of what `~` would represent.
I actually stole `&` from VB, as well as `\` as floor division.
To take fear away, you can easily create very very complex stuff with a single language feature. Think about QBASIC, one would think that you cannot do anything except some command-line programs with it. See the complete opposite: http://www.pcworld.com/article/2033318/black-annex-is-the-be...
Some people like "slim" languages like C, Go, Clojure, Python, others like "fat" ones like C++, D, Rust, Scala, CL. It's just a matter of taste. I tend to like the "slim" ones more but maybe a "fat" language is better suited to wrangle the browser-based-dev can of worms.
I'm having trouble imagining when you'd need to do this. But oh well..
>> There is also a short syntax for single-word strings that also convert dashed-names to camelCase just as normal identifiers do.
I'd like to see an example of this. I don't think I quite understand what it's meant to do.
>> Binding access
I would have preferred: f@obj (arg1, arg2, arg3) for function application over: f@ (obj, arg1, arg2, arg3)
Other that that, I can certainly appreciate the huge amount of work involved in creating this and I wish them success.
\alpha-bravo-charlie == "alphaBravoCharlie"
Well, since GorillaScript doesn't require parentheses for invocation, you'd need to have some delimiter between the context and the arguments or have to wrap one of them. Why not simply just use "," as the delimiter and then you can treat it as if doing .call/.apply?
Well, I was trying to think why you'd even need a delimiter. I thought the absence of the comma between the context and the first argument would disambiguate:
f@x (a, b, c)
would be written as: f@x a, b, c // Comma between a and b
While: f@(x a) (b, c)
would be: f@x a b, c // No comma between a and b
Then again, a call like: f@ a b c d, e
would be ambiguous, so on second thought I think you've made the right decision.Heck, even the non-ambiguous cases seem easier to mentally parse with the comma there.
So I retract from what I said. I should have thought this through before posting.
Edit: I see you've also added the example of dashed-names to camelCase conversion to the page. Thanks, that helps. Edit2: formatting.
> || - or, can no longer be used with and unless one group is in parentheses
Seriously? Do you require the "+" and "" operators to have parentheses around them? Why do people think order of operations is hard with "and" and "or"? I really* don't get it (and always turn off compiler warnings about that when working with C).
Also, "ownskey" is a bit of a mouthful. Why not ".?" ("?." would be "get key if object isn't null")?
The reason `and` and `or` are best in parenthesized groups is that it creates confusion in the precedences otherwise. asking someone what `alpha and bravo or charlie` means can be confusing if there's no obvious grouping (like a standard mathematical operator's) giving `and` more precedence than `or` or the other way around.
You actually can use `!.` as an "owns access", as used in `alpha!.bravo`, which means `if alpha ownskey \bravo then alpha.bravo`.
And yes, `?.` means an "existential access", as used in `alpha?.bravo`, turns into `if alpha? then alpha.bravo`.
You can also mix these with `?!.` and it works as expected. :)
BTW, I don't like coming across super critical, but this is a pet peeve of mine. This particular issue really gets under my skin (I always pass -Wnoparenthases to gcc). Overall I am very impressed with Gorillascript. I love the macros, the functional programming stuff and the basic terseness of it all.
* immutable values
* cascades
* try/catch/else/finally
* promises
* async
* optional typing
* curreid functions
* genericsHeck, I could probably write the build tools plugins in Komodo WITH GorillaScript. Of course, not quite the same as a brand new self-hosted compiler, but I'd like to think that it's circular-referency is adequate to be in the vein of functional programming tradition :)
I was never excited about Coffeescript. This is JavaScript: The Good Parts And Even The Best Brand New Parts
Then, file issues on the GitHub tracker with your ideas to make GorillaScript better for you. Or join us on IRC at irc.freenode.net at #gorillascript. I (ckknight, author of GorillaScript) try to be there as often as I can, but I do have a life outside of IRC, for now...
I kinda hope this doesn't become a trend though, every web development enviroment spawning its own compile-to-JS language. I feel that there are too many such languages already.
Honestly, the macros section scared me out, especially the "there are five types of macro syntaxes" (...this is as far away from the pythonic way of doing things as one can get) but this seems the most promising compile-to-Js language I've seen.
GorillaScript is designed to run with JavaScript semantics and be as efficient as possible (within reason), so that when you write something as simple as `x + y` in GorillaScript, it is efficient, even with unknown types, unlike Python which would need to check `__add__` or Ruby with its operators-as-methods system.
Also, since JavaScript is so different from so many languages with async-by-default, handle through callbacks or promises or something, being able to replicate something as compilicated as Python's GIL in JavaScript would be atrocious as compared to designing your code in a language that nudges you to write "good" JavaScript.
The section on "Optional Typing" is too brief. Type inference is mentioned, but there are no details on how the type system functions and what a failure of type checking means. It seems one can't name types, which is odd and will lead to a great deal of repetition.
A failure of type checking generally means you get an early runtime error, though I plan on reworking things to catch more at compile-time (while still not being a pain).
GorillaScript works with the JavaScript type system instead of against it, at least as well as it can, including supporting nice future additions like Map, Set, and WeakMap.
GorillaScript tries to work within the JavaScript landscape as best it can so that there is as little of a disconnect between GorillaScript and JavaScript as possible.
Code that uses bitwise operations looks fairly ugly as is. Hav e you considered enabling them with a prefix or suffix? E.G. ^$ instead of bitxor, or >>$ instead of bitrshift...
macro operator binary ^$
ASTE $left bitxor $right
There are also operators for `and`, `or`, `xor` which act logically. `not` which does a boolean invert. So there are `bitand`, `bitor`, `bitxor`, `bitnot` which fit well as working bitwise instead of logically.I suppose the outliers are `bitlshift`, `bitrshift`, `biturshift`, but it does somewhat make sense to keep the `bit` prefix because it casts to int32 and because its operations are inherently bitwise.