HNHacker News
TopNewBestAskShowJobs

cameronkknight

32 karma · joined July 7, 2013

submissionscomments
cameronkknight··on GorillaScript
Juxtaposition as an operator is already used by function invocation, e.g.

    f "Hello"
If f were a string, how would I know I wanted to concat it instead of calling?
cameronkknight··on GorillaScript
If I ever write JavaScript, then I definitely use JSHint. It doesn't help with some things such as immutable local bindings, type checking, not having to worry about hoisting, or any of the nice syntax features you get with GorillaScript, which is why I see the benefit.
cameronkknight··on GorillaScript
I feel it pretty much holds true when you try to compile any language that was designed without JavaScript in mind into JavaScript, the result is likely to be ugly and/or imperformant. (Not always, e.g. asm.js)

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.

cameronkknight··on GorillaScript
Dart reinvents a full type system for itself, with its own environment and paradigms, and if you want to cross-compile to JavaScript instead of running in a dart VM, you have to ship a huge swath of code with it to get it working.

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.

cameronkknight··on GorillaScript
That could be possible, you could even do it today, if you wanted:

    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.

cameronkknight··on GorillaScript
Yes, I take a lot of inspiration (with some leeway for personal taste) from Delphi and C#, so I have great respect for Anders Hejlsberg - who I'm now competing with since TypeScript.
cameronkknight··on GorillaScript
Actually, here's a better example, if you want defaults.

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?
cameronkknight··on GorillaScript
I'm very happy you're excited about it. The best thing you could do for me now is to use GorillaScript to make something. Anything. Something big, something small, something with fancy features or minimal ones. I want you to think along the way "how could my life be easier?" "Better typing? Terser syntax? More verbose syntax? Easier async support? Some other fanciful feature?"

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...

cameronkknight··on GorillaScript
Currently, no. I do plan on adding warnings (not errors) on a statically-calculated type issue. One thing is that I don't plan on it erroring if you call a function with a value whose type might be an "any" type or a "union" where it expects a specific type. If there's some overlap, it would allow, to make things easier and give the programmer the benefit of the doubt.

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".

cameronkknight··on GorillaScript
I thought about it, but for the most part I used `~` as a way of referring to something in an unstrict way, e.g. `~+` doesn't typecheck, it just adds and auto-coerces both operands to a number.

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.

cameronkknight··on GorillaScript
Yeah, I hung out there. Also contributed a bit. No, most of my macro ideas come from Scheme.
cameronkknight··on GorillaScript
Yeah, class Child extends Parent
cameronkknight··on GorillaScript
They have made serious effort with developer tooling, especially with Source Maps.
cameronkknight··on GorillaScript
I never claimed for GorillaScript to be a C-like language, because at its core, stripped of syntax, JavaScript is not a C-like language.

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.

cameronkknight··on GorillaScript
Most of the time, string concatenation is unnecessary with interpolation: "$alpha $bravo" means alpha & " " & bravo.

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. :)

cameronkknight··on GorillaScript
Interesting work. Once I refactored GorillaScript to be pretty much fully macro-driven, it made everything super-nice to deal with. Every single syntax construct in GorillaScript is a macro, even `if` and all that stuff.
cameronkknight··on GorillaScript
and= isn't all that useful, I agree, but it would be odd to leave out given that there's or=, ?=, ownsor=, etc.

\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?

cameronkknight··on GorillaScript
GorillaScript has full support for Source Maps, as any good compile-to-JS language should.
cameronkknight··on GorillaScript
Yeah, I stepped away from the Lua world to work on JavaScript-related stuff.
cameronkknight··on GorillaScript
In production code, using & for string concat is pretty atypical, since you can use string interpolation: "$(alpha)$(bravo)"
cameronkknight··on GorillaScript
Yes, you would, at least in the current state of things. I do plan on adding some compile-time checking (without getting in the way), but it's not there yet.
cameronkknight··on GorillaScript
For named parameters, this is the best one can do in JavaScript without constructing a fully static type analyzer:

let use-named({ name, age }) ->

use-named(name: "ckknight", age: 25)

For refinements, what are you referring to?

cameronkknight··on GorillaScript
type-checking and such features can be easily disabled by sticking

const DISABLE_TYPE_CHECKING = true

at the top of your file. It's handy to have in development vs. production.

cameronkknight··on GorillaScript
Types are allowed to be any of the primitive types (e.g. Boolean, Number, etc.), null, undefined, any custom "class", a specifically-typed array (e.g. [Number] for an array of numbers), or a specifically-typed object (e.g. {x: Number, y: String}). I do plan on being able to alias the typed object as an "interface", just haven't gotten around to that.

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).

cameronkknight··on GorillaScript
If anyone would like to look at the slides for a presentation I did on GorillaScript, here you go: http://ckknight.github.io/gorillascript-presentation/

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.

cameronkknight··on GorillaScript
Macros can be scary. The need for five different syntaxes is because in a non-homoiconic language, positioning and syntax matter, so having an infix operator would be defined differently from a call-like macro. They all act fundamentally the same, it's just how you define the syntax of how they are used that is the difference.
cameronkknight··on GorillaScript
Bitwise operators for JavaScript are unused except in special cases, as JavaScript really doesn't handle bitwise as one might expect. Everything is cast to either int32 (or uint32 in the case of >>>) and then recast back into a Number (8-bit IEEE-754 floating point). Due to their relative lack of use, freeing up the symbols to represent something else seemed prudent.
cameronkknight··on GorillaScript
Actually it does have list comprehensions. You just use a for loop (or any loop) as an expression and it evaluates to an array.