diff --git a/monitor-specs b/monitor-specs index d76765e..adf2fc8 100755 --- a/monitor-specs +++ b/monitor-specs @@ -1 +1 @@ -jasmine-node --autotest --coffee --verbose spec \ No newline at end of file +jasmine-node spec --coffee --autotest \ No newline at end of file diff --git a/package.json b/package.json index 57e5b3d..da4e3dd 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "name": "Fuell", "description": "A library for purely declarative functional programming with support for async actions", "keywords": ["functional", "declarative", "async", "library", "haskell", "coffeescript"], - "version": "0.1.1", + "version": "0.1.2", "homepage": null, "repository": { "type": "git", @@ -17,7 +17,6 @@ "test": "jasmine-node spec --coffee" }, "dependencies": { - "HOFU": "0.1.1" }, "devDependencies": { "jasmine-node": "1.0.x" diff --git a/spec/FunctionComposing/Function.spec.coffee b/spec/FunctionComposing/Function.spec.coffee new file mode 100644 index 0000000..faa0bbd --- /dev/null +++ b/spec/FunctionComposing/Function.spec.coffee @@ -0,0 +1,23 @@ +describe "Function", -> + {composableByPositions} = require "../../src/FunctionComposing/Function" + + describe "composableByPositions", -> + it "works", -> + d = [[matches, isEven], [mapped, [sum, 2]]] + f = composableByPositions [0], result + expect(f d, [0, 1, 2, 3]).toEqual [2, 4] + + + + + +sum = (y, x) -> x + y +difference = (y, x) -> x - y +product = (y, x) -> x * y +quotient = (y, x) -> x / y +isEven = (x) -> x % 2 == 0 +isOdd = (x) -> x % 2 == 1 +times = (y, x) -> y for i in [0...x] +matches = (f, xs) -> x for x in xs when f x +mapped = (f, xs) -> f x for x in xs +result = (f, x) -> f x if x? \ No newline at end of file diff --git a/spec/FunctionComposing/FunctionTemplate.spec.coffee b/spec/FunctionComposing/FunctionTemplate.spec.coffee new file mode 100644 index 0000000..93621e5 --- /dev/null +++ b/spec/FunctionComposing/FunctionTemplate.spec.coffee @@ -0,0 +1,49 @@ +describe "FunctionTemplate", -> + {func} = require "../../src/FunctionComposing/FunctionTemplate" + + describe "func", -> + + it "fails when first element is inappropriate", -> + (expect -> func [2, sum, 3]).toThrow() + + it "returns a fully-applied function when all parameters are provided", -> + (expect (func [difference, 17, 3])()).toEqual -14 + (expect (func [product, 3, 6])()).toEqual 18 + + it "returns a partially applied function on lesser arguments", -> + f = func [difference, 12] + (expect f instanceof Function).toBeTruthy() + expect(f 7).toBe -5 + + it "filter on a function works", -> + f = func [matches, isEven] + expect(f [0, 1, 2, 3, 4]).toEqual([0, 2, 4]) + + it "works on nested declarations", -> + f = func [mapped, [sum, 2]] + expect(f [0, 1, 2]).toEqual([2, 3, 4]) + + it "works on uber-nested declarations", -> + f = func [mapped, [mapped, [sum, 3]]] + expect(f [[2, 3], [4]]).toEqual([[5,6], [7]]) + + it "works on nested functions", -> + f = func [[matches, isEven], [mapped, [sum, 2]]] + expect(f [2, 3, 4]).toEqual [4, 6] + expect(f [0, 1, 2, 3]).toEqual [2, 4] + + # it "passes the function unchanged", -> + # expect((func difference) 7, 4).toEqual -3 + + + +sum = (y, x) -> x + y +difference = (y, x) -> x - y +product = (y, x) -> x * y +quotient = (y, x) -> x / y +isEven = (x) -> x % 2 == 0 +isOdd = (x) -> x % 2 == 1 +times = (y, x) -> y for i in [0...x] +matches = (f, xs) -> x for x in xs when f x +mapped = (f, xs) -> f x for x in xs +result = (f, x) -> f x if x? \ No newline at end of file diff --git a/src/Async.coffee b/src/Async.coffee new file mode 100755 index 0000000..faaef68 --- /dev/null +++ b/src/Async.coffee @@ -0,0 +1,3 @@ +exports[name] = require "./Async/#{name}" for name in [ + "Function" +] diff --git a/src/Async/Function.coffee b/src/Async/Function.coffee new file mode 100644 index 0000000..69ebd82 --- /dev/null +++ b/src/Async/Function.coffee @@ -0,0 +1,20 @@ +BasicFunctionComposing = require "../BasicFunctionComposing" + +exports.Function = Function + +exports.async = +async = (f) -> + -> callAsync BasicFunctionComposing.Function.partiallyApplied arguments, f + +exports.callAsync = +callAsync = (f) -> + if process? && process.nextTick then process.nextTick f + else setTimeout f, 0 + return + +exports.action = +action = (f) -> + async (args..., cb) -> + cb f.apply this, args + + diff --git a/src/BasicFunctionComposing.coffee b/src/BasicFunctionComposing.coffee new file mode 100644 index 0000000..ba473f1 --- /dev/null +++ b/src/BasicFunctionComposing.coffee @@ -0,0 +1,3 @@ +exports[name] = require "./BasicFunctionComposing/#{name}" for name in [ + "Function" +] diff --git a/src/BasicFunctionComposing/Function.coffee b/src/BasicFunctionComposing/Function.coffee new file mode 100644 index 0000000..dcb6f53 --- /dev/null +++ b/src/BasicFunctionComposing/Function.coffee @@ -0,0 +1,33 @@ + +exports.Function = Function + +exports.nested = +nested = (container, f) -> + -> container.call this, f.apply this, arguments + +# exports.partiallyApplied = +# partiallyApplied = (args, f) -> +# args = members args unless args instanceof Array +# -> f.apply this, union (members arguments), args + +# union = (ys, xs) -> if not ys then xs else xs.concat ys +# members = (x) -> v for _, v of x + +exports.partiallyApplied = +partiallyApplied = (args, f) -> + -> + args1 = [] + args1.push(arg) for arg in args + args1.push(arg) for arg in arguments + f.apply this, args1 + + +exports.withLength = +withLength = (length, f) -> + args = ("a" + i for i in [0...length]) + body = """ + return function(#{args.join(", ")}) { + return f.apply(this, arguments); + } + """ + (new Function "f", body) f \ No newline at end of file diff --git a/src/Fuell.coffee b/src/Fuell.coffee index 821860b..46a016b 100755 --- a/src/Fuell.coffee +++ b/src/Fuell.coffee @@ -1,3 +1,4 @@ +# TODO: all the composing and async declarations could be done here exports[name] = require "./Fuell/#{name}" for name in [ "Action" "Actions" @@ -5,6 +6,9 @@ exports[name] = require "./Fuell/#{name}" for name in [ "Arrays" "Environment" "Function" + "FunctionByLengthMap" + "FunctionByTypesPairs" + "FunctionTemplate" "Keys" "Map" "Number" diff --git a/src/Fuell/Actions.coffee b/src/Fuell/Actions.coffee index b00ebeb..844c558 100644 --- a/src/Fuell/Actions.coffee +++ b/src/Fuell/Actions.coffee @@ -1,8 +1,8 @@ -HOFU = require "../HOFU" +Async = require "../Async" # deprecated in favor of `parallel` exports.callParallelly = -callParallelly = HOFU.async (fs, cb) -> +callParallelly = Async.Function.async (fs, cb) -> results = [] for f in fs f (result) -> @@ -21,7 +21,7 @@ callConsecutively = (actions, cb) -> else cb() callNext() return - # throw "todo: Actions.callConsecutively" + # throw "Unimplemented: Actions.callConsecutively" diff --git a/src/Fuell/Array.coffee b/src/Fuell/Array.coffee index 06fda18..db2721c 100644 --- a/src/Fuell/Array.coffee +++ b/src/Fuell/Array.coffee @@ -1,5 +1,6 @@ +Async = require "../Async" +FunctionComposing = require "../FunctionComposing" Object = require "./Object" -HOFU = require "../HOFU" exports.Array = Array @@ -9,7 +10,7 @@ ASYNC ACTIONS ### # processBy exports.collect = -collect = HOFU.async (action, xs, cb) -> +collect = Async.Function.async (action, xs, cb) -> zs = [] for x in xs action x, (z) -> @@ -29,7 +30,7 @@ collectResults = (action, xs, cb) -> return exports.each = -each = HOFU.async (action, xs, cb) -> +each = Async.Function.async (action, xs, cb) -> if xs.length > 0 finished = 0 for x in xs @@ -75,17 +76,17 @@ spread = (f, xs) -> [r1, r2] exports.resultPairs = -resultPairs = HOFU.composable (f, keys) -> +resultPairs = FunctionComposing.Function.composable (f, keys) -> [k, f k] for k in keys exports.leftReduction = -leftReduction = HOFU.composable (f, r, xs) -> +leftReduction = FunctionComposing.Function.composable (f, r, xs) -> for x in xs r = f r, x r exports.rightReduction = -rightReduction = HOFU.composable (f, y0, xs) -> +rightReduction = FunctionComposing.Function.composable (f, y0, xs) -> y = y0 for i in [(xs.length - 1)..0] y = f xs[i], y @@ -93,16 +94,16 @@ rightReduction = HOFU.composable (f, y0, xs) -> # not `reduced` since the returned type changes exports.reduction = -reduction = HOFU.composable (f, xs) -> +reduction = FunctionComposing.Function.composable (f, xs) -> rightReduction f, (last xs), (allButLast xs) if xs.length > 0 exports.firstResult = -firstResult = HOFU.composable (f, xs) -> +firstResult = FunctionComposing.Function.composable (f, xs) -> return z for x in xs when (z = f x)? exports.result = -result = HOFU.composable (f, xs) -> +result = FunctionComposing.Function.composable (f, xs) -> ### Get either the same array or null if it contains a null ### @@ -113,33 +114,33 @@ result = HOFU.composable (f, xs) -> # processed exports.results = -results = HOFU.composable (f, xs) -> +results = FunctionComposing.Function.composable (f, xs) -> ### ### z for x in xs when (z = f x)? exports.allMatch = -allMatch = HOFU.composable (f, xs) -> +allMatch = FunctionComposing.Function.composable (f, xs) -> return false for x in xs when not f x true exports.someMatch = -someMatch = HOFU.composable (f, xs) -> +someMatch = FunctionComposing.Function.composable (f, xs) -> return true for x in xs when f x false exports.noneMatch = -noneMatch = HOFU.composable (f, xs) -> +noneMatch = FunctionComposing.Function.composable (f, xs) -> return false for x in xs when f x true exports.matches = -matches = HOFU.composable (f, xs) -> +matches = FunctionComposing.Function.composable (f, xs) -> x for x in xs when f x exports.firstMatch = -firstMatch = HOFU.composable (f, xs) -> +firstMatch = FunctionComposing.Function.composable (f, xs) -> return x for x in xs when f x diff --git a/src/Fuell/Function.coffee b/src/Fuell/Function.coffee index 2a889a5..b2b50e5 100644 --- a/src/Fuell/Function.coffee +++ b/src/Fuell/Function.coffee @@ -1,11 +1,13 @@ Array = require "./Array" Object = require "./Object" Pair = require "./Pair" -HOFU = require "../HOFU" +Set = require "./Set" -exports.Function = Function +exports[k] = v for k, v of require "../BasicFunctionComposing/Function" +exports[k] = v for k, v of require "../FunctionComposing/Function" +exports[k] = v for k, v of require "../Async/Function" -exports[k] = v for k, v of HOFU +exports.Function = Function exports.disposable = disposable = (f) -> @@ -26,21 +28,11 @@ memoized = (f) -> f.cache.push [arguments, r] r -exports.withLength = -withLength = (length, f) -> - args = ("a" + i for i in [0...length]) - body = """ - return function(#{args.join(", ")}) { - return f.apply(this, arguments); - } - """ - (new Function "f", body) f - exports.flipped = flipped = (f) -> (x, y) -> f y, x exports.remapped = remapped = (m, f) -> - throw "Incorrect map" if not Array.empty Array.symmetricDifference m, [0...f.length] + throw "Incorrect map" if not Set.equals [0...f.length], m -> f.apply this, (arguments[i] for i in m) diff --git a/src/Fuell/FunctionByLengthMap.coffee b/src/Fuell/FunctionByLengthMap.coffee new file mode 100644 index 0000000..b735fba --- /dev/null +++ b/src/Fuell/FunctionByLengthMap.coffee @@ -0,0 +1,6 @@ +exports.func = +func = (template) -> + -> + ln0 = arguments.length.toString() + return f.apply this, arguments for ln, f of template when ln == ln0 + throw "Function does not support #{arguments.length} arguments" \ No newline at end of file diff --git a/src/Fuell/FunctionByTypesPairs.coffee b/src/Fuell/FunctionByTypesPairs.coffee new file mode 100644 index 0000000..e60d2e8 --- /dev/null +++ b/src/Fuell/FunctionByTypesPairs.coffee @@ -0,0 +1,14 @@ +exports.func = +func = (template) -> + -> + for [types, f] in template + return f.apply this, arguments if argsMatchTypes types, arguments + throw "Function does not support types of supplied arguments: #{(type arg)?.name for arg in arguments}" + +argsMatchTypes = (types, args) -> + return false if args.length != types.length + return false for t, i in types when t? && !instanceOf t, args[i] + true + +instanceOf = (t, x) -> (x instanceof t) || (t == type x) +type = (x) -> x?.constructor diff --git a/src/Fuell/FunctionTemplate.coffee b/src/Fuell/FunctionTemplate.coffee new file mode 100644 index 0000000..a65a59d --- /dev/null +++ b/src/Fuell/FunctionTemplate.coffee @@ -0,0 +1 @@ +exports[k] = v for k, v of require "../FunctionComposing/FunctionTemplate" \ No newline at end of file diff --git a/src/Fuell/Keys.coffee b/src/Fuell/Keys.coffee index 3eea842..cae5d3c 100644 --- a/src/Fuell/Keys.coffee +++ b/src/Fuell/Keys.coffee @@ -1,6 +1,6 @@ # Most probably a useless module -HOFU = require "../HOFU" +FunctionComposing = require "../FunctionComposing" exports.pairs = pairs = (values, keys) -> [k, values[i]] for k, i in keys @@ -13,6 +13,5 @@ map = (values, keys) -> # Fits fine in Array exports.resultPairs = -resultPairs = HOFU.composable (f, keys) -> +resultPairs = FunctionComposing.Function.composable (f, keys) -> [k, f k] for k in keys - diff --git a/src/Fuell/Object.coffee b/src/Fuell/Object.coffee index 3fd528a..3b451a8 100644 --- a/src/Fuell/Object.coffee +++ b/src/Fuell/Object.coffee @@ -1,4 +1,4 @@ -HOFU = require "../HOFU" +FunctionComposing = require "../FunctionComposing" exports.Object = Object @@ -7,7 +7,7 @@ HIGHER ORDER ### exports.result = -result = HOFU.composable (f, o) -> +result = FunctionComposing.Function.composable (f, o) -> throw "deprecated: Object.result in favor of Optional.result" f o if o? diff --git a/src/Fuell/Optional.coffee b/src/Fuell/Optional.coffee index 2977cee..dba63e2 100644 --- a/src/Fuell/Optional.coffee +++ b/src/Fuell/Optional.coffee @@ -1,7 +1,7 @@ -HOFU = require "../HOFU" +FunctionComposing = require "../FunctionComposing" exports.result = -result = HOFU.composable (f, o) -> +result = FunctionComposing.Function.composable (f, o) -> f o if o? exports.after = diff --git a/src/Fuell/Set.coffee b/src/Fuell/Set.coffee index a3d7646..483b845 100644 --- a/src/Fuell/Set.coffee +++ b/src/Fuell/Set.coffee @@ -3,4 +3,4 @@ Array = require "./Array" exports.equals = equals = (ys, xs) -> - [] == Array.difference ys, xs + Array.empty Array.difference ys, xs diff --git a/src/Fuell/Text.coffee b/src/Fuell/Text.coffee index 0fbf8f1..f1fa9a2 100644 --- a/src/Fuell/Text.coffee +++ b/src/Fuell/Text.coffee @@ -4,11 +4,11 @@ Strings = require "./Strings" # exports.equals = # equals = (y, x) -> -# throw "TODO: Text.equals" +# throw "Unimplemented: Text.equals" # exports.normalized = # normalized = (text) -> -# throw "TODO: Text.normalized" +# throw "Unimplemented: Text.normalized" exports.indented = indented = (spaces, text) -> diff --git a/src/FunctionComposing.coffee b/src/FunctionComposing.coffee new file mode 100755 index 0000000..626aa45 --- /dev/null +++ b/src/FunctionComposing.coffee @@ -0,0 +1,4 @@ +exports[name] = require "./FunctionComposing/#{name}" for name in [ + "Function" + "FunctionTemplate" +] diff --git a/src/FunctionComposing/Function.coffee b/src/FunctionComposing/Function.coffee new file mode 100644 index 0000000..a11ff94 --- /dev/null +++ b/src/FunctionComposing/Function.coffee @@ -0,0 +1,18 @@ +FunctionTemplate = require "./FunctionTemplate" + +exports.Function = Function + +exports.composable = +composable = (f) -> + composableByPositions [0], f + +exports.composableByPositions = +composableByPositions = (positions, f) -> + -> + newArgs = [] + for v, i in arguments + if i in positions && v not instanceof Function + newArgs.push FunctionTemplate.func v + else + newArgs.push v + f.apply this, newArgs \ No newline at end of file diff --git a/src/FunctionComposing/FunctionTemplate.coffee b/src/FunctionComposing/FunctionTemplate.coffee new file mode 100644 index 0000000..77326be --- /dev/null +++ b/src/FunctionComposing/FunctionTemplate.coffee @@ -0,0 +1,33 @@ +BasicFunctionComposing = require "../BasicFunctionComposing" + +exports.func = +func = (declaration) -> + # [head, tail...] = declaration + head = first declaration + tail = allButFirst declaration + if head instanceof Function + BasicFunctionComposing.Function.partiallyApplied (composed tail), head + else if isDeclaration head + reduced BasicFunctionComposing.Function.nested, (composed declaration) + else throw "Incorrect declaration" + +composed = (xs) -> + for x in xs + if isDeclaration x then func x else x + +isDeclaration = (x) -> + (x instanceof Array) && + (x.length != 0) && + ((x[0] instanceof Function) || isDeclaration x[0]) + + +first = (xs) -> xs[0] +last = (xs) -> xs[xs.length - 1] +allButLast = (xs) -> xs.slice 0, -1 +allButFirst = (xs) -> xs.slice 1 +reducedRight = (f, y0, xs) -> + y = y0 + for i in [(xs.length - 1)..0] + y = f xs[i], y + y +reduced = (f, xs) -> reducedRight f, (last xs), allButLast xs if xs.length > 0