一、柯里化
柯里化:把一个多参函数转换成每次只接受一个参数的函数
二、柯里化推导
从大家都很容易理解的一个简单版本开始:
fun <P1, P2, R> ((P1, P2) -> R).curry(): (P1) -> (P2) -> R {
return { p1: P1 -> { p2: P2 -> this(p1, p2) } }
}
函数 ((P1, P2) -> R)
可以用 Function2 代替(Function2 是 SAM接口,所以这里其实是 SAM 逆转换):
fun <P1, P2, R> Function2<P1, P2, R>.curry(): (P1) -> (P2) -> R {
return { p1: P1 -> { p2: P2 -> this(p1, p2) } }
}
由于扩展函数内就一条 return 语句,所以可以直接使用等号把 return 表达式赋值给扩展函数:
fun <P1, P2, R> Function2<P1, P2, R>.curry(): (P1) -> (P2) -> R =
{ p1: P1 -> { p2: P2 -> this(p1, p2) } }
把 lambda 表达式替换成匿名函数:
fun <P1, P2, R> Function2<P1, P2, R>.curry(): (P1) -> (P2) -> R =
fun(p1: P1): (P2) -> R {
return { p2: P2 -> this(p1, p2) }
}
P1 匿名函数(为方便理解 这里我们简单把 P1 入参的匿名函数记作 P1 匿名函数,后续雷同)的函数体也是只有一个 return 语句,同样地 我们直接使用等号把 return 表达式赋值给 P1 匿名函数:
fun <P1, P2, R> Function2<P1, P2, R>.curry(): (P1) -> (P2) -> R =
fun(p1: P1): (P2) -> R = { p2: P2 -> this(p1, p2) }
再次把 lambda 表达式替换成匿名函数:
fun <P1, P2, R> Function2<P1, P2, R>.curry(): (P1) -> (P2) -> R =
fun(p1: P1): (P2) -> R = fun(p2: P2): R {
return this(p1, p2)
}
P2 匿名函数的函数体依然只有一个 return 语句,直接使用等号把 return 表达式赋值给 P2 匿名函数:
fun <P1, P2, R> Function2<P1, P2, R>.curry(): (P1) -> (P2) -> R =
fun(p1: P1): (P2) -> R = fun(p2: P2): R = this(p1, p2)
最后,由于 kotlin 具有自动推导类型功能,我们可以把 P1 匿名函数、P2 匿名函数、curry 扩展函数的返回类型都给去掉:
fun <P1, P2, R> Function2<P1, P2, R>.curry() = fun(p1: P1) = fun(p2: P2) = this(p1, p2)
最终的柯里化版本就出炉了,其实光看最终版也不难理解,只需要知道代码右结合性、类型自动推导、匿名函数就好了。
以上仅仅是针对 Function2 的柯里化扩展,诸如 Function3 ~ Function22 都是依葫芦画瓢,以此类推。真正的难点在于 FunctionN
。
另外,对于最终版还可以显式调用 Function2 的 invoke 方法(不建议这种装*写法,仅为了列出所有的柯里化写法):
fun <P1, P2, R> Function2<P1, P2, R>.curry() = fun(p1: P1) = fun(p2: P2) = invoke(p1, p2)
三、反柯里化推导
同样地,从大家都很容易理解的一个简单版本开始:
fun <P1, P2, R> ((P1) -> (P2) -> R).uncurry(): (P1, P2) -> R {
return { p1: P1, p2: P2 -> this(p1)(p2) }
}
用 Function2 替换扩展函数返回值 (P1, P2) -> R
(这里仅仅是为了演示两种写法是等价的)
fun <P1, P2, R> ((P1) -> (P2) -> R).uncurry(): Function2<P1, P2, R> {
return { p1: P1, p2: P2 -> this(p1)(p2) }
}
由于 kotlin 可以自动推导类型,去掉扩展函数返回值(提前去掉便于后续少写点代码,也可以后面一并再去掉):
fun <P1, P2, R> ((P1) -> (P2) -> R).uncurry() {
return { p1: P1, p2: P2 -> this(p1)(p2) }
}
函数体只有一条 return 语句,直接等号赋值:
fun <P1, P2, R> ((P1) -> (P2) -> R).uncurry() = { p1: P1, p2: P2 -> this(p1)(p2) }
匿名函数替换掉 lambda 表达式:
fun <P1, P2, R> ((P1) -> (P2) -> R).uncurry() = fun(p1: P1, p2: P2): R {
return this(p1)(p2)
}
干掉匿名函数的 return,直接等号赋值:
fun <P1, P2, R> ((P1) -> (P2) -> R).uncurry() = fun(p1: P1, p2: P2): R = this(p1)(p2)
去掉匿名函数返回值:
fun <P1, P2, R> ((P1) -> (P2) -> R).uncurry() = fun(p1: P1, p2: P2) = this(p1)(p2)
反柯里化为 Function2 函数的最终版就完成了。同样,这里的 this 调用同样也可以替换为 invoke 调用。
四、测试
private fun foo(a: Int, b: Int, c: Int, d: Int): Int {
return a + 10 * b + 100 * c + 1000 * d
}
@Test
fun testCurrying() {
val curry = ::foo.curry()
val uncurry = curry.uncurry()
assertEquals(foo(9, 5, 2, 7), curry(9)(5)(2)(7))
assertEquals(foo(9, 5, 2, 7), uncurry(9, 5, 2, 7))
assertNotEquals(foo(9, 5, 2, 7), curry(1)(3)(1)(4))
assertNotEquals(foo(9, 5, 2, 7), uncurry(1, 3, 1, 4))
}
五、偏函数
偏函数:固定一个多元函数的一些参数,然后产生另一个更小元的函数,那么这个小元函数就是原函数的一个偏函数。
元是指函数参数的个数,比如一个带有两个参数的函数被称为二元函数。
fun <P1, P2, R> Function2<P1, P2, R>.partial1(p1: P1) = fun(p2: P2) = this(p1, p2)
fun <P1, P2, R> Function2<P1, P2, R>.partial2(p2: P2) = fun(p1: P1) = this(p1, p2)