Created
August 1, 2011 18:43
-
-
Save autotelicum/1118737 to your computer and use it in GitHub Desktop.
CoffeeScript samples for Functional from Oliver Steele
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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