gkz / LiveScript

LiveScript is a language which compiles to JavaScript. It has a straightforward mapping to JavaScript and allows you to write expressive code devoid of repetitive boilerplate. While LiveScript adds many features to assist in functional style programming, it also has many improvements for object oriented and imperative programming.
http://livescript.net
MIT License
2.32k stars 156 forks source link

Implicit arrays look messy #648

Open farzher opened 9 years ago

farzher commented 9 years ago

This is really hard to rewrite as LS, because implicit arrays require do and ... and *

todo.view = function() {
    return m("html", [
        m("body", [
            m("input"),
            m("button", "Add"),
            m("table", [
                m("tr", [
                    m("td", [
                        m("input[type=checkbox]")
                    ]),
                    m("td", "task description"),
                ])
            ])
        ])
    ]);
};

Here's how I wrote it. It's really bad. Am I doing something wrong? Do I have to give up and just wrap my arrays in multiline []

todo.view = ->
  return
    m 'html' do
      * m 'body' do
          * m 'input'
            m 'button' 'Add'
            m 'table' do
              * m 'tr' do
                  * m 'td' do
                      * m 'input[type=checkbox]'
                        ...
                    m 'td', 'task description'
                ...
        ...
igl commented 9 years ago

Am I doing something wrong?

You are adding markup to your code. I still had no one explain the "beauty" of react to me.

Just use brace and brackets when you feel like you need them to properly visually delimit blocks

vendethiel commented 9 years ago

I agree with the sentiment... Just use what you need to be readable when you need it...

farzher commented 9 years ago

I'm just trying every framework.

If there were some sort of do array keyword, this would look pretty nice actually. It says "The argument right here is an array, but I don't want to close it manually, just look at indentation"

todo.view = ->
  return do[]
    m 'html' do[]
      m 'body' do[]
        m 'input'
        m 'button' 'Add'
        m 'table' do[]
          m 'tr' do[]
            m 'td' do[]
              m 'input[type=checkbox]'
            m 'td', 'task description'
farzher commented 9 years ago

I closed this because I didn't think it was important.

But actually, I want someone to tell me why having a do list type keyword is not a good idea.

dead-claudia commented 9 years ago

+1 to this. I ended up giving up and going with ES6 initally because of this same dilemma.

For the meantime, here's a solution I found just now toying with the LiveScript online REPL. I'm only sad about the fact it took this long for me to figure out this solution.

# Arguments to list. The name is arbitrary, short only for
# convenience. It's almost like an array constructor.
a = -> [.. for &]

m 'div' { # Braces are required here
    onclick: -> foo!
    onhover: -> bar!
} a do
    'text'
    m 'button' 'Button!'
    'some more text'
    randomDiv

It's surprisingly versatile and simple. It is its own sugar. The beauty of functional programming.

Here's your specific initial case used as an example (I love LiveScript's implicit commas):

todo.view = ->
    m 'html' a do
        m 'body' a do
            m 'input'
            m 'button' 'Add'
            m 'table' a do
                m 'tr' a do
                    m 'td' a m 'input[type=checkbox]'
                    m 'td' 'task description'

If you'd rather include an extra snippet of JavaScript at first instead, here it is. Performance shouldn't be too much of an issue, either, since it is easily optimized. I think this should be sightly faster and smaller as well. If the call can be a performance bottleneck (as it may be called literally 100+ times from a single event), this might be better as well, as I've added a few optimizations, namely type hints and preallocation (faster for smaller arrays of static length, which is what Mithril accepts). A minified version is also given.

function a() {
    "use strict";
    var len = arguments.length|0;
    var list = Array(len);
    for (var i = 0|0; i < len; i++) {
        list[i] = arguments[i];
    }
    return list;
}
// Minified
function a(){"use strict";for(var e=arguments.length|0,l=Array(e),i=0|0;i<len;i++)l[i]=arguments[i];return l}

<off-topic> The output does seem a little Lispy to me...especially when whitespace is added for readability (the compiler output is a literal one-liner).

// Generated by LiveScript 1.3.1
todo.view = function(){
    return m('html', a(
        m('body', a(
            m('input'),
            m('button', 'Add'),
            m('table', a(
                m('tr', a(
                    m('td', a(
                        m('input[type=checkbox]'))),
                    // Lots of closing parentheses... O.o
                    m('td', 'task description')))))))));
};
; ClojureScript, assuming `m` is properly shimmed.
(defn todo/view []
    (m "html" (list
        (m "body" (list
            (m "input")
            (m "button" "Add")
            (m "table" (list
                (m "tr" (list
                    (m "td" (list (m "input[type=checkbox]")))
                    (m "td" "task description"))))))))))

</off-topic>

farzher commented 9 years ago

That's pretty clever. I don't know why I didn't think of that. Thanks, I can cleanup my mess now. Although this would obviously be better if it were a LiveScript keyword. Then I wouldn't have the silly performance loss.

dead-claudia commented 9 years ago

It isn't really much of one, anyways. The performance loss isn't significant unless you have a _lot_ of dynamic content (like in a game), in which you'll have to use the optimized JS version. A LiveScript keyword could simply change it to produce a static array, which would be the most useful. This is where you profile your code, anyways.

vendethiel commented 9 years ago

* is just void =, which basically creates a block

dead-claudia commented 9 years ago

@vendethiel And it's effectively useful only for aesthetics in my version.

emilis commented 9 years ago

@impinball Excuse a noob, but this thread made me very curious.

Wouldn't

a = -> Array.prototype.slice.call &

be more conventional than:

a = -> [.. for &]

?

dead-claudia commented 9 years ago

In JavaScript or CoffeeScript, yes, but it is a little slower, as I don't know of any engine that fast tracks it. Bluebird and Lodash both do similar. It isn't too much of a problem unless you're dealing with mobile Web development, where it could become a performance problem, since it would be a very frequent call.

This I was solving isn't exactly a common problem in most other languages. I was just looking for a simple sugar, and that's what seemed to work best.

Also, comprehensions in general are more idiomatic in functional languages than method calls, even when OOP exists (like in OCaml). Your version is more idiomatic in JavaScript and CoffeeScript, but wouldn't see a lot of use, since they are a little more imperative, CoffeeScript more so. LiveScript is best used as a functional language, not imperative. The OO parts simply supplement it.

It's equivalent to this:

[arg for arg in arguments]

@impinball https://github.com/impinball Excuse a noob, but this thread made me very curious.

Wouldn't

a = -> Array.prototype.slice.call &

be more conventional than:

a = -> [.. for &]

?

— Reply to this email directly or view it on GitHub https://github.com/gkz/LiveScript/issues/648#issuecomment-72327784.

farzher commented 9 years ago

This is a related issue:

I recently got a bug in my program because I refactored this:

a =
  1
  2

to this:

a = if true
  1
  2
else
  3
  4

Which did not do what I expected...

dead-claudia commented 9 years ago

I generally prefer explicit lists outside of React/Mithril/etc., because it's clearer, anyways. On Feb 16, 2015 6:45 PM, "Stephen Kamenar" notifications@github.com wrote:

This is a related issue:

I recently got a bug in my program because I refactored this:

a = 1 2

to this:

a = if true 1 2 else 3 4

Which did not do what I expected...

— Reply to this email directly or view it on GitHub https://github.com/gkz/LiveScript/issues/648#issuecomment-74592103.

pepkin88 commented 8 years ago

For the record, you can also do it like this:

todo.view = ->
    m 'html' []=
        ...
        m 'body' []=
            m 'input'
            m 'button' 'Add'
            m 'table' []=
                ...
                m 'tr' []=
                    m 'td' []=
                        ...
                        m 'input[type=checkbox]'
                    m 'td', 'task description'

Splats are unfortunately still required, but you can use them directly under the call, so the indents looks better (comparing to the example from the first post).

[]= is not some new syntax, it's just a destructuring assignment without stating a variable, i.e. "anonymous assignment".