Skip to content

Instantly share code, notes, and snippets.

@showell
Created December 22, 2011 22:20
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save showell/1512100 to your computer and use it in GitHub Desktop.
Save showell/1512100 to your computer and use it in GitHub Desktop.
scoping in CoffeeScript
# This code demonstrates how CS scoping works within a file. Scroll down to the end to
# see how cross-file scoping works.
# Here are the rules.
#
# Scoping is all lexical. Read the file from top to bottom to determine variable scopes.
#
# 1) When you encounter any variable in the top-level nesting, its scope is top level.
# 2) Inside a function, if you encounter a variable name that still exists in an outer scope, then that
# variable name refers to the variable in the outer scope. (This is "closure".)
# 3) Inside a function, if you encounter a variable name that does not exist in an inner scope, then a
# new local variable is created, and its scope only gets hoisted to the top of the immediately
# enclosing functions.
# 4) Top level variables go out of scope when you reach the bottom of the file.
# 5) Function-scoped variables go out of scope when you reach the lexical end of the function. (As a
# consequence, two lexically independent functions in a file can reuse common names like "i", "s",
# etc. without side effects.)
# 6) Scoping rules don't change according to casing: bar, Bar, BAR, and $bar all have the same
# rules.
#
# If the rules above are hard to grok, then it's probably best to either experiment yourself or to
# simply learn by example.
BANNER = '\n--------------'
# BASIC SCOPING: independent functions can declare local variables.
#
# The first example should not be surprising to most folks. The variables "a" in f1 are f2
# are not shared.
f1 = ->
console.log BANNER
console.log a # undefined
a = 1
console.log a # 1
# end of a's scope
f2 = ->
console.log BANNER
console.log a # undefined
a = 2
console.log a # 2
# end of a's scope
# f1 and f2 are still in scope
f1() # calls the f1 we defined above
f2() # calls the f1 we defined above
# there is no "a" at outer scope
console.log BANNER
console.log "Is a in scope? #{a?}" # false
# CLOSURES: functions can easily alias variables in outer lexical scope
#
# CS has normal closure behavior
Accumulator = (seed) ->
initial_value = seed * 2
return ->
initial_value += 1 # refers to initial_value above
return initial_value
# If you want to execute a few statements inside a new scope, CS lets
# you create one with its "do ->" idiom. It translates to this is JS:
# (function() { // stuff })();
do ->
# new scope here, so we don't pollute the top level namespace
console.log BANNER
f_incr = Accumulator(5) # Accumulator refers to same Accumulator above
console.log f_incr() # 11
console.log f_incr() # 12
# f_incr falls out of scope
console.log f_incr? # false, out of scope
# CLASSES: "this" is explicit, unlike Ruby self
#
# CS does not conflate local variables with instance variables.
class Person
constructor: (name) ->
this.name = name
add_friend: (name) ->
# name and this.name are two different concepts, obviously
console.log "#{name} and #{this.name} are friends"
do ->
console.log BANNER
person = new Person("alice")
person.add_friend("bob") # bob and alice are friends
# At this point, we are back at top-level scope, and these variables are
# defined:
console.log BANNER # same string as above
console.log f1, f2, Accumulator, Person # [Function] [Function] [Function] [Function: Person]
console.log person? # false, person is not in scope
# SHARED VARIABLES: CS is for consenting adults.
#
# We can define a new variable PLANET that is available at all scopes below it:
PLANET = "Earth"
do ->
console.log BANNER
f = ->
g = ->
h = ->
mars = "Mars" # local variable
# PLANET is already in scope, so it stays in its existing scope.
PLANET = mars # hey, we're tired of Earth
return PLANET
return h
return g
console.log f()()() # Mars
console.log PLANET # Mars (our call to the innermost function was touching the top-level PLANET)
# Back out at top-level scope, f, g, h, and mars no longer exist
console.log f?, g?, h?, mars? # all false
# GOTCHAS??
#
# Try to be tricky, and create subtle coupling at the top level by slyly putting a
# variable into top-level scope without assigning to it first.
try
console.log BANNER
console.log should_be_local # will be undefined
catch e
console.log "We got a ReferenceError, so no subtle bugs: #{e}"
# GOOD PRACTICES: Namespace your top-level variables.
Tree =
root: "thing that goes into the ground to get minerals"
bark: "protective covering"
Dog =
root: -> console.log "dog is rooting around for foot"
bark: ->
bark = "RUFF!!!" # no ambiguity with Dog.bark, this is just a local
console.log bark
# No ambiguity here.
console.log BANNER
console.log Tree.root
console.log Tree.bark
Dog.root()
Dog.bark() # RUFF!!!!
# Hopefully, this covers 99% of the scoping situations you're likely to create in your own codebase.
#
# Some final advice:
# 1) Use descriptive names to avoid unintentional naming collisions.
# 2) Use objects like Tree and Dog to create namespaces.
# 3) Use functions and "do" to create smaller scopes.
# 4) Use naming conventions for top-level variables, especially classes and constants.
# Cross-file scoping.
#
# You don't have to worry about naming collisions between multiple files in CoffeeScript.
# CoffeeScript automatically wraps all files in a function closure, which means all variables
# inside a file, even top level variables, are scoped to the function wrapper.
#
# Of course, there are occasions when you need scoping to cross file boundaries. For very
# small projects, you can use the -b option of the compiler to suppress the function closure
# wrappers, but this is very brittle. The preferred approach is to selectively add variables
# to exports or window. I'm not going to cover that here, because the details of how you
# do that are really more of a Javascript issue than a Coffeescript issue, and the solutions
# largely depend on the size of your project.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment