Skip to content

adqm/coffeescript-for-python

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Logo: Python snake in CoffeeScript cup CoffeeScript for Python Programmers

See https://edemaine.github.io/coffeescript-for-python/ for a better-formatted version of this document. {: .github-only}

CoffeeScript is a programming language whose syntax is clearly designed to match much of Python (with additional inspirations from Perl, ECMAScript, Ruby, etc.). But most documentation for learning CoffeeScript assumes knowledge of JavaScript (which CoffeeScript compiles to), which is far messier.

This guide attempts to teach CoffeeScript to someone fluent in just Python (such as the typical MIT sophomore), showing the slight tweaks needed to convert Python code into CoffeeScript code. The goal is to make it easy for someone to learn CoffeeScript as a second language after Python, without first learning JavaScript, thereby enabling a Python programmer to also make cool web applications (for example). This should also make it easier to later learn JavaScript as a third language (as the CoffeeScript compiler and documentation provides countless examples relating the two).

This guide is still a work-in-progress, and is not yet complete. Feel free to submit an issue for anything you find lacking or confusing.

Why CoffeeScript instead of Python?

Both Python and CoffeeScript are great languages. The main reason to prefer CoffeeScript is that it compiles to JavaScript, resulting in several advantages:

  1. It can run in any web browser, making it easy to distribute your software for people to play with: just embed it in a web page, and anyone can run it on their desktop computer or smartphone. (This feature is also especially important for web development.) It can also run stand-alone/from a command line (like Python) via Node, which is now the basis for many web servers as part of a complete "web application".
  2. When running in a browser, you gain access to many powerful GUI features in the browser, notably HTML/CSS (e.g., buttons), SVG (vector 2D graphics), Canvas (raster 2D graphics), and WebGL (3D graphics). This makes it really easy to do complex interactive graphics.
  3. It is much faster: Node runs typical CoffeeScript 2-5x faster than the equivalent Python (though this gap narrows if you use PyPy). This performance boost is thanks to extensive optimization, such as just-in-time compilation, because of the intense interest in web applications.

Major Similarities and Differences

Both Python and CoffeeScript share many features:

They also have some major differences (some better for Python and some better for CoffeeScript):

  • CoffeeScript requires less punctuation, and relies even more on indentation:
  • Variables have different scope: every variable in CoffeeScript is as if it was declared nonlocal in Python. This makes it easier to access variables in enclosing scopes, but also easier to accidentally re-use variables.
  • The typing systems differ: CoffeeScript uses prototype object orientation, while Python uses multiple-inheritance object orientation. The main practical difference is that multiple inheritance is not supported by CoffeeScript. In principle, it's also easier to create "classes" in CoffeeScript because every object can act as a class.
  • lambda-style inline functions can be multiple lines in CoffeeScript, making mixed imperative/functional programming even easier. On the other hand, CoffeeScript functions do not support keyword arguments.
  • The built-in types differ substantially, e.g., their method names differ. But for the most part, there is a one-to-one mapping.
  • CoffeeScript has more helpful syntax for a lot of important features, but also misses a few features:
    • There is just one Number type corresponding to Python's float. There are no (big) integers, complex numbers, or rationals.
    • String interpolation, regular expressions, and the equivalent of range have built-in syntax (instead of relying on methods/libraries/functions).
    • There are two slicing operators depending on whether you want to include the final index or not.
    • All comparisons are shallow; no built-in deep comparison support. Truthiness is similarly shallow; e.g., [] is considered true.
    • unless alternative to if not; switch alternative to long if/then/else chains; and if can come at the end of a line;
    • Multiline ifs, while, and for loops are expressions instead of statements, so single statements span multiple lines with full indentation support. (while and for loops helpfully accumulate the list of final values.)
    • Three types of for loops, including cleaner syntax for Python's for i, x in enumerate(...) and for key, value in d.items().
    • Exceptional behavior generally doesn't raise exceptions like it does in Python. For example, d[key] returns undefined when key not in d (instead of raising KeyError); 1/0 returns Infinity (instead of raising ZeroDivisionError); and function calls with incorrect number of arguments work fine (with missing arguments set to undefined and extra arguments discarded, instead of raising TypeError).
    • No dictionary comprehensions.
    • No operator overloading via __special_methods__. No metaclasses.

Quick Reference Guide

At a high level, if you remove all the colons at the end of lines from Python code, you end up with equivalent CoffeeScript code. There are many smaller differences, though, stemming in particular from different built-in types (a consequence of JavaScript). This section aims to document these differences by way of side-by-side examples.

print

The analog of Python's print is CoffeeScript's console.log. Like Python 3, it is just a regular function.

PythonCoffeeScript
# Python 2
print "Hello world", 1+2
# Python 3
print("Hello world", 1+2)
#or
print ("Hello world", 1+2)
console.log "Hello world", 1+2
#or
console.log('Hello world', 1+2)
#INVALID: no space allowed between function and argument paren
#console.log ('Hello world', 1+2)

Comments

Python and CoffeeScript comments are generally the same:

PythonCoffeeScript
# This line is a comment
x = 5  # set x to five
# This line is a comment
x = 5  # set x to five

CoffeeScript also supports block comments (similar to triple-quoted strings in Python), which you should be careful not to trigger by accident:

PythonCoffeeScript
# Some comments
# Some more comments
# Even more comments
###
Some comments
Some more comments
Even more comments
###
### This line is a comment
## This line is a comment

Strings

CoffeeScript string notion is very similar syntax to Python, except in how triple-quoted strings deal with indentation. In addition, strings enclosed with "..." have built-in string interpolation. (If you want something like Python's % operator, try the sprintf-js package.)

The resulting JavaScript String type has many similar methods to Python str, though often with different names.

PythonCoffeeScript
"Hello {}, your age is {}".format(name, age)
'Hello {}, your age is {}'.format(name, age)
"Hello {}, your age is {}".format(name,
  thisYear - birthYear)
"Hello #{name}, your age is #{age}"
#INVALID: 'Hello #{name}, your age is #{age}'
#  #{...} is allowed only in ""s, not ''s
"Hello #{name}, your age is #{thisYear - birthYear}"
s = '''\
hello
world'''
# 'hello\nworld'

s = '''
hello
world
'''
# '\nhello\nworld\n'
s = '''
  hello
  world
'''
# 'hello\nworld' -- common indentation removed
s = '''

  hello
  world

'''
# '\nhello\nworld\n'
'\033'   # 3-digit octal
'\x1b'   # 2-digit hex
'\u001b' # 4-digit hex
'\033'   # 3-digit octal
'\x1b'   # 2-digit hex
'\u001b' # 4-digit hex
'\a' # bell
'\b' # backspace
'\f' # formfeed
'\n' # linefeed
'\r' # carriage return
'\t' # tab
'\v' # vertical tab
'\007' # bell
'\b' # backspace
'\f' # formfeed
'\n' # linefeed
'\r' # carriage return
'\t' # tab
'\v' # vertical tab
'y' in s
'y' not in s
s.startswith('y')
s.endswith('?')
s.find('hi')
s.find('hi', start)
s.rfind('hi')
s.rfind('hi', start)
s.find('hi') >= 0
s.replace('hi', 'bye')
s.replace('hi', 'bye', 1)
s.lower()
s.upper()
s.strip()
s.lstrip()
s.rstrip()
s.split()
s.split(',')
s.split(',', 2)
', '.join(array)
'y' in s
'y' not in s
s.startsWith('y')
s.endsWith('?')
s.indexOf 'hi' # also supports RegExp
s.indexOf 'hi', start
s.lastIndexOf 'hi' # also supports RegExp
s.lastIndexOf 'hi', start
s.includes 'hi'
s.replace 'hi', 'bye'
s.replace 'hi', 'bye', 1
s.toLowerCase()
s.toUpperCase()
s.trim()      # no argument allowed
s.trimStart() # no argument allowed
s.trimEnd()   # no argument allowed
s.split()
s.split(',')
s.split(',', 2)
array.join(', ')
s1 + s2 + s3
s1 + s2 + s3
#or
s1.concat s2, s3
s * 5
s.repeat 5
len(s)
s.length
ord(s)
chr(27)
s.charCodeAt 0
String.fromCharCode 27
str(x)
x.toString()

See also string slicing.

Numbers

JavaScript has just one Number type corresponding to Python's float (IEEE double precision). There are no built-in integers, big integers, complex numbers, or rationals.

PythonCoffeeScript
6.0001
7.0
7e9
6.0001
7
7e9
0b11111111
0o377
255
0xff
0b11111111
0377
255
0xff
int('ff', 16)
parseInt 'ff', 16
float('7e9')
parseFloat '7e9'
str(n)
bin(n)
oct(n)
hex(n)
n.toString()
n.toString(2)
n.toString(8)
n.toString(16)
str(n)
bin(n)
oct(n)
hex(n)
n.toString()
n.toString(2)
n.toString(8)
n.toString(16)
(-b + (b**2 - 4*a*c)**0.5) / (2*a)
(-b + (b**2 - 4*a*c)**0.5) / (2*a)
x // y # integer division
x % y  # mod in [0, y)
x // y # integer division
x %% y # mod in [0, y)
(~a | b) & (c ^ d) << n
(~a | b) & (c ^ d) << n
math.e
math.pi
math.tau
math.inf
math.nan
Math.E
Math.PI
2*Math.PI
Infinity
NaN
round(x)
math.trunc(x)
math.floor(x)
math.ceil(x)
math.sqrt(x)
abs(x)
math.log(x)
math.log(x, base)
math.log1p(x)
math.log2(x)
math.log10(x)
Math.exp(x)
Math.expm1(x)
math.degrees(x)
math.radians(x)
math.cos(x)
math.sin(x)
math.tan(x)
math.acos(x)
math.asin(x)
math.atan(x)
math.atan2(y, x)
math.hypot(x, y)
Math.round x
Math.trunc x
Math.floor x
Math.ceil x
Math.sqrt x
Math.abs x
Math.log x
(Math.log x) / Math.log base
Math.log1p x
Math.log2 x
Math.log10 x
Math.exp x
Math.expm1 x
x * 180 / Math.PI
x * Math.PI / 180
Math.cos x
Math.sin x
Math.tan x
Math.acos x
Math.asin x
Math.atan x
Math.atan2 y, x
Math.hypot x, y # or more args

Functions

CoffeeScript functions automatically return the last expression (if they are not aborted with an explicit return), making the final return optional. All arguments default to undefined unless otherwise specified. Defaults are evaluated during the function call (unlike Python which evalutes them at function definition time).

PythonCoffeeScript
def rectangle(x, y = None):
  if y is None:
    y = x
  return x * y
rectangle = (x, y = x) -> x * y
def f(x, add = 1):
  y = x + add
  return y*y
f = (x, add = 1) ->
  y = x + add
  y*y
add1 = lambda x: x+1
add = lambda x, y=1: x+y
zero = lambda: 0
add1 = (x) -> x+1
add = (x, y=1) -> x+y
zero = -> 0
def callback(x):
  print('x is', x)
  return f(x)
compute('go', callback)
compute 'go', (x) ->
  console.log 'x is', x
  f(x)

In CoffeeScript (like Perl and Ruby), the parentheses in function calls are allowed but optional; when omitted, they implicitly extend to the end of the line. (Similar behavior is possible in IPython via the %autocall magic.) Parentheses are still required for zero-argument function calls and when an argument list ends before the end of the line. In CoffeeScript (unlike Python), there can be no space between the function name and the parentheses.

PythonCoffeeScript
f(5)
#or
f (5)

f(5, 10)
#or
f (5, 10)
f(5)
#or
f 5
# f (5) is technically valid but only by luck
f(5, 10)
#or
f 5, 10
# f (5, 10) is INVALID: no space allowed between function and argument paren
add(5, add1(add1(zero())))
add 5, add1 add1 zero()
add(add1(1), 2)
add add1(1), 2
#or
add (add1 1), 2

CoffeeScript functions do not support keyword arguments. The typical workaround is to use an object for an argument.

PythonCoffeeScript
f(add = 2, x = 10)
f(10, 2)
# no keyword arguments in function calls
def g(x, **options):
  f(x, **options)
g = (x, options) ->
  f(x, options.add)

CoffeeScript allows calling a function with a variable number of arguments, and getting back all remaining arguments, with splats (...):

PythonCoffeeScript
x = [1, 2, 3]
print(*x)
#or
apply(print, x)
x = [1, 2, 3]
console.log ...x
#or
console.log.apply console, x
def add(first, *rest):
  for arg in rest:
    first += rest
  return first
add = (first, ...rest) ->
  for arg in rest
    first += arg
  first

Variable scoping

CoffeeScript has no variable declarations like Python's global and nonlocal. CoffeeScript's only behavior is the equivalent of Python's nonlocal: a variable is local to a function if it is assigned in that function and it is not assigned in any higher scope. An exception is that function arguments are always local to the function, with together with CoffeeScript's do makes it simple to explicitly request Python's default behavior.

PythonCoffeeScript
def f(x):
  def next():
    nonlocal x
    x += 1
    return x
  return [next(), next()]
f = (x) ->
  next = ->
    x += 1  # implicitly return x
  [next(), next()]
def f(x):
  def g(x):
    return -x
  return g(x+1)
f = (x) ->
  g = (x) -> -x
  g x+1
def delay(bits):
  out = []
  for bit in bits:
    def g(): # bit stored in closure
      return bit
    out.append(g)
  return out
delay = (bits) ->
  for bit in bits
    do (bit) -> # force locally scoped bit
      -> bit
def recurse(x, stack = []):
  for item in x:
    stack.append(item)
    recurse(item)
    stack.pop()
recurse(root)
stack = []
recurse = (x) ->
  for item in x
    stack.push item
    recurse item
    stack.pop()
recurse root

if/then/else and switch

CoffeeScript ifs are similar to Python's, except that elif is spelled else if. In addition, CoffeeScript offers unless as a more intuitive if not, and allows a one-line suffixed if (and unless).

PythonCoffeeScript
if x:
  y = 1
else:
  y = 2
if x
  y = 1
else
  y = 2
if error:
  return
if error
  return
#or
return if error
if not ok:
  continue
unless ok
  continue
#or
continue unless ok
y = 1 if x else 2
y = if x then 1 else 2
#or
y =
  if x
    1
  else
    2
if x:
  y = 1
elif z:
  y = 2
else:
  y = 3
if x
  y = 1
else if z
  y = 2
else
  y = 3
y = 1 if x else (2 if z else 3)
y =
  if x
    1
  else if z
    2
  else
    3

Unlike Python, CoffeeScript offers a switch expression as an alternative to if/then/else. switch is especially concise when branching on the same value.

PythonCoffeeScript
if x:
  y = 1
elif z:
  y = 2
else:
  y = 3
switch
  when x
    y = 1
  when z
    y = 2
  else
    y = 3
y = 1 if x else (2 if z else 3)
y =
  switch
    when x
      1
    when z
      2
    else
      3
if x == 0:
  print('zero')
elif x == 1 or x == 2 or x == 3:
  print('small')
elif x in ['hello', 'world']:
  print('hi')
else:
  print('unknown')
switch x
  when 0
    console.log 'zero'
  when 1, 2, 3
    console.log 'small'
  when 'hello', 'world'
    console.log 'hi'
  else
    console.log 'unknown'

while loops

CoffeeScript while loops are roughly identical to Python's, including support for continue and break. Like unless, until is shorthand for while not. In addition, loop is shorthand for while true. Like if and unless, while and until have a one-line suffix form. In addition, while and until loops are expressions, returning an array of the final values.

PythonCoffeeScript
while this or that:
  ...
while this or that
  ...
while items:
  items.pop()
items.pop() while items.length
reversed = []
while items:
  reversed.append(items.pop())
reversed =
  while items.length
    items.pop()
while not done:
  ...
until done
  ...
while True:
  ...
  if done:
    break
loop
  ...
  break if done
line = getNextLine()
while match:
  if not line.strip():
    continue
  ...
  line = getNextLine()
while (line = getNextLine())
  continue unless line.trim().length
  ...

for loops

CoffeeScript for...in loops are similar to Python's, including support for continue and break, plus a concise notation for for...in enumerate(...). In addition, for loops are expressions (like while and until loops), returning an array of the final values.

PythonCoffeeScript
for x in [1, 2, 3]:
  y += x
for x in [1, 2, 3]
  y += x
for i, x in enumerate([1, 2, 3]):
  y += x * i
for x, i in [1, 2, 3]
  y += x * i
out = []
for x in [1, 2, 3]:
  out.append(x * x)
#or
out = [x * x for x in [1, 2, 3]]
out =
  for x in [1, 2, 3]
    x * x
#or
out = (x * x for x in [1, 2, 3])
for x in range(10):
  y += x
for x in [...10]
  y += x
for x in range(5, 10):
  y += x
for x in [5...10]
  y += x

See also comprehensions.

Comprehensions

CoffeeScript array comprehensions are similar to Python's list comprehensions, but written with parentheses instead of brackets and with when instead of if. Unlike Python, they are just a one-line inverted form of a regular for loop (symmetric to the one-line inverted if), and can also be written in the non-inverted multiline form.

PythonCoffeeScript
y = [f(i) for i in x]
#or
y = list(map(f, x))
y = (f i for i in x)
#or
y =
  for i in x
    f i
#or
y = x.map f
y = [f(i) for i in x if condition(i)]
#or
y = list(filter(condition, map(f, x)))
y = (f i for i in x when condition i)
#or
y =
  for i in x when condition i
    f(i)
#or
y =
  for i in x
    continue unless condition i
    f(i)
z = [[f(i,j) for j in y] for i in x]
y = (f i, j for j in y for i in x)
#or
y = ((f i, j for j in y) for i in x)
#or
y =
  for i in x
    for j in y
      f i, j
z = [f(i,j) for i in x for j in y]
y = [].concat ...(f i, j for j in y for i in x)
#or
y = [].concat ...(
  for i in x
    for j in y
      f i, j
)

CoffeeScript lacks dictionary/object comprehensions.

Comparison operators

Most Python comparison/Boolean operators have the same name in CoffeeScript (in addition to offering C-style names). CoffeeScript also supports chained comparisons just like Python. One key difference is that == and != are shallow comparisons, not deep, and [] and {} are considered true in CoffeeScript.

PythonCoffeeScript
True
False
true
false
1+2 == 3  # True
1 < 2 < 3 # True
1+2 == 3  # true
1 < 2 < 3 # true
x == 5 and not (y < 5 or done)
x == 5 and not (y < 5 or done)
b = bool(object)
b = new Boolean object
#or
b = not not object
#or
b = !!object
if items: # check for nonempty list
  process(items)
if items.length: # check for nonempty list
  process(items)
x = [1, 2, 3]
y = [1, 2, 3]
# pointer comparison
x is x    # True
x is y    # False
# deep comparison
x == x    # True
x == y    # True
x = [1, 2, 3]
y = [1, 2, 3]
# pointer comparison
x == x    # true
x == y    # false
# deep comparison
_.isEqual x, x  # true
_.isEqual x, y  # false

Python list / CoffeeScript Array

CoffeeScript arrays include notation similar to Python lists, as well as an indentation-based notion that avoids the need for commas.

The resulting JavaScript Array type has many similar methods to Python list, though often with different names.

PythonCoffeeScript
x = [1, 2, 3]
x = [1, 2, 3]
#or
x = [
  1
  2
  3
]
3 in x
3 not in x
3 in x
3 not in x
len(x)
x.length
x = []
x.append(5)
x.append(10)
x = []
x.push 5, 10
x = [1, 2, 3]
y = [4, 5, 6]
x.extend(y)
x = [1, 2, 3]
y = [4, 5, 6]
x.push ...y
last = x.pop()
first = x.pop(0)
last = x.pop()
first = x.shift()
x.reverse()
x.reverse()
x.sort(key = lambda item: str(item))
x.sort() # sort by string value
min(x)
max(x)
min(a, b)
max(a, b)
Math.min ...x
Math.max ...x
Math.min a, b
Math.max a, b
try:
  i = x.index(a)
except ValueError:
  i = -1
i = x.indexOf a
try:
  i = x.index(a, 5)
except ValueError:
  i = -1
i = x.indexOf a, 5
x + y + z
x.concat y, z

CoffeeScript destructuring assignment is a nice tool for extracting parts of arrays:

PythonCoffeeScript
a, b = b, a
[a, b] = [b, a]
head, *tail = [1, 2, 3]
[head, ...tail] = [1, 2, 3]

See also array slicing.

CoffeeScript has no analog to Python's tuple type, but the same effect of "an unchangable list" can be obtained via Object.freeze:

PythonCoffeeScript
x = (1, 2)
x = Object.freeze [1, 2]

Python dict / CoffeeScript Object

Python has two key/value mechanisms: hasattr/getattr/setattr for object attributes and __contains__/__getitem__/__setitem__ for dictionaries. CoffeeScript has no such asymmetry, making regular Objects a fine substitute for dictionaries.

PythonCoffeeScript
d = {1: 2, 'hello': 'world'}
d = {1: 2, hello: 'world'}
#or
d =
  1: 2
  hello: 'world'
d.get(key)
d[key]
d.get('hello')
d.hello
#or
d['hello']
d.set('hello', 'bye')
d.hello = 'bye'
#or
d['hello'] = 'bye'
del d[key]
delete d[key]
key in d
key of d
for key in d:
  f(key)
for key of d
  f key
# Safer version, in case Object has added properties:
for own key of d
  f key
for key, value in d.items():
for key, value of d
list(d.keys())
list(d.values())
list(d.items())
Object.keys d
Object.values d
Object.entries d
len(d)
Object.keys(d).length
d.setdefault(key, value)
d[key] ?= value
d.setdefault(key, []).append(value)
(d[key] ?= []).push value

CoffeeScript destructuring assignment is a nice tool for extracting parts of objects:

PythonCoffeeScript
a = d['a']
b = d['b']
c = d['c']
c_x = c['x']
{a, b, c: {x}} = d
head, *tail = [1, 2, 3]
[head, ...tail] = [1, 2, 3]

Python dict / CoffeeScript Map

While CoffeeScript objects are a fine substitute for dictionaries, they have a few limitations, most notably, that all keys in objects must be strings. (You can use e.g. numbers as keys, but they get mapped to strings.) The built-in Map type solves this problem, acting more like Python dicts, but their syntax is uglier.

PythonCoffeeScript
d = {1: 2, 'hello': 'world'}
d = new Map [
  [1, 2]
  ['hello', 'world']
]
len(d)
d.size
d.get(key)
d.get key
d[key] = value
d.set key, value
del d[key]
d.delete key
key in d
d.has key
for key, value in d.items():
for [key, value] from d
d.keys()
d.values()
d.items()
d.keys()
d.values()
d.entries()
d.setdefault(key, value)
d.set key, value unless d.has key

Python set / CoffeeScript Set

PythonCoffeeScript
x = set()
x = new Set
x = {1, 2, 3}
x = new Set [1, 2, 3]
# x is a set
5 in x
x.add(5)
x.discard(7)
# x is a set
x.has 5
x.add 5
x.delete 7
# x is a set
if x:
  print len(x), 'elements'
else:
  print 'empty'
# x is a Set
if x.size
  console.log x.size, 'elements'
else
  console.log 'empty'
# x is a set
for item in x:
  print item
# x is a Set
for item from x
  console.log item
iter(x)
x.values()

Slicing and range

CoffeeScript slicing features two notations for the range from i to j: i...j excludes j like Python's i:j, while i..j includes j.

PythonCoffeeScript
list(range(7, 10))
# [7, 8, 9]
[7...10]
#or
[7..9]
for i in range(9999):
  # list not generated
  # (in Python 3)
for i in [0...9999]
  # list not generated
# x is str or list
x[7:10] # 7, 8, 9
# x is String or Array
x[7...10] # 7, 8, 9
#or
x[7..9]   # 7, 8, 9
# x is list
x[7:10] = ['a', 'b']
x.insert(0, 'c')
x.insert(7, 'd')
del x[7]
del x[7:10]
x.clear()
y = x.copy()
# x is Array
x[7...10] = ['a', 'b']
x.unshift 'c'
x[7...7] = ['d']
x[7...7] = []
x[7...10] = []
x[..] = []
y = x[..]
# x is str or list
x[:] # shallow copy
# x is String or Array
x[..] # shallow copy
# x is str or list
x[:-1]
# x is String or Array
x[...-1]

Note that negative numbers behave like Python in slices, but negative numbers behave differently when simply getting an item: x[-1] is equivalent to x['-1'] and will typically return undefined; to access the last element, use x[x.length-1].

Null values

Python has one "null" value, None. CoffeeScript has two, undefined and null. Essentially, undefined is the default initial value for all variables (a notion absent from Python), while null is an explicit null value.

PythonCoffeeScript
x = None
# x is automatically undefined
# explicit setting:
x = undefined
# alternate None-like value:
x = null

CoffeeScript defines an existential ? operator, both a unary form to test for undefined or null, and a binary form to provide alternate (default) values in case of undefined or null:

PythonCoffeeScript
if x is not None:
  ...
if x?
  ...
#equivalent to:
if x != undefined and x != null
  ...
y = 5 if x is None else x
y = x ? 5

CoffeeScript also defines many conditional operators that apply the operator only when the left-hand side isn't undefined or null (and otherwise leaves it alone):

PythonCoffeeScript
try:
  x
except UnboundLocalError:
  x = 5
x ?= 5
# d is a dictionary
d.setdefault(key, value)
# d is an object
d[key] ?= value
d[key] if d is not None else d
d?[key]
if callback is not None:
  callback(value)
callback?(value)
#or
callback? value
if x is not None and hasattr(x, 'set') and x.set is not None:
  x.set(5)
x?.set?(5)
#or
x?.set? 5

Regular expressions

CoffeeScript has built-in Perl-like /.../ syntax for regular expressions, and a triple-slash version ///.../// for multiline regular expressions that ignores whitespace.

PythonCoffeeScript
r = re.compile(r'^Hello (\w+)',
      re.IGNORECASE | re.MULTILINE)
r = /^Hello (\w+)/im
r = re.compile(r'[(\[]*(\d+)/(\d+)/(\d+)[)\]]*')
r = /[(\[]*(\d+)\/(\d+)\/(\d+)[)\]]*/
#or
r = ///
  [(\[]*        # leading brackets
  (\d+) \/ (\d+) \/ (\d+)  # y/m/d
  [)\]]*        # closing brackets
///
def bracket(word):
  return re.compile(r'^[(\[]*' + word + r'[)\]]*')
bracket = (word) ->
  new RegExp "^[(\\[]*#{word}[)\\]]*"
#or
bracket = (word) ->
  /// ^[(\\[]* #{word} [)\\]]* ///
match = r.search(string)
match.group(0) # whole match
match.group(1) # first group
match.start()  # start index
match.string   # input string
match = r.exec string
match[0]     # whole match
match[1]     # first group
match.index  # start index
match.input  # input string
if r.search(string):
if r.test string
for match in re.finditer(r'(pattern)', string):
  match.group(0) # whole match
  match.group(1) # first group
  match.start()  # start index
  match.string   # input string
while (match = /(pattern)/g.exec string)?
  match[0]     # whole match
  match[1]     # first group
  match.index  # start index
  match.input  # input string
out = re.sub(r'pattern', repl, string)
out = string.replace /pattern/g, repl
out = re.sub(r'pattern', repl, string, 1)
out = string.replace /pattern/, repl
out = re.sub(r'(pattern)', r'$(\1) \&', string)
out = string.replace /(pattern)/g, '$$($1) $&'
def replacer(match):
  all = match.group(0)
  group1 = match.group(1)
  index = match.start()
  string = match.string
  ...
out = re.sub(r'(pattern)', r'$(\1) \&', replacer)
out = string.replace /(pattern)/g,
  (all, group1, index, string) ->
    # (unneeded arguments can be omitted)
    ...
out = re.sub(r'(pattern)', r'$(\1) \&', string)
out = string.replace /(pattern)/g, '$$($1) $&'
out = re.split(r'\s*,\s*', string)
out = string.split /\s*,\s*/
out = re.split(r'\s*,\s*', string, limit)
out = string.split /\s*,\s*/, limit

JavaScript regular expression syntax and usage is roughly the same as Python, with some exceptions:

  • JavaScript doesn't support (?P<...>...), (?<=...), (?<!...), (?#...), (?(...)...|...), \A, or \Z in regular expressions.
  • In JavaScript, / needs to be escaped as \/ within /.../. Also, spaces right next to the surrounding /s can confuse the parser, so you may need to write them as [ ] (for example).
  • JavaScript doesn't support flags re.ASCII, re.DEBUG, re.LOCALE, or re.DOTALL. (///.../// is the analog of re.VERBOSE.) You can simulate an re.DOTALL-style . with [^].
  • JavaScript's \d matches just [0-9], and \w matches just [a-zA-Z_], instead of the Unicode notions matched by Python. However, \s matches all Unicode space characters like Python.
  • JavaScript replacement patterns need to use $... instead of \... (and $50 instead of \g<50>), and thus need to have $ escaped as $$. Additional replacement features are $`, which expands to the portion of the string before the match, and $', which expands to the portion of the string after the match.

Classes

CoffeeScript classes behave similar to Python classes, but are internally implemented with prototypes, so do not support multiple inheritence. CoffeeScript provides @ as a helpful alias for this (the analog of self in Python), and @foo as an alias for @.foo.

PythonCoffeeScript
class Point:
  def __init__(self, x, y):
    self.x = x
    self.y = y
  def translate(self, dx, dy):
    self.x += dx
    self.y += dy
  def __str__(self):
    return "({}, {})" % (self.x, self.y)
class Point
  constructor: (@x, @y) ->
  translate: (dx, dy) ->
    @x += dx
    @y += dy
    null  # otherwise, would return @y
  toString: ->
    "(#{@x}, #{@y})"
p = Point(1, 2)
p.translate(3, 4)
print(p)
p = new Point 1, 2
p.translate 3, 4
console.log "#{p}"
#or
console.log p.toString()
# Note: console.log p will not call toString()
trans = p.translate
trans(5, 6)
trans = p.translate
# Note: trans 5, 6 will use this = null
trans.call p, 5, 6
#or
trans = (...args) -> p.translate ...args
trans 5, 6
class PPoint extends Point
  dim = 2
  @classmethod
  def parse(Class, string):
    return Class(*[float(word)
      for word in string.split(",")])
class PPoint extends Point
  @dim: 2
  @parse: (string) ->
    # @ = this is the class in an @method
    new @ ...(parseFloat word
      for word in string.split ","])
print(PPoint.dim)
PPoint.slope = lambda self: self.y / self.x
console.log PPoint::dim
PPoint::slope = -> @y / @x

-> vs. =>

Within methods, use => in place of -> to construct a function with the same value of this (@) as the method creating it.

PythonCoffeeScript
class Accumulator:
  def __init__(self):
    self.value = 0
  def adder(self):
    def add(x):
      self.value += x
    return add
class Accumulator:
  constructor: ->
    @value = 0
  adder: ->
    (x) => @value += x

Exceptions

Exceptions are far less important in CoffeeScript than Python because most invalid operations return special values like NaN or undefined instead of raising an exception.

PythonCoffeeScript
try:
  x = a/b
except ZeroDivisionError:
  if a > 0:
    x = math.inf
  elif a < 0:
    x = math.inf
  else:
    x = math.nan
x = a/b
try:
  x = d[key]
except KeyError:
  x = None
#or
x = d.get(key)
x = d[key]
# f either needs 1 argument
# or needs 0 arguments
try:
  f(argument)
except TypeError:
  f()
# f will ignore excess arguments
f argument
# f either needs 1 argument
# or needs 0 arguments
try:
  f()
except TypeError:
  f(None)
# Missing f argument defaults to undefined
f()

But exceptions still exist, and can be captured in a similar way: CoffeeScript try is similar to Python's, except there is no exception type matching.

PythonCoffeeScript
try:
  f()
except Exception as e:
  ...
finally:
  cleanup()
try
  f()
catch e
  ...
finally
  cleanup()
try:
  f()
except ErrorType1 as e:
  ...
except ErrorType2 as e:
  ...
except Exception as e:
  ...
try
  f()
catch e
  if e instanceof ErrorType1
    ...
  else if e instanceof ErrorType1
    ...
  else
    ...
raise new RuntimeError('Oops!')
throw new Error 'Oops!'

See JavaScript's built-in Error types.

Generator functions

CoffeeScript generator functions are roughly identical to Python's, except that looping over generators requires for...from instead of for...in.

PythonCoffeeScript
def positive_ints():
  n = 0
  while True:
    n += 1
    yield n
positive_ints = ->
  n = 0
  loop
    n += 1
    yield n
for i in positive_ints():
for i from positive_ints()

Asynchronous functions

CoffeeScript async functions are similar to Python's (which coevolved to be similar to JavaScript's), except that there's no need to declare a function async; like generator functions, any function with an await keyword is automatically async.

PythonCoffeeScript
async def process(db):
  data = await db.read('query')
  return f(data)
process = (db) ->
  data = await db.read 'query'
  f data
async def fast():
  return 'done'
fast = ->
  await 'done'
async def slow():
  print('hello')
  await asyncio.sleep(1)
  print('world')
sleep = (seconds) ->
  new Promise (resolve) ->
    setTimeout resolve, seconds * 1000
slow = ->
  console.log 'hello'
  await sleep 1
  console.log 'world'

Installation / Getting Started

To install CoffeeScript on your machine, first install NodeJS. (LTS = Long Term Support is a good choice.) Or install NodeJS with a package manager. This will install (at least) two commands, node and npm.

Then run the following command:

npm install --global coffeescript

You should then have a command coffee that runs the interactive interpreter (similar to python). You can also compile a CoffeeScript file filename.coffee into a JavaScript file filename.js via

coffee -c filename.coffee

Package Management (on Node)

The analog of PyPI (Python Package Index) is NPM (Node Package Manager). The analog of command-line tool pip is npm.

Unlike PyPI, NPM packages are usually installed locally to each project, which makes it easy for different projects to use different versions of the same package. To get started, run

npm init

This will ask questions for the creation of a stub package.json file.

Then you can install packages local to your project using npm install. For example, to install the underscore package (written by the same author as CoffeeScript), run

npm install underscore

This will install the package in node_packages/underscore, and change package.json to note which version you depend on.

You can use a package installed in this way via

_ = require 'underscore'

It's also easy to create your own packages and publish them for others to use.

About

This document is by Erik Demaine. The top image is based on the CoffeeScript logo and this free Python clipart.

About

CoffeeScript for Python programmers (a guide)

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Ruby 79.0%
  • CSS 13.7%
  • HTML 7.3%