lydell / frappe

JavaScript with some nice fluff on top of it.
7 stars 1 forks source link

This looks great! #1

Open rattrayalex opened 9 years ago

rattrayalex commented 9 years ago

Hi @lydell ,

Coming from the CoffeeScript Redux ES6 thread and just wanted to say I think this looks like a terrific syntax.

Wanted to share a few (totally personal, this-is-all-just-me) thoughts:

lydell commented 9 years ago

Wow, I didn’t expect that kind of response in so little time! Thanks!

|> is the most visually jarring thing on the page... it seems like a good idea, I'm also bothered by deleting/re-adding the ()'s, it's just the most different.

If you have some alternate syntax idea I’m more than happy to hear it. |> was jarring to me, too, at first, but then I got used to it.

I'd be in favor of having isnt -> !==, I think it's clear to developer that it means something different than is not.

I think the problem is the other way around, it might not be clear that is not means something different than isnt.

is not!== has some nice minimalism that I’ve started to like, but at the same time is not=== ! feels more consistent. On the other hand, I very seldomly use isnt and != in CoffeeScript, much thanks to unless

a.'prop-name' is concerning to me... allowing more ways to access properties makes grepping harder, and I don't really see the advantage. Though I generally find that style dynamic property access to be something of an antipattern, so partly I just don't want to make it any easier than it has to be.

I’m not sure I follow along here, but I think it is a nice shortcut. It also gives a nice parity between member access and object literals: Everything allowed as a key is also allowed as member access:

let obj = {
  key: 1,
  'key': 1,
  '${key}': 1,
  [key()]: 1,
  5: 1,
}
obj.key
obj.'key'
obj.'${key}'
obj[key()]
obj.5

Automatic comma insertions would be a top feature for me, at least in the case of newlines. I don't think automatic commas when things are all the same line makes the code more clear.

I’ll think about it a little bit more, but right now I’d say that then automatic comma insertion at the end of lines is in!

Perhaps use when to alias case... break? case-fallthrough looks a bit cumbersome, and case without break seems confusing/misleading. when would signal something different from ES behavior is happening.

This is a tough one. I like how when reads, and that it is very appearant that it is different from JavaScript. At the same time I’m a bit iffy on adding a new keyword just for this. You almost always end your cases with break, so it’d be nice to simply default to that.

In CoffeeScript a common pattern for me is:

for item in items then switch
  when Array.isArray(item)
    break if foo # want to break the for loop!
  when ...
    ...

It’d be nice to simplify break to only deal with loops, but your probably right that we’re departing too much from JavaScript...

What are some of the "tough decisions" that significant whitespace would entail making? (genuine question, I'm sure there are many but I don't know what they are).

For example, how do you pass a multiline function as an argument, and then yet one? This is the setTimeout problem from CoffeeScript:

setTimeout ->
  foo()
, 1000 # That comma is super ugly, IMO, especially if we have automatic comma insertion.

setTimeout (->
  foo()
), 1000 # Not so pretty either

setTimeout
  ->
    foo()
  1000 # Could possibly work, but is it readable?

It would probably be nicer for the Frappe linter to wrap something like ESLint, rather than making the developer use two linters.

That’s probably true. I was a bit tired when I mashed in the last sentences ... what I really meant was that I thinnk it is important to have a Frappe linter, while still being able to benefit from existing linters.

The changes to strings is clearly the biggest departure from JavaScript. I'm not sure I see the advantage in merging ', ", and ```, though I like the proposed newline-related behavior . ES's behavior of backticks-for-templates makes it clear to the developer when a string is a template string, which is a good thing IMO.

This is actually the part of Frappe that I care the most about :) That it is so amazingly difficult to do multiline strings in JavaScript has always bothered me. Therefore I was really excited when I first heard of template string. Unfortunately, though, they suck in my opinion because they don’t strip indentation.

I really like interpolation in regular strings in CoffeeScript, but it is silly to me to restrict it to "-strings only (as mentioned in the readme).

I think that if backwards-compatibility had been of no concern, `` strings would not have been added to JavaScript. Instead I think'and"` would have been modified to allow tags and interpolation. In Frappe we have the possibility to make such a breaking change, which I don’t think many will notice anyway. IMO, this is not a big departure from JavaScript—just some tweaks. I like the KISS principle here: All strings are the same.

Why is it important to you to “make it clear to the developer when a string is a template string”?

The "runtime" recommendation for trailing whitespace is the kind of ugly hack that indicates there's a deeper problem... But maybe just remove that recommendation :-)

Yeah, perhaps that should be removed. The only time I’ve had use for trailing whitespace in strings is when I’ve written test cases for other compilers to test that they strip trailing whitespace correctly! Then I’ve often resorted to that method of adding |s at the end of lines and then .replaceing them away. In test case code performance is not so much of a concern, so that’s OK. But perhaps that example should be removed. It’s just that a ${} looks so clunky.

lydell commented 9 years ago
class Person
  constructor(@name)
  greet() console.log 'Hello ${@name}!'

function log(message)
  console.log

let obj = {
  prop: 12,
  fn(arg) arg * @prop
  foo()
    console.log 'hi'
    console.log 'there'
}
// Perhaps allow dropping parentheses on method calls:
gulp.task 'foo', () =>
  gulp.src('*.js')
    .pipe compiler()
    .pipe minifier
    .pipe gulp.dest('./dist')

array = array
  .filter item => item > 2
  .map item => item * 2
rattrayalex commented 9 years ago

I would say all sounds good to me!

Would a simple rule of "indentation translates into { wrap }" work? ie; anytime there's an indent, it's wrapped in curly braces?

vendethiel commented 9 years ago

greet() console.log 'Hello ${@name}!'

you might not want to open that can of worms.

Would a simple rule of "indentation translates into { wrap }" work? ie; anytime there's an indent, it's wrapped in curly braces?

Another can of worms, and a very dangerous one at that. Some do it (say, stylus has mixed mode), but i'm not sure it's a good idea in general

rattrayalex commented 9 years ago

Asking out of ignorance: why is that a can of worms? eg; what are some scenarios with that maxim that become tricky?

On Sun, Aug 16, 2015 at 5:44 PM ven notifications@github.com wrote:

greet() console.log 'Hello ${@name}!'

you might not want to open that can of worms.

Would a simple rule of "indentation translates into { wrap }" work? ie; anytime there's an indent, it's wrapped in curly braces?

Another can of worms, and a very dangerous one at that. Some do it (say, stylus has mixed mode), but i'm not sure it's a good idea in general

— Reply to this email directly or view it on GitHub https://github.com/lydell/frappe/issues/1#issuecomment-131645766.

vendethiel commented 9 years ago

Asking out of ignorance: why is that a can of worms? eg; what are some scenarios with that maxim that become tricky?

With separation-less function: it makes implicit call ambiguous.

foo bar baz
# is most probably equal to
foo(bar(baz))

# so, what does this do?
foo() bar baz
# foo = -> bar(baz) ??? very surprising

Would a simple rule of "indentation translates into { wrap }" work? ie; anytime there's an indent, it's wrapped in curly braces?

You're just asking for the community to fight over which one to use. There are already many things to nitpick about, it's probably a good idea not to give people such a big choice to make (indent vs braces). Also, some people will go crazy and use both in the same file, making it very, very hard to read.

lydell commented 9 years ago

@vendethiel

greet() console.log 'Hello ${@name}!'

you might not want to open that can of worms.

You misunderstood me. I meant only allowing that in class bodies, to define methods.

vendethiel commented 9 years ago

I still don't like that it looks like implicit calls, but at least it's not ambiguous :).

rattrayalex commented 9 years ago

FWIW I actually side with @vendethiel on the greet() console.log 'Hello &{@name}!' subject.

@vendethiel what if it was strictly whitespace-only, as per python? I don't see why the braces would be optional rather than banned...

class Person
  constructor(@name)
  greet() 
    console.log 'Hello ${@name}!'

function log(message)
  console.log

let obj = 
  prop: 12
  fn(arg) 
    arg * @prop
  foo()
    console.log 'hi'
    console.log 'there'

setTimeout( |>
  console.log("i'm in the future!")
  console.log("me too!")
, 1000)
// or: 
setTimeout( |>
    console.log("i'm in the future!")
    console.log("me too!")
  , 1000
)

... shoouuld be unambiguous... It gets a bit tougher when you can also have function calls without parens, though.

lydell commented 9 years ago

I dislike removing braces for object literals, because:

I’d rather leave them out.

