puffnfresh / roy

Small functional language that compiles to JavaScript.
http://roy.brianmckenna.org/
MIT License
834 stars 74 forks source link

Started a backbone todos example #171

Closed joneshf closed 9 years ago

joneshf commented 11 years ago

I threw together this todos example. It is very much a copy-paste job with a conversion to roy syntax. There's no roy semantics in there as far as types and functional idioms go. However, it provided me with some insight into trying to use roy for an actual project. These are all my own opinions in no particular order, so take them for what they're worth.

  1. objects should take optional braces
  2. new keyword is not a keyword in roy
  3. boolean not syntax is too versbose
  4. this syntax is too verbose
  5. parens around arguments do little
  6. functions and parens are very unintuitive.
  7. functions can't take lambdas as arguments.
  8. cases cant take numbers
  9. if statements too verbose and wrapping in a function causes issues.
  10. extending objects is all but impossible.
  11. let bindings need some help

1 I think it might be helpful to take a cue from coffeescript and allow the braces around objects to be optional. I understand that they show visually where the object starts and ends. However, objects are already controlled by whitespace, so it's not like you can have attributes of an object at different indentation levels.

2 This is just something that needs to be added, otherwise new is interpreted as a function, which can cause issues when you need to pass things to your constructor. I've added the new keyword as a keyword in the todos example branch.

3 This is a minor annoyance, but I assumed not would work as any other function, and just take the rest of the line as its arguments (with precedence of course).

4 Another minor annoyance, but I'd like to have a shortcut for this akin to coffeescript's @.

5 An example of this is:

account.get('balance') + 100

This is parsed as:

account.get 'balance' + 100

Which clearly fails, as a string and a number cannot be added in roy. In order to get the semantics intended, I have found it needs to be written like this:

(account.get 'balance') + 100

which compiles to

account.get('balance') + 100;

Which seems semi-lispy, but is very unintuitive to me.

6 This kind of goes with the previous one. To get a feel for this, if you want to pass the result of a function that takes no arguments to another function, it needs to be wrapped in parens.

account.set 'balance' (wonLottery())

compiles to

account.set('balance', wonLottery());

Otherwise it just passes the function itself.

account.set 'balance' wonLottery()

compiles to

account.set('balance', wonLottery);

Which isn't really what was intended.

If I want to chain functions together, they need to be wrapped completely

(((((this).is).a).long).chain())

compiles to

this.is.a.long.chain()

Very messy.

7 This won't compile.

theMeaning λ() → 42

So you can't pass a lambda into a function call.

8 Cases seem to only take Data types. Not much to say here. Just thought I could pattern match with a case.

9 I feel it should be possible to only have the if..then.. part of a conditional statement. There are times when the else has no immediate value. It is a bit annoying to put in a null or something just to be able to compile. What is more important though, is that since conditionals are compiled inside of a function, they need to be called with the current context. I actually ended up changing my list comp to do the same in my pull request. This was also added in the branch

10 This was a big one. Trying to do something like:

this.attribute = value

or

someObject: {
    this.attribute: value
}

Does not compile. This is hell for a framework like backbone. If you ever need to extend an object that is already created, you have to use something outside of roy. I ended up using underscore for extending things, but it gets very unreadable and messy quickly, e.g.:

(_ this.allCheckbox).extend {checked: !(remaining)}

When all I want is

this.allCheckbox.checked = !remaining;

11 If a let binding is the last line in a function, it is still compiled with var in front of it. This is an issue when trying to return that binding.

let func () =
  let x = 3

compiles to

var func = function() {
    return var x = 3;
};

So let bindings either need to be lifted to the top of the function, or they need to check if they're the last line in a function. They can then do some different things like: lift the declaration one line before itself, not have a return keyword at all, etc.

There's probably some stuff that I missed, and again, this is all just my own opinions. Overall, there's not much wrong. One or two big things and the rest are personal annoyances. The example should defintely be rewritten with concern for types and whatnot. But, this also provides an example that you don't have to be concerned with types if you just want to mock up something.

kmarekspartz commented 11 years ago

Regarding 5 and 6, it seems to me this is a result of compiling what we see as a curried function to a non-curried function.

let f a b = a + b

should compile to:

var f = function (a) { function (b) { return a + b; }};

and

let f (a, b) = a + b

should compile to:

var f = function (a, b) { return a + b; }

Currently the first example compiles to the last. Is there rationale for not changing this?

kmarekspartz commented 11 years ago

Regarding 10, maybe := should be used for impure assignments?

joneshf commented 11 years ago

Yeah, after I took a step back and looked at it, the parens made more sense. Guess I've been in js too much lately.

kmarekspartz commented 11 years ago

The parens are internally consistent, but I would prefer consistency with ML and Haskell. I believe it can still be consistent with js if the tuple notation is used. Another option would be to define a curry function which can wrap around a js function.

Also, maybe all external js calls should be within a monad? The above change would make that a reasonable change.