Binary Line Starts - caffeine-suite/caffeine-script GitHub Wiki

This is one of CaffeineScript's primary strategies for eliminating bracket-matching.

Examples

turn left || right
&& dont worry
# turn(left || right) && dont(worry)
url || defaultUrl
.split :/
# (url || defaultUrl).split("/")
ageInDays = (epochSeconds) ->
  Date.now() / 1000 - epochSeconds
  / 60 * 60 * 24

# JavaScript:
# let ageInDays = function(epochSeconds) {
#   return (Date.now() / 1000 - epochSeconds) / (60 * 60 * 24);
# };

Benefits

The ability to start a line with a dot (.) or other binary operator reduces syntax and increases readability.

CaffeineScript's binary-operator-line-starts are:

  • Predictable
  • Editable - changing the order of lines has a straightforward result
  • Reduces the need for ()s
  • Works after all statements and expressions

Two Simple Rules

1. Start a line with a Binary or Dot Operator

Starting a line with a binary operator applies the operator to the result of the previous line. Think of it as if the previous line had ()s around the whole line.

a + b
* c + d
# (a + b) * (c + d)

2: Indenting AND Starting a line with a Binary or Dot Operator

When you start an indented line with a binary operator, it acts as if there was no newline. Example:

a + b
  * c + d
# a + (b * (c + d))

Note

In both cases, if the line-start-operator is Binary, everything to the right of the line-start-operator is considered to be wrapped in parentheses.

Dot-Line-Starts

# CaffeineScript
new MyClass
.foo()
// JavaScript
new MyClass().foo();

Indented Dot-Line-Starts

# CaffeineScript
new MyClass
  .getSomeOtherClass()
// Javascript
new MyClass.getSomeOtherClass();

Mixed Indented and Non-Indented Dot-Line-Starts

# CaffeineScript
new MyClass
  .a 1
  .b 2
.foo "hi"
.bar "bye"
// Javascript
(new MyClass.a(1).b(2)).foo("hi").bar("bye");

With Control Statements

It works after 'if' and all other kinds of control statements.

# CaffeineScript
if foo
  foo 1
else
  1
.myMethod 123
// Javascript
(foo ? foo(a) : 1).myMethod(123);

With Assignment

foo = bar
  .fud()
# foo = bar.fud();

And the other way around:

foo = bar
.fud()
# (foo = bar).fud();

To capture the result of a more complex computation, leverage the fact that '=' can take a block for its right-hand-side value (i.e. its "r-value"):

foo = 
  bar + baz
  .bud()
# foo = (bar + baz).bud();

Other Binary Operators

The exact same logic applies to all binary operators.

# CaffeineScript - 15 tokens
encodedBitmap
|| file && readAsBinaryString file
|| sourceUrl && RestClient.get sourceUrl
  || defaultUrl
// Javascript - 24 tokens
encodedBitmap ||
  (file && readAsBinaryString(file)) ||
  (sourceUrl && RestClient.get(sourceUrl || defaultUrl));

Binary Operator Line Ends

When a line ends with a binary operator, one of two things can follow:

  • an unindented expression is interpreted as if there was no new-line
  • a value-block returns a single value or, if more than one value is provided, an implicit array

NOTE: You cannot end a line with a dot (.).

a || b ||
c
# a || (b || c) 

a || b ||
  c
# a || (b || c) 

a || b ||
  c
  d
# a || (b || [c, d]) 

Beneficial for Refactoring

It's easier to refactor the order of a chain of lines starting with binary operators (CaffeineScript) rather than lines ending with binary operators (CoffeeScript). Usually the first line is fixed at the start of a chain of lines. It provides the anchor and context for the other lines. The lines after the first are more likely to need their order changed in some future refactor. Line-start-binary-operators let you re-order all but the first line easily. Line-end-binary-operators let you re-order all lines but the last-line - which I have found less helpful.