blob: 57bb4e07c413abd516e0540f10f041cf7c9e634e [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package groovy
import groovy.test.GroovyTestCase
import org.codehaus.groovy.runtime.DefaultGroovyMethods
/**
* Tests for curried closures
*/
class ClosureCurryTest extends GroovyTestCase {
void testCurry() {
def clos1 = {s1, s2 -> s1 + s2}
def clos2 = clos1.curry("hi")
def value = clos2("there")
assert value == "hithere"
def clos3 = {s1, s2, s3 -> s1 + s2 + s3}
def clos4 = clos3.curry('a')
def clos5 = clos4.curry('b')
def clos6 = clos4.curry('x')
def clos7 = clos4.curry('f', 'g')
value = clos5('c')
assert value == "abc"
value = clos6('c')
assert value == "axc"
value = clos4('y', 'z')
assert value == "ayz"
value = clos7()
assert value == "afg"
clos3 = {s1, s2, s3 -> s1 + s2 + s3}.asWritable()
clos4 = clos3.curry('a')
clos5 = clos4.curry('b')
clos6 = clos4.curry('x')
clos7 = clos4.curry('f', 'g')
value = clos5('c')
assert value == "abc"
value = clos6('c')
assert value == "axc"
value = clos4('y', 'z')
assert value == "ayz"
value = clos7()
assert value == "afg"
clos3 = {s1, s2, s3 -> s1 + s2 + s3}
clos4 = clos3.curry('a').asWritable()
clos5 = clos4.curry('b').asWritable()
clos6 = clos4.curry('x').asWritable()
clos7 = clos4.curry('f', 'g').asWritable()
value = clos5('c')
assert value == "abc"
value = clos6('c')
assert value == "axc"
value = clos4('y', 'z')
assert value == "ayz"
value = clos7()
assert value == "afg"
clos3 = {s1, s2, s3 -> s1 + s2 + s3}
clos4 = clos3.curry('a').clone()
clos5 = clos4.curry('b').clone()
clos6 = clos4.curry('x').clone()
clos7 = clos4.curry('f', 'g').clone()
value = clos5('c')
assert value == "abc"
value = clos6('c')
assert value == "axc"
value = clos4('y', 'z')
assert value == "ayz"
value = clos7()
assert value == "afg"
clos3 = {s1, s2, s3 -> s1 + s2 + s3}
clos4 = clos3.curry('a').asWritable().clone()
clos5 = clos4.curry('b').asWritable().clone()
clos6 = clos4.curry('x').asWritable().clone()
clos7 = clos4.curry('f', 'g').asWritable().clone()
value = clos5('c')
assert value == "abc"
value = clos6('c')
assert value == "axc"
value = clos4('y', 'z')
assert value == "ayz"
value = clos7()
assert value == "afg"
}
void testParameterTypes() {
def cl1 = {String s1, int i -> return s1 + i }
assert "foo5" == cl1("foo", 5)
assert [String, int] == cl1.getParameterTypes().toList()
def cl2 = cl1.curry("bla")
assert "bla4" == cl2(4)
assert null != cl2.getParameterTypes()
assert [int] == cl2.getParameterTypes().toList()
}
void testVarargParameterTypes() {
def a = { Object one, Object[] others -> }
def b = a.ncurry(2, 'x', 'y')
assert b.parameterTypes.name == ['java.lang.Object', 'java.lang.Object', '[Ljava.lang.Object;']
def c = a.ncurry(-3, 'x', 'y')
assert c.parameterTypes.name == ['java.lang.Object', '[Ljava.lang.Object;']
def d = a.curry('a')
assert d.parameterTypes.name == ['[Ljava.lang.Object;']
def g = { String s, Integer num, Date da, Object[] others -> }
def h = g.ncurry(1, 4, new Date(), 'foo')
assert h.parameterTypes.name == ['java.lang.String', '[Ljava.lang.Object;']
}
void testVarargCurry() {
def c = { arg, Object[] extras -> arg + ', ' + extras.join(', ') }
def d = c.curry( 1 ) //curry first param only
assert d( 2, 3, 4 ) == '1, 2, 3, 4'
def e = c.curry( 1, 3 ) //curry part of Object[] also
assert e( 5 ) == '1, 3, 5'
def f = e.curry( 5, 7, 9, 11 ) //currying continues on Object
assert f( 13, 15 ) == '1, 3, 5, 7, 9, 11, 13, 15'
}
void testDelegate() {
def res = null
def c = {a -> res = z}
def cc = c.curry(1)
cc.delegate = [z: "goodbye"]
cc()
assert res == cc.delegate.z
}
void testExpandoWithCurry() {
def sz = 'java.util.Date'.size()
def c = {arg -> arg + delegate.getClass().name.size() }
def d = new Date()
d.metaClass.foo = c
assert d.foo(42) == 42 + sz
d.metaClass.bar = c.curry('baz')
assert d.bar() == 'baz' + sz
}
void testCurryMultiply() {
def multiply = { a, b -> a * b }
def doubler = multiply.curry(2)
assert doubler(4) == 8
}
void testRCurryDivide() {
def divide = { a, b -> a / b }
def halver = divide.rcurry(2)
assert halver(8) == 4
}
void testNCurryBinarySearch() {
def caseInsensitive = { a, b -> a.toLowerCase() <=> b.toLowerCase() } as Comparator
def caseSensitive = { a, b -> a <=> b } as Comparator
def animals1 = ['ant', 'dog', 'BEE']
def animals2 = animals1 + ['Cat']
// curry middle param of this utility method:
// Collections#binarySearch(List list, Object key, Comparator c)
def catSearcher = Collections.&binarySearch.ncurry(1, "cat")
def combos = [[animals1, animals2], [caseInsensitive, caseSensitive]].combinations()
assert combos.collect{ List a, Comparator c ->
// make use we use DGM#sort, not JDK8 sort
def sorted = DefaultGroovyMethods.sort(a,c)
catSearcher(sorted, c)
} == [-3, 2, -3, -4]
}
void testNestedNcurryRcurry() {
def operation = { int x, Closure f, int y -> f(x, y) }
def divider = operation.ncurry(1) { a, b -> a / b }
def halver = divider.rcurry(2)
assert 50 == halver(100)
}
void testEquivalentsNormal() {
def a = {one, two, three, four, five -> "$one,$two,$three,$four,$five"}
def expected = '1,2,3,4,5'
checkEquivalents(a, expected)
}
void testEquivalentsVararg() {
def a = {one, two, Object[] others -> "one=$one, two=$two, others=${others.join(',')}"}
def expected = 'one=1, two=2, others=3,4,5'
checkEquivalents(a, expected)
}
private void checkEquivalents(a, expected) {
def examples = [
[a.curry(1), [2,3,4,5]],
[a.curry(1,2), [3,4,5]],
[a.curry(1,2,3), [4,5]],
[a.curry(1,2,3,4), [5]],
[a.curry(1,2,3,4,5), []],
[a.curry(1,2).curry(3,4), [5]],
[a.curry(1).curry(2).curry(3).curry(4), [5]],
[a.rcurry(5), [1,2,3,4]],
[a.rcurry(5).rcurry(3,4), [1,2]],
[a.rcurry(4,5).rcurry(3), [1,2]],
[a.rcurry(4,5), [1,2,3]],
[a.rcurry(2,3,4,5), [1]],
[a.rcurry(1,2,3,4,5), []],
[a.ncurry(2,3), [1,2,4,5]],
[a.ncurry(2,3,4), [1,2,5]],
[a.ncurry(-3,3,4), [1,2,5]],
[a.ncurry(-3,3), [1,2,4,5]]
]
examples.each{ clos, args ->
assert clos(args as Object[]) == expected
}
}
void testNullVariants() {
assert { x, y -> x ?: y }.curry(null)(null) == null
assert { x, y -> x ?: y }.curry(null)(2) == 2
assert { x, y -> x ?: y }.curry(1)(null) == 1
assert { x, y -> x ?: y }.curry(1)(2) == 1
assert { x, y -> x ?: y }.curry(null, null)() == null
assert { x, y -> x ?: y }.curry(null, 2)() == 2
assert { x, y -> x ?: y }.curry(1, null)() == 1
assert { x, y -> x ?: y }.curry(1, 2)() == 1
assert { x, y -> x ?: y }(null, null) == null
assert { x, y -> x ?: y }(null, 2) == 2
assert { x, y -> x ?: y }(1, null) == 1
assert { x, y -> x ?: y }(1, 2) == 1
assert { x, y -> x ?: y }.curry([null] as Object[])(2) == 2
}
private a(Integer b, Integer c, Integer d){ "3Int: $b $c $d" }
private a(b,c,d){ "3Obj: $b $c $d" }
private a(b,c){ "2Obj: $b $c" }
private a(b){ "1Obj: $b" }
void testCurryWithMethodClosure() {
def c = (this.&a).curry(0)
assert c(1,2) == '3Int: 0 1 2'
assert c("foo",2) == '3Obj: 0 foo 2'
assert c(1) == '2Obj: 0 1'
assert c() == '1Obj: 0'
def b = (this.&a).rcurry(0)
assert b(1,2) == '3Int: 1 2 0'
assert b("foo",2) == '3Obj: foo 2 0'
assert b(1) == '2Obj: 1 0'
assert b() == '1Obj: 0'
}
void testInsufficientSuppliedArgsWhenUsingNCurry() {
def cl = { a, b, c = 'x' -> a + b + c }
assert cl.ncurry(1, 'b')('a') == 'abx'
// ncurry is done eagerly and implies a minimum number of params
shouldFail(IllegalArgumentException) {
cl.ncurry(1, 'b')()
}
// rcurry is done lazily
assert cl.rcurry('b', 'c')('a') == 'abc'
assert cl.rcurry('b', 'c')() == 'bcx'
}
void testCurryInComboWithDefaultArgs() {
def cl = { a, b, c='c', d='d' -> a + b + c + d }
assert 'abcd' == cl.ncurry(0, 'a')('b')
assert 'xycd' == cl.ncurry(1, 'y')('x')
assert 'abdd' == cl.ncurry(0, 'a').rcurry('d')('b')
assert 'abcx' == cl.ncurry(3, 'x')('a', 'b', 'c')
shouldFail(IllegalArgumentException) {
// ncurry is done eagerly and implies a minimum number of params
// default arguments are ignored prior to the ncurried argument
cl.ncurry(4, 'd')('a', 'b')
}
}
}