Created
May 3, 2012 00:46
-
-
Save dsrkoc/2582238 to your computer and use it in GitHub Desktop.
Attempt at implementing the `uncurried` method for all Closures, focus on enforcing single parameter functions
This file contains hidden or 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
| class ClosureCategory { | |
| static Closure uncurried(Closure orig) { | |
| def nestingLen = checkCurried(orig, 1) | |
| nestingLen == 1 ? | |
| orig : | |
| { ...args -> | |
| if (args.size() != nestingLen) | |
| throw new IllegalArgumentException( | |
| "Arity does not match, expecting $nestingLen parameters, got ${args.size()}") | |
| else | |
| args.inject(orig) { cl, arg -> cl(arg) } | |
| } | |
| } | |
| // Each nested function in a properly curried Closure | |
| // must accept one parameter and return its inner Closure. | |
| // The innermost function won't return Closure, obviously. | |
| private static int checkCurried(Closure cl, int nestingLength) { | |
| if (isArity1(cl)) { | |
| def (resultTypeClosure, clResult) = returnsClosure(cl) | |
| resultTypeClosure ? | |
| checkCurried(clResult, nestingLength + 1) : // going deeper | |
| nestingLength // bottom maybe reached | |
| } else { | |
| throw new UnsupportedOperationException('Closure is not properly curried') | |
| } | |
| } | |
| private static boolean isArity1(Closure cl) { | |
| cl.maximumNumberOfParameters == 1 | |
| } | |
| // a hack to get the Closure's return type, | |
| // and access the inner function | |
| private static List returnsClosure(Closure cl) { | |
| try { | |
| def res = cl(null) | |
| [res instanceof Closure, res] | |
| } catch (ignore) { | |
| [false, null] | |
| } | |
| } | |
| } | |
| // the test ---------- | |
| def c1 = { a -> { b -> { c -> a + b + c }}}, | |
| c2 = { a, b -> a + b }, | |
| c3 = { a -> a + 1 }, // treated as trivially curried | |
| c4 = { -> 1 }, | |
| c5 = { a, b -> { c -> a + b + c }}, | |
| c6 = { a -> { b, c -> a + b + c }}, | |
| c7 = { ...xs -> xs.sum() } // counts as one parameter? | |
| class X { def i } | |
| class Y { def j } | |
| def x = new X(i: 1), | |
| y = new Y(j: 2), | |
| m = [k: 3] | |
| def c8 = { X a -> { Y b -> { Map c -> a.i + b.j + c.k }}} | |
| def expectFail(exClass, action) { | |
| try { | |
| action() | |
| throw new IllegalStateException("Expected failure with exception $exClass") | |
| } catch (e) { | |
| assert e.getClass() == exClass | |
| } | |
| } | |
| use(ClosureCategory) { | |
| assert c1.uncurried()(1, 2, 3) == 6 | |
| assert c1.uncurried().curry(1, 2)(3) == 6 // partially apply args | |
| expectFail(IllegalArgumentException) { c1.uncurried()(1, 2) } | |
| expectFail(IllegalArgumentException) { c1.uncurried()(1, 2, 3, 4) } | |
| expectFail(UnsupportedOperationException) { c2.uncurried() } | |
| assert c3.uncurried()(1) == 2 | |
| expectFail(UnsupportedOperationException) { c4.uncurried() } | |
| expectFail(UnsupportedOperationException) { c5.uncurried() } | |
| expectFail(UnsupportedOperationException) { c6.uncurried() } | |
| assert c7.uncurried()(1, 2, 3) == 6 | |
| assert c8.uncurried()(x, y, m) == 6 | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment