_ = require 'underscore'
{ok, throws, deepEqual} = require 'assert'
eq = deepEqual
Using
apply
, I can create a neat little function namedsplat
that just takes a function and returns another function that takes an array and calls the original withapply
, so that its elements serve as its arguments.
splat = (f) ->
(args) -> f.apply(null, args)
sum = splat (x, y) -> x + y
ok sum([1, 2]) is 3
My variant:
splat = (f) ->
(args) -> f args...
sum = splat (x, y, z) -> x + y + z
ok sum([1, 2, 3]) is 6
Getting off track:
Array::sum = -> @reduce ((a, b) -> a + b), 0
ok [1, 2, 3].sum() is 6
We can create a function
unsplat
that works opposite fromsplat
, taking a function and returning another function that takes any number of arguments and calls the original with an array of the values given:
unsplat = (f) ->
->
args = _.toArray(arguments) # Array.prototype.slice.call(arguments)
f.call(null, args)
join = unsplat (array) -> array.join ' '
ok join(1, 2) is '1 2'
ok join('a', 'b', 'c') is 'a b c'
My variant:
unsplat = (f) ->
(args...) -> f args
join = unsplat (array) -> array.join ' '
ok join(1, 2) is '1 2'
ok join('a', 'b', 'c') is 'a b c'
fail = (msg) -> throw new Error msg
warn = (msg) -> console.log "warning: #{msg}"
note = (msg) -> console.log "note: #{msg}"
isIndexed = (x) -> _.isArray(x) or _.isString(x)
The function
isIndexed
is also a function providing an abstraction over checking if a piece of data is a string or an array. Building abstraction on abstraction leads to the following complete implementation ofnth
:
nth = (arr, i) ->
fail('index should be an integer') if not _.isNumber(i)
fail('not supported on non-indexed types') if not isIndexed(arr)
fail('index value out of bounds') if i not in [0..arr.length]
arr[i]
ok nth('abc', 0) is 'a'
nums = [1..3]
throws (-> nth(nums, 6)), /out of bounds/
throws (-> nth(1000, 1)), /not supported/
throws (-> nth('abc', 'i')), /index should be an int/
second = (arr) -> nth(arr, 1)
ok second('ab') is 'b'
throws (-> second(1000)), /not supported/
numerically = (x, y) ->
status = 0 # x = y
status = -1 if x < y
status = 1 if x > y
status
numeric = (x, y) -> x >= y # or even `x - y`
unsorted = [2, 3, -1, -6, 0, -108, 42, 10]
expected = [-108, -6, -1, 0, 2, 3, 10, 42]
sorted = unsorted.sort numerically
eq sorted, expected
sorted = unsorted.sort numeric
eq sorted, expected
comparator = (pred) ->
(x, y) ->
status = 0 # x = y
status = -1 if pred(x, y)
status = 1 if not pred(x, y)
status
sorted = unsorted.sort comparator((x, y) -> x <= y)
eq sorted, expected
table = '''
NAME AGE HAIR
Bob 64 brown
Ann 35 red
'''
csv = table.split(/\ +/).join(',')
Our csv
input:
NAME,AGE,HAIR
Bob,64,brown
Ann,35,red
A small function to parse this very constrained CSV representation stored in a string is implemented as follows:
parse = (csv) ->
rows = csv.split '\n'
pushRowSplits = (memo, next) ->
memo.push(next.split(','))
memo
rows.reduce(pushRowSplits, [])
expected = [
[ 'NAME', 'AGE', 'HAIR' ] # header
[ 'Bob', '64', 'brown' ]
[ 'Ann', '35', 'red' ]
]
eq parse(csv), expected
My variant:
parse = (csv) ->
row.split(',') for row in csv.split '\n'
table = parse(csv)
eq table, expected
data = _.rest(table).sort()
expected = [
[ 'Ann', '35', 'red' ]
[ 'Bob', '64', 'brown' ]
]
eq data, expected
Since I know the form of the original data, I can create appropriately named selector functions to access the data in a more descriptive way:
names = (data) ->
data.map _.first
ages = (data) ->
data.map second
hair = (data) ->
data.map (row) -> nth(row, 2)
eq names(data), ['Ann', 'Bob']
eq ages(data), ['35', '64']
eq hair(data), ['red', 'brown']
merged = _.zip names(data), ages(data)
expected = [
[ 'Ann', '35' ]
[ 'Bob', '64' ]
]
eq merged, expected
existy = (x) -> x?
truthy = (x) -> x? and x != false
ok existy(x) for x in [1, 'q', false, 0, '']
ok truthy(x) for x in [1, 'q', true, 0, '']
ok not existy(x) for x in [null, undefined]
ok not truthy(x) for x in [null, undefined, false]
result = [null, undefined, 1, 2, false].map(existy)
expect = [false, false, true, true, true]
eq result, expect
result = [null, undefined, 1, 2, false].map(truthy)
expect = [false, false, true, true, false]
eq result, expect
It’s sometimes useful to perform some action only if a condition is true and return something like undefined or null otherwise. Using
truthy
, I can encapsulate this logic in the following way:
callIf = (cond, f) -> f() if cond
_exec = (target, name) ->
cond = target?[name]
callIf cond, -> _.result(target, name)
eq _exec([1..3], 'reverse'), [3, 2, 1]
eq _exec(foo: 7, 'foo'), 7
eq _exec(foo: 7, 'bar'), undefined