Upgrade to Pro — share decks privately, control downloads, hide ads and more …

CoffeeScript - Rescuing JS from the accidents o...

CoffeeScript - Rescuing JS from the accidents of its birth

Javascript has a beautiful essence - closures, prototypes and dyaniamism - which is obscured by accidents - WATs, verbose syntax and design mistakes.

Coffeescript keeps the essence and drops the accident as a thin layer over JS. You can even hack the language to create your own variant.

Tim Ruffles

October 17, 2011
Tweet

More Decks by Tim Ruffles

Other Decks in Programming

Transcript

  1. ESSENCE: var BankAccount = function(balance) { return function(cmd,amount) { amount

    || (amount = 0); if(cmd == "withdraw") return BankAccount(balance - amount) else if(cmd == "deposit") return BankAccount(balance + amount) else if(cmd == "balance") return balance.toFixed(2) else return "What kind of person would try to " + cmd + " a bank account?" } }; var BankAccount = function(balance) { balance || (balance = 0); return function(cmd,amount) { amount || (amount = 0); if(cmd === "withdraw") return BankAccount(balance - amount) else if(cmd === "deposit") return BankAccount(balance + amount) else if(cmd === "balance") return balance.toFixed(2) else return "What kind of person would try to " + cmd + " a bank account?" } }; λ
  2. ACCIDENT var BankAccount = function(balance) { balance || (balance =

    0); return function(cmd,amount) { amount || (amount = 0); if(cmd === "withdraw") return BankAccount(balance - amount); else if(cmd === "deposit") return BankAccount(balance + amount); else if(cmd === "balance") return balance.toFixed(2); else return "What kind of person would try to " + cmd + " a bank account?"; } };
  3. ESSENCE BankAccount = (balance = 0) -> (cmd, amount =

    0) -> if cmd == "withdraw" then BankAccount balance - amount else if cmd == "deposit" then BankAccount balance + amount else if cmd == "balance" then balance.toFixed 2 else "What kind of person would try to #{cmd} a bank account?"
  4. var winners = function(race) { race || (race = "today's

    race"); return "Results of " + race ":\n" + [].slice.call(arguments,1).join("\n"); }; var keywordArgs = function(args) { foo = args.foo; bar = args.bar; }; winners = (race = "today's race",winners...) -> "Results of #{race}:\n#{winners.join("\n")}" keywordArgs = ({foo,bar}) ->
  5. The Zen of Python, PEP: 20 Beautiful is better than

    ugly. Explicit is better than implicit. Simple is better than complex. Readability counts. There should be one obvious way to do it.
  6. // null guard possiblyNull && possiblyNull.foo; // chained comparison var

    valid = foo > 5 && foo < 10; wasDisplayed = this.displayed; this.displayed = this.hidden; this.hidden = wasDisplayed; possiblyNull?.foo valid = 5 < foo < 10 [@hidden,@displayed] = [@displayed,@hidden]
  7. var transformed = []; for (var index=0, length = listOfThings.length;

    index < length; index++) { transformed.push(transform(listOfThings[index])); }; var squared = list.map(function(item) { return item * item; }); transformed = (transform item for item in listOfThings) squared = (item * item for item in list) squared = list.map (item) -> item * item
  8. var Widget = function(el) { this.sayHi = function() { this.el.show()

    }; this.el = el; this.el.click(this.sayHi.bind(this)); }; var Widget = function(el) { self = this; this.sayHi = function() { self.el.show() }; this.el = el; this.el.click(this.sayHi); }; Widget = (@el) -> @sayHi = => @el.show() @el.click @sayHi class Widget constructor: (@el) -> @el.click @sayHi sayHi: => @el.show()
  9. var Animal = Actor.extend({ initialize: ... override: function() { this.__super__.override.apply(this,arguments)

    } }); _.extend Animal.prototype, ai.instinctive; dojo.provide("my.actors.Animal"); dojo.declare("my.actors.Animal",[Actor,ai.instinctive],{ constructor: ... override: function() { this.inherited(arguments); } }); var Animal = new JS.Class(Actor,{ init: ... override: function() { this.callSuper(arguments) } }); Animal.include(ai.instinctive);
  10. You can do anything with function in JS, but you

    shouldn’t have to. Brendan Eich
  11. "" == "0" // false 0 == "" // true

    0 == "0" // true false == "false" // false false == "0" // true false == undefined // false false == null // false null == undefined // true " \t\r\n" == 0 // true http://bonsaiden.github.com/JavaScript-Garden/
  12. var values = { foo: 1, bar: 2, }; values.foo

    = 3; var maxSize = 2.pow(8); var alerter = function(value) { alert(value); } (5).times(function() { alerter("hello") }); var greeting = "hello" (10).times(function() { alert(greeting) }); HALTING PROBLEMS
  13. +-----------------------------------------------------+--------------+-------------------+----------+---------------+ | File | Coffee Lines | Coffee Characters |

    JS Lines | JS Characters | +-----------------------------------------------------+--------------+-------------------+----------+---------------+ | app/coffee/plv/accessors.coffee | 17 | 320 | x 1.9 | x 1.8 | | app/coffee/plv/clock.coffee | 30 | 671 | x 1.6 | x 1.6 | | app/coffee/plv/collection.coffee | 38 | 1089 | x 1.9 | x 1.4 | | app/coffee/plv/comparison.coffee | 13 | 192 | x 1.5 | x 1.6 | | app/coffee/plv/config.coffee | 24 | 597 | x 1.3 | x 1.1 | | app/coffee/plv/console.coffee | 8 | 162 | x 1.9 | x 1.7 | | app/coffee/plv/controller.coffee | 1 | 41 | x 3.0 | x 1.5 | | app/coffee/plv/controllers/main.coffee | 12 | 371 | x 1.8 | x 1.2 | | app/coffee/plv/ext/backbone.coffee | 16 | 514 | x 1.9 | x 1.6 | | app/coffee/plv/ext/class.coffee | 10 | 288 | x 3.3 | x 2.0 | | app/coffee/plv/ext/core.coffee | 126 | 2178 | x 1.7 | x 1.6 | | app/coffee/plv/ext/date.coffee | 57 | 1206 | x 1.8 | x 1.5 | | app/coffee/plv/layers/core.coffee | 1 | 67 | x 1.0 | x 1.0 | | app/coffee/plv/model.coffee | 115 | 3194 | x 1.9 | x 1.5 | .... | app/coffee/plv/models/entry_handler.coffee | 10 | 302 | x 1.9 | x 1.2 | | app/coffee/plv/models/event.coffee | 15 | 399 | x 1.8 | x 1.6 | | app/coffee/plv/models/football/point_in_game.coffee | 113 | 2262 | x 1.5 | x 1.4 | | app/coffee/plv/models/football.coffee | 35 | 1272 | x 1.9 | x 1.4 | | app/coffee/plv/models/game.coffee | 2 | 71 | x 1.5 | x 1.3 | ... | app/coffee/plv/polling.coffee | 30 | 560 | x 1.9 | x 1.8 | | app/coffee/plv/procs/boot.coffee | 40 | 1000 | x 1.3 | x 1.1 | | app/coffee/plv/share.coffee | 15 | 702 | x 2.1 | x 1.2 | | app/coffee/plv/spec/factory.coffee | 194 | 4686 | x 1.1 | x 1.2 | | app/coffee/plv/spec/fake_game.coffee | 211 | 5742 | x 1.7 | x 1.2 | | app/coffee/plv/spec/fake_server.coffee | 44 | 2360 | x 2.2 | x 1.3 | | app/coffee/plv/sync.coffee | 12 | 352 | x 2.0 | x 1.3 | | app/coffee/plv/ui.coffee | 42 | 903 | x 1.6 | x 1.7 | | app/coffee/plv/view.coffee | 9 | 299 | x 1.6 | x 1.3 | | app/coffee/plv/views/avatar_picker.coffee | 12 | 385 | x 1.6 | x 1.4 | | app/coffee/plv/views/backup_players.coffee | 5 | 142 | x 1.6 | x 1.3 | | app/coffee/plv/views/backup_toggle.coffee | 25 | 729 | x 1.4 | x 1.6 | | app/coffee/plv/views/big_game.coffee | 71 | 2623 | x 1.3 | x 1.3 | ... | app/coffee/plv/views/loading.coffee | 42 | 1067 | x 1.0 | x 1.3 | | app/coffee/plv/views/main.coffee | 336 | 9290 | x 1.3 | x 1.3 | | app/coffee/plv/views/message.coffee | 39 | 954 | x 1.8 | x 1.6 | | app/coffee/plv/views/nav.coffee | 44 | 931 | x 1.4 | x 1.3 | .... | app/coffee/plv/views/sounds.coffee | 85 | 2239 | x 1.5 | x 1.4 | | app/coffee/plv/views/standings.coffee | 78 | 2501 | x 1.5 | x 1.4 | | app/coffee/plv/views/tutorial.coffee | 81 | 2817 | x 1.7 | x 1.4 | | app/coffee/plv/web.coffee | 18 | 461 | x 2.7 | x 2.2 | +-----------------------------------------------------+--------------+-------------------+----------+---------------+ | Total | 3552 | 98736 | 5563 | 134774 | | Diff | | | + 2011 | + 36038 | +-----------------------------------------------------+--------------+-------------------+----------+---------------+ | Ratios JS/Coffee | | | x 1.566 | x 1.365 | +-----------------------------------------------------+--------------+-------------------+----------+---------------+
  14. +------------------+--------------+-------------------+----------+---------------+ | | Coffee Lines | Coffee Characters | JS

    Lines | JS Characters | +------------------+--------------+-------------------+----------+---------------+ | Total | 3552 | 98736 | 5563 | 134774 | | Diff | | | + 2011 | + 36038 | +------------------+--------------+-------------------+----------+---------------+ | Ratios JS/Coffee | | | x 1.566 | x 1.365 | +------------------+--------------+-------------------+----------+---------------+
  15. HOW DOES IT WORK? Lexer Rewriter Grammar JISON Parser JS

    Coffee ( ) Tokens Tokens Nodes Code Code
  16. $ ./bin/coffee -te 'console.log "hello world"' [IDENTIFIER console] [. .]

    [IDENTIFIER log] [CALL_START (] [STRING "hello world"] [CALL_END )] [TERMINATOR \n] TOKENS
  17. GOAL coffee> 300 <=> 500 -1 coffee> arrogant = {">":

    -> true} { '>': [Function] } coffee> arrogant > Infinity true coffee> [1,4,2,3,5,6,7].map (val) -> val <=> 5 [ -1, -1, -1, -1, 0, 1, 1 ]
  18. LEXER.COFFEE OPERATOR = /// ^ ( ?: [-=]> # function

    + | <=> # our new op | [-+*/%<>&|^!?=]= # compound assign / compare ... # Comparison tokens. -COMPARE = ['==', '!=', '<', '>', '<=', '>='] +COMPARE = ['<=>', '==', '!=', '<', '>', '<=', '>=']
  19. GRAMMAR.COFEE + isComparison: -> + @operator in ['<', '>', '>=',

    '<=', '===', '!==', '<=>'] - code = @first.compile(o, LEVEL_OP) + ' ' + @operator + ' ' + - @second.compile(o, LEVEL_OP) + code = if @isComparison() + utility("comparison") + "(" + @first.compile(o, LEVEL_OP) + ", " + + @second.compile(o, LEVEL_OP) + ", '#{@operator}')" + else + @first.compile(o, LEVEL_OP) + ' ' + @operator + ' ' + + @second.compile(o, LEVEL_OP) if o.level <= LEVEL_OP then code else "(#{code})"
  20. GRAMMAR.COFFEE + comparison: -> """ + function(a,b,op) { + if(!a

    || !b || !a[op]) { + switch(op) { + case "===": + return a === b; + case ">=": ... + case "<=>": + if(a === b) return 0; + return a > b ? 1 : -1; + default: + throw "Unknown operator: " + op; + } + } else { + return a[op](b); + } + } + """
  21. ALL TOGETHER NOW $ ./bin/coffee -et '5 <=> 6' [NUMBER

    5] [COMPARE <=>] [NUMBER 6] [TERMINATOR \n] $ ./bin/coffee -en '5 <=> 6' Block Op <=> Value "5" Value "6" $ ./bin/coffee -epb '5 <=> 6' var __comparison = function(a,b,op) { ... }; __comparison(5, 6, '<=>');
  22. coffee> 300 <=> 500 -1 coffee> arrogant = {">": ->

    true} { '>': [Function] } coffee> arrogant > Infinity true coffee> [1,4,2,3,5,6,7].map (val) -> val <=> 5 [ -1, -1, -1, -1, 0, 1, 1 ]
  23. λ λ Same power, 40% less code Less noise Less

    ?!?! Familiar, clean syntax No need to wait for IE (took 6.3 years for JS 1.5) If you fancy it, add your own language features ;{ {foo:bar,} "\n" == 0