Skip to content

Instantly share code, notes, and snippets.

@autotelicum
Created August 1, 2011 18:43
Show Gist options
  • Save autotelicum/1118737 to your computer and use it in GitHub Desktop.
Save autotelicum/1118737 to your computer and use it in GitHub Desktop.
CoffeeScript samples for Functional from Oliver Steele
# CoffeeScript samples for Functional from Oliver Steele
# -------------------------------------------------------------------
# Documentation: http://osteele.com/sources/javascript/functional/
# Repository: https://github.com/osteele/functional-javascript
# -------------------------------------------------------------------
# Directly import the Functional library since it is not CommonJS enabled
# In a browser these files must be included before the script
if exports?
#eval require('fs').readFileSync './collection-utils.js', 'utf8'
eval require('fs').readFileSync './to-function.js', 'utf8'
eval require('fs').readFileSync './functional.js', 'utf8'
# Maintain compatibility with both node.js and browsers
show =
if exports?
(obj, depth = 2, showHidden = false, colors = no) ->
util = require 'util'
switch typeof obj
when 'string' then util.print obj, '\n'
when 'function' then util.puts obj.toString()
else util.puts util.inspect obj, showHidden, depth, colors
obj
else
alert
# -------------------------------------------------------------------
# !!! Not advisable !!! but saves a lot of typing, so ...
if exports?
global[name] = Functional[name] for name of Functional
# Override of library function because it depends on window.
# Returns a function identical to this function except that
# it prints its arguments on entry and its return value on exit.
# This is useful for debugging function-level programs.
if exports?
Function::traced = (name = undefined) ->
util = require 'util'
self = this
name ?= self
return =>
show "[#{name} apply(#{this}, #{util.inspect arguments})] #{name} ->"
show self.apply this, arguments
# Rename 'until' to `till` and 'not' to `negate`
# because they clash with keywords in CoffeeScript
(global ? window).till = Functional.until # Resolve naming conflict
(global ? window).negate = Functional.not # Resolve naming conflict
# -------------------------------------------------------------------
# String lambdas
# -------------------------------------------------------------------
# `lambda` creates a function from a string that contains a single
# expression. This function can then be applied to an argument list,
# either immediately:
show 'x+1'.lambda() 2
show 'x+2*y'.lambda() 2, 3
# or (more usefully) later:
square = 'x*x'.lambda()
show square 3
show square 4
# Explicit parameters
# -------------------------------------------------------------------
# If the string contains a `->`, this separates the parameters
# from the body.
show 'x y -> x+2*y'.lambda() 2, 3
show 'y x -> x+2*y'.lambda() 2, 3
# Otherwise, if the string contains a `_`, it's a unary function
# and `_` is name of the parameter:
show '_ -> _+1'.lambda() 2
show '_ -> _*_'.lambda() 3
# Implicit parameters
# -------------------------------------------------------------------
# If the string doesn't specify explicit parameters, they
# are implicit.
# If the string starts with an operator or relation
# besides `-`, or ends with an operator or relation, then its
# implicit arguments are placed at the beginning and/or end:
show '*2'.lambda() 2
show '/2'.lambda() 4
show '2/'.lambda() 4
show '/'.lambda() 2, 4
# '.' counts as a right operator:
show '.x'.lambda() {x:1, y:2}
# Otherwise, the variables in the string, in order of occurrence,
# are its parameters.
show 'x+1'.lambda() 2
show 'x*x'.lambda() 3
show 'x + 2*y'.lambda() 1, 2
show 'y + 2*x'.lambda() 1, 2
# Chaining
# -------------------------------------------------------------------
# Chain -> to create curried functions.
show 'x y -> x+y'.lambda()(2, 3)
show 'x -> y -> x+y'.lambda()(2)(3)
show 'x -> y -> x+y'.lambda()(2)
# Higher-order functions
# -------------------------------------------------------------------
# The `Functional` namespace defines the higher-order functions (functionals):
# `map`, `reduce`, `select`, and a bunch of others.
show Functional.map ((x) -> x+1), [1,2,3]
# Lambda strings are useful as arguments to functionals. Functionals
# convert the string to a function once per call, not once per application.
show Functional.map '_+1', [1,2,3]
# Use functions in the global namespace
show map '+1', [1,2,3]
show map '_.length', 'here are some words'.split(' ')
show select '>2', [1,2,3,4]
show reduce '2*x+y', 0, [1,0,1,0]
show some '>2', [1,2,3,4]
show every '>2', [1,2,3,4]
# `compose` and `sequence` compose sequences of functions
# backwards and forwards, respectively:
show compose('+1', '*2')(1)
show sequence('+1', '*2')(1)
show compose('+1', '*2', '_.length')('a string')
show compose.apply(null, map('x -> y -> x*y+1', [2,3,4]))(1)
# `foldr` (right reduce) could have handled the last example:
show foldr 'x -> y -> x*y+1'.lambda().uncurry(), 1, [2,3,4]
show foldr 'x*y+1', 1, [2,3,4]
# `pluck` and `invoke` turn methods into functions:
show map pluck('length'), ['two', 'words']
show map invoke('toUpperCase'), ['two', 'words']
# `lambda` can do this too:
show map '.length', ['two', 'words']
show map '.toUpperCase()', ['two', 'words']
# and `pluck` and `lambda` can both implement projections:
cities = [['NYC', 'NY'], ['Boston', 'MA']]
show map '_[0]', cities
show map pluck(0), cities
show map pluck(1), cities
show map '_.x', [{x:10, y:20}, {x:15, y:25}, {x:0, y:-5}]
show map pluck('x'), [{x:10, y:20}, {x:15, y:25}, {x:0, y:-5}]
show map pluck('y'), [{x:10, y:20}, {x:15, y:25}, {x:0, y:-5}]
# Functional iteration with `till`:
show till('>10', '*2') 1
show till('>100', 'x*x') 2
show till(negate('<100'), 'x*x') 2
fwhile = compose(till.ncurry(2), negate).uncurry()
show fwhile('<100', 'x*x') 2
# Higher order higher-order programming, and the fusion rule:
show map '_(1)', map '_.lambda()', ['x+1', 'x-1']
show map compose('_(1)', '_.lambda()'), ['x+1', 'x-1']
# Function methods
# Functional attaches a number of methods to `Function`, that are
# useful for functional method chaining and functional-level programming.
# Here are a few.
# Guards
# -------------------------------------------------------------------
# The first expression below (without `guard`) attempts the reciprocal of *all*
# the list items.
# The second expression guards the division so that it's not applied to null.
show map '1/', [1,2,null,4]
show map guard('1/'), [1,2,null,4]
# Double only the even numbers:
xs = [1,2,3,4]
show map guard('2*', negate('%2')), xs
# `filter` creates a list with only the predicated elements,
# while `guard` can be used to replace them by null, but leave
# the indices of the remaining elements unchanged:
show filter '%2', xs
show map guard(Functional.K(null), '%2'), xs
# Replace odd numbers by 'odd'
show map guard(Functional.K('odd'), '%2'), xs
# Or label "even" and "odd":
show map guard(Functional.K('odd'), '%2', Functional.K('even')), xs
# although we could also use any one of these for the last one:
show map curry('o[ix]', ['even', 'odd']).compose('%2'), xs
show map curry('o[i%2]', ['even', 'odd']), xs
show map '["even","odd"][_%2]', xs
# Curry
# -------------------------------------------------------------------
list4 = (a,b,c,d) -> [a,b,c,d]
# `curry` creates a new function that applies the original arguments, and
# then the new arguments:
right = list4.curry 1, 2
show right 3,4
left = list4.rcurry 1, 2
show left 3, 4
# Use `rcurry` ("right curry") to create `halve` and `double` functions from
# `divide`:
divide = (a, b) -> a / b
halve = divide.rcurry 2
double = divide.rcurry 1/2
show halve 10
show double 10
# `ncurry` and `rncurry` wait until they're fully saturated before
# applying the function.
show list4.ncurry(4,1,2)(3)
show list4.ncurry(4,1,2)(3)(4)
show list4.ncurry(4,1,2)(3,4)
show list4.rncurry(4,1,2)(3)
show list4.rncurry(4,1,2)(3,4)
# [r]curry can't do this because it doesn't
# in general know the polyadicity of the underlying function.
# (Sometimes `fn.length` works, but some functions, especially
# constructed functions, don't declare all their arguments, so
# `fn.length` this lies.)
show list4.curry(1,2)(3)
# Partial function application
# -------------------------------------------------------------------
# `curry` is a special case of partial function application.
# `partial` is the general case. `partial` can
# specialize parameters in the middle of the parameter list;
# the `curry` functions have to fill them in from the end.
# `list4` is an unspecialized function that returns an array of its (four) arguments.
# We'll create partially applied (specialized) versions of this function
# below.
list4 = (a,b,c,d) -> [a,b,c,d]
# Specialize the first and third parameters. This creates a new
# function, that interleaves its arguments with the 1 and 2:
finterleave = list4.partial 1,_,2,_
show finterleave 3, 4
# Specialize the outer two parameters, to produce a function whose
# arguments supply the middle two arguments to `list4`:
finners = list4.partial 1,_,_,2
show finners 3, 4
# if not all the parameters are supplied, the result is a function...
show finterleave 4
# ...which can be applied repeatedly until the argument list is saturated:
show finterleave(3)(4)
show finners(_,3)
show finners(_,3)(4)
show finners(3)(4)
show list4.partial(_,_,_,1)(2,_,3)(4)
# Two specializations of String's `replace` method.
# The function replaces vowels in its object with the value of its
# argument:
replaceVowels = "".replace.partial /[aeiou]/g, _
# This is a method, so use `call` to invoke it on an object:
show replaceVowels.call "change my vowels to underscores", '_'
# The second function replaces slices that match the pattern of its argument,
# with 'th':
replaceWithCoronalFricatives = "".replace.partial _, 'th'
str = "substitute my esses with tee-aitches"
show replaceWithCoronalFricatives.call str, /s/g
# The syntax for partials is meant to suggest the hyphen placeholders
# in abstract algebra:
# Hom(F-, -) = Hom(-, G_)
# This isn't unification or pattern-matching, though. All the hyphens
# have to be at the top level (as arguments, not items in lists, etc.).
# And there's no facility for binding two hyphens to the same parameter
# position.
# Methods on Function are object-free functions too
# -------------------------------------------------------------------
# The higher-order methods on `Function` are also available as
# functions, that take a function as their first argument. These
# functions are in the `Functional` namespace. `Functional.install`
# also installs these functions in the global namespace.
#
# Unlike the methods on Function, these functions can be applied to
# string lambdas too:
show '+'.lambda().curry(1)(2)
show curry('+', 1)(2)
show Function.bind('-> this', 1)()
# Using Functional with Prototype
# -------------------------------------------------------------------
# Prototype defines a larger set of collection functions than
# Functional, and attaches them to Array so that they can
# be chained.
# Invoke `lambda` on a string to create a function for Prototype:
show [1, 2, 3].map 'x*x'.lambda()
show [1, 2, 3].map('x*x'.lambda()).map('x+1'.lambda())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment