Skip to content

Instantly share code, notes, and snippets.

@Engelberg
Created December 27, 2013 02:59
Show Gist options
  • Save Engelberg/8142000 to your computer and use it in GitHub Desktop.
Save Engelberg/8142000 to your computer and use it in GitHub Desktop.
Example of how classes can be thought of as a "parameterized namespace".
; This is sort of a pseudo Clojure/Scala mash-up.
; In an OO language, you might start off with the following class as a namespace
; for all your functions that revolve around manipulating 3x4 grids.
class Grid {
val rows = 3
val cols = 4
val cells = for [row in range(rows), col in range(cols)] yield [row,col]
def selectCellsByRow(row) = cells.filter(fn [[r c]] (= row r))
def printCellsByRow(row) = for [cell in selectCellsByRow(row)] println(cell)
}
; When we're using a class as a namespace, we create an instance to access the functions
; For example:
grid = new Grid();
grid.printCellsByRow(3);
; When my problem grows, and now I need to parameterize rows and cols, it's easy:
class Grid(_rows = 3, _cols = 4) {
val rows = _rows
val cols = _cols
val cells = for [row in range(rows), col in range(cols)] yield [row,col]
def selectCellsByRow(row) = cells.filter(fn [[r c]] (= row r))
def printCellsByRow(row) = for [cell in selectCellsByRow(row)] println(cell)
}
; 1. It was a trivial change to make. None of the bodies of my functions change at all.
; 2. All my old code still works, creating a "namespace" that refers to 3x4 grids.
; 3. Now you can mix and match versions of printCellsByRow derived from various grids, for example:
grid5x9 = new Grid(5, 9);
grid5x9.printCellsByRow(row);
; I claim that in Clojure there's no similarly smooth path as a project grows, and no similarly concise way
; to express a bunch of functions sharing the same immutable state.
; In Clojure, a project often begins with a bunch of functions sharing some immutable state or "magic constants" stored in vars.
; At some point, you realize you need to make that immutable state a parameter, shared across multiple functions.
; The only way to do that is to thread some data structure (e.g., a record) as an extra parameter through all the functions.
; All functions must be modified as follows:
; 1. Record added as input to all functions (even those that don't necessarily *use* the record, must take it as an input in order to call functions that require that information)
; 2. Record must be destructured where function needs access to those fields.
; 3. All calls to functions in this namespace must be modified to pass in the intialization information.
; 4. If you want to support previously existing API consumers who already assume a certain default state, you need to take cases in every single one of your exposed functions.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment