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.
Both Python and CoffeeScript are great languages. The main reason to prefer CoffeeScript is that it compiles to JavaScript, resulting in several advantages:
- 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".
- 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.
- 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.
Both Python and CoffeeScript share many features:
- Code block nesting is based on indentation.
- Variables don't need to be declared; you just assign a value.
- Everything is an object and has a type, but you don't need to declare the types of variables.
- Imperative programming plus strong functional programming support.
- Self-resizing arrays and dictionaries are powerful native types.
- Numbers,
strings,
regular expressions,
slicing,
comparisons with chaining,
and
/or
/not
,if
,while
,for...in
, list comprehensions, generator functions andyield
, asynchronous functions andawait
, exceptions, and many other features are all very similar.
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:
- no colons to start code blocks,
- optional braces around dictionary literals,
- optional quotes around dictionary keys,
- optional commas between list items, and
- optional parentheses in function calls.
- 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'sfloat
. 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 toif not
;switch
alternative to longif
/then
/else
chains; andif
can come at the end of a line;- Multiline
if
s,while
, andfor
loops are expressions instead of statements, so single statements span multiple lines with full indentation support. (while
andfor
loops helpfully accumulate the list of final values.) - Three types
of
for
loops, including cleaner syntax for Python'sfor i, x in enumerate(...)
andfor key, value in d.items()
. - Exceptional behavior generally doesn't raise exceptions
like it does in Python.
For example,
d[key]
returnsundefined
whenkey not in d
(instead of raisingKeyError
);1/0
returnsInfinity
(instead of raisingZeroDivisionError
); and function calls with incorrect number of arguments work fine (with missing arguments set toundefined
and extra arguments discarded, instead of raisingTypeError
). - No dictionary comprehensions.
- No operator overloading via
__special_methods__
. No metaclasses.
- There is just one
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.
The analog of Python's print
is CoffeeScript's
console.log
.
Like Python 3, it is just a regular function.
Python | CoffeeScript |
---|---|
# 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) |
Python and CoffeeScript comments are generally the same:
Python | CoffeeScript |
---|---|
# 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:
Python | CoffeeScript |
---|---|
# 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 |
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.
Python | CoffeeScript |
---|---|
"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.
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.
Python | CoffeeScript |
---|---|
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 |
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).
Python | CoffeeScript |
---|---|
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.
Python | CoffeeScript |
---|---|
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.
Python | CoffeeScript |
---|---|
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 (...
):
Python | CoffeeScript |
---|---|
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 |
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.
Python | CoffeeScript |
---|---|
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 |
CoffeeScript if
s
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
).
Python | CoffeeScript |
---|---|
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.
Python | CoffeeScript |
---|---|
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' |
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.
Python | CoffeeScript |
---|---|
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
... |
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.
Python | CoffeeScript |
---|---|
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.
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.
Python | CoffeeScript |
---|---|
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.
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.
Python | CoffeeScript |
---|---|
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 |
CoffeeScript arrays include
notation similar to Python list
s, 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.
Python | CoffeeScript |
---|---|
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:
Python | CoffeeScript |
---|---|
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
:
Python | CoffeeScript |
---|---|
x = (1, 2) |
x = Object.freeze [1, 2] |
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
Object
s a fine substitute for dictionaries.
Python | CoffeeScript |
---|---|
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:
Python | CoffeeScript |
---|---|
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] |
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 dict
s,
but their syntax is uglier.
Python | CoffeeScript |
---|---|
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 | CoffeeScript |
---|---|
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() |
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
.
Python | CoffeeScript |
---|---|
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]
.
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.
Python | CoffeeScript |
---|---|
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
:
Python | CoffeeScript |
---|---|
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):
Python | CoffeeScript |
---|---|
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 |
CoffeeScript has built-in Perl-like /.../
syntax for regular expressions,
and a triple-slash version ///...///
for multiline regular expressions
that ignores whitespace.
Python | CoffeeScript |
---|---|
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
, orre.DOTALL
. (///...///
is the analog ofre.VERBOSE
.) You can simulate anre.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.
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
.
Python | CoffeeScript |
---|---|
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 |
Within methods, use =>
in place of ->
to construct a function with
the same value of this
(@
) as the method creating it.
Python | CoffeeScript |
---|---|
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 are far less important in CoffeeScript than Python because
most invalid operations return special values like NaN
or undefined
instead of raising an exception.
Python | CoffeeScript |
---|---|
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.
Python | CoffeeScript |
---|---|
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.
CoffeeScript generator functions
are roughly identical to Python's, except that
looping over generators requires for...from
instead of for...in
.
Python | CoffeeScript |
---|---|
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() |
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
.
Python | CoffeeScript |
---|---|
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' |
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
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.
This document is by Erik Demaine. The top image is based on the CoffeeScript logo and this free Python clipart.