But brace-less significant indentation for blocks might work out the more I think about it. Perhaps the following rules are sane:

  1. Allow to drop commas in arrays, objects and parameter lists at EOL.
  2. Require a newline. No if cond then foo else bar. No function foo() bar(). (But a if cond and a while b are OK.)
  3. You cannot pass an argument after a function literal unless the it started the line or it is surrounded by parentheses:

    foo arg, |result> // No further arguments to `foo` possible
    console.log result
    
    foo arg, (|result>
    console.log result // BTW, unlike CoffeeScript we couldn’t have placed the `)` here.
    ), arg3
    
    foo
    arg
    |result>
      console.log result
    arg2 // leading comma forbidden

It gets a bit tougher when you can also have function calls without parens, though.

Hmm, I don’t think so, because the plan is to only allow function calls without parentheses when used as statements.

rattrayalex commented 9 years ago

Those look pretty good to me.

I think the {a} -> {a: a} syntax, which I love, should still be possible with this, if you follow the strict rule of "indentation always means braces, unless noted otherwise":

let obj = 
  keyAndValAreSame
  key: val
  method() 
    'hello'  // implicit returns should still be possible, no?
  anotherMethod(arg1, arg2)
    arg1 + arg2

would there be another way to interpret the intended? I suppose it makes it impossible to do things like this:

let chainable = 
  otherThing
  .transform()
  .secondTransform()

... which might not be so bad to disallow anyway.

though this should be a clear case for not inserting braces:

let result = myList
  .map(someFunc)

but may become ambiguous, as a user could expect myList to be a function call in the above... only the dot on the next line tells you that's not the case, right? eg, if it was

let result = myFunc
  anotherFunc(someVal)

you would expect that to translate to:

var result = myFunc(anotherFunc(someVal))

which... I'm not sure how I/we should feel about.

Either way, I guess you don't insert the braces when the line ends with (, [, or a variable?

In regards to the Array, I would expect an extra indentation to provide the braces:

let arrayOfObjects = [
    key: val
  ,
    key: val
  ,
    keyAndValAreSame
  ,
  'notAnObj'
]
// would translate to:
var arrayOfObjects = [
  {
    key: val
  }, 
  {
    key: val
  },
  { 
    keyAndValAreSame: keyAndValAreSame
  },
  'notAnObj'
]

Thoughts?

rattrayalex commented 9 years ago

Single-line object literals should probably still have braces, for the reasons you alluded to.

rattrayalex commented 9 years ago

And I think I'd go back on what I said earlier regarding disallowing braces - they should probably be allowed when it aids clarity.

lydell commented 9 years ago

I'm very sure I don't want to allow braceless objects. Let's not discuss that now.

The biggest challenge, IMO, is adding another array item or function argument after a function expression. If we find a good solution there, significant indentation might become a thing. I think that's an important case to solve, since .then of promises takes to functions.

lydell commented 9 years ago

Regarding your implicit return question, no, there won't be implicit return in that case. Only in arrow functions like in JavaScript.

rattrayalex commented 9 years ago

The biggest challenge, IMO, is adding another array item or function argument after a function expression.

Why wouldn't an extra level of indentation work for that? eg;

myCall.funkyThen(|arg> 
    callWithinCallBack()
  , anotherArgToThen
  , |arg2>
    console.log("I'm in a second callback!")
    if (someCond) 
      console.log("I'm in a branch!")
      let someFunc = |>
        hiThere()
        return "hello"
  , arg4
  , arg5
)

// or, without commas:
funcWithCallbacks
  |arg>
    console.log('hi!')
    return 42
  5
  7
    key: value // if there were objects without braces
  |>
    console.log('in another callback')
  |>
    return "wow, callbacks everywhere!"

thoughts?

lydell commented 9 years ago

I don't like the leading commas example, but I really like the comma-less variant. If you really want commas in that case you can wrap the functions in parentheses.

lydell commented 9 years ago

It just struck me that since we have the not in and not instanceof operators, perhaps not is could make sense. Or perhaps that's too weird ...

rattrayalex commented 9 years ago

not is should work, why not! Though I don't think a clearly-documented isnt->!== would confuse too many developers, especially if the surface area of the language is kept to a minimum.

lydell commented 9 years ago

I've made up my mind about is not now. If we don't choose that, the issue about is not not being the same as !== will always remain, no matter if we choose isnt or not is or whatever. So we're gonna go with isnt—but document the difference with is not very clearly and explain why is not works in python but not in Frappe.

rattrayalex commented 9 years ago

makes sense to me!

On Thu, Aug 20, 2015 at 10:22 PM Simon Lydell notifications@github.com wrote:

I've made up my mind about is not now. If we don't choose that, the issue about is not not being the same as !== will always remain, no matter if we choose isnt or not is or whatever. So we're gonna go with isnt—but document the difference with is not very clearly and explain why is not works in python but not in Frappe.

— Reply to this email directly or view it on GitHub https://github.com/lydell/frappe/issues/1#issuecomment-133290715.

lydell commented 9 years ago

@rattrayalex I’ve pushed some updates to the readme now, based on the discussions in this issue.

vendethiel commented 9 years ago

Are you sure you want both significant whitespace and postfixes (if, while, etc)?

lydell commented 9 years ago

Why wouldn’t I?

vendethiel commented 9 years ago

interaction with if / precedence / the usual (with regards to function and everything else)

lydell commented 9 years ago

Simple. Postfixes are statements that wrap another statement.

console.log value / 2 for value of values if values

is equivalent to:

if values
  for value of values
    console.log value / 2
vendethiel commented 9 years ago

make sure to specify the behavior with functions as well

lydell commented 9 years ago

Right, here we go:

add = (a, b) => a + b unless add
// equivalent to
unless add
  add = (a, b) => a + b

While writing the above, though, I realized one thing:

let foo = 5 if bar
// does that mean:
if bar
  let foo = 5
// which means that you cannot use `foo` outside that `if`, or does it mean:
let foo
if bar
  foo = 5
vendethiel commented 9 years ago

That seems like a really surprising behavior, tbh. the fact that the unless is even looser than the function

lydell commented 9 years ago

So you’d rather have

log = arg => console.log arg if arg

compile to

log = arg => { if (arg) console.log(arg) }

?

Might make sense.

vendethiel commented 9 years ago

Yes, I'd.

lydell commented 9 years ago

Thinking about it some more, perhaps we should even disallow that case, because it is not clear what it means. Perhaps we should disallow multiple postfixes on the same line as well. Or add a rule to the linter.

vendethiel commented 9 years ago

disagree on the multiple -- I really like "eat food if edible(food) for food in foods" kind of things

vendethiel commented 9 years ago

and I really dislike that coffee got it wrong

lydell commented 9 years ago

Good example. Agreed, multiple postfixes will be allowed.

rattrayalex commented 9 years ago

I'm not sure how I feel about postfixes, given the ambiguity... if x then y else z is much closer to js's x ? y : z. And if you directly translate from the one to the other then you can disallow the js version, making it easier to use the ? operator (thing?.method()) which I think helps with a number of scenarios postfix would be useful anyway.

I don't have strong feelings here fwiw (though I really do like the ? feature of coffee, kotlin, swift)

rattrayalex commented 9 years ago

I'd be a +1 on Coffee's switch/when implementation. I hadn't actually seen the "naked switch" before, it looks very nice.

lydell commented 9 years ago

I'm not sure how I feel about postfixes, given the ambiguity... if x then y else z is much closer to js's x ? y : z

They have nothing to do with each other. Postfixes are not expressions.

rattrayalex commented 9 years ago

Perhaps I have a poor understanding - my impression was that a postfix was:

let thing = val if cond

as opposed to the non-post-fix version:

let thing = if cond then val

My impression would be that both would evaluate to something like:

let thing = cond ? val : null

lydell commented 9 years ago
a = b if c
// equivalent to:
if c
  a = b

foo() until bar()
// equivalent to:
until bar()
  foo()
forivall commented 9 years ago

i personally prefer

if c then a = b

for oneliners. but then again, I'm working on my own post-coffeescript language.

faustinoaq commented 9 years ago

I read the readme and i like it, Can i help with something?

I like Coffeescript, but this line is true:

    Implicit returns in all functions. Too far away from JavaScript.
lydell commented 9 years ago

@faustinoaq Glad you like it! If you can help? Well, I'm not planning anything for this repo anytime soon – except updating the readme about significant indentation. I've decided on including it now. Feel free to discuss things you like, dislike and would like to see – or even start implementing the language ...

lydell commented 8 years ago

Significant indentation is now a thing.