[Swift]커링(Currying)
커링(Currying)
수학과 컴퓨터 과학에서 커링은 다중 인수를 갖는 함수를 단일 인수로 갖는 함수들의 함수열로 바꾸는 것입니다.[1]
언커링(UnCurrying)은 커링의 듀얼 변형으로 f(x) 함수를 취하면 그 결과로 g(y)라는 다른 함수를 반환하고, 새로운 함수 f(x, y)를 가져옵니다.[2]
커링(Currying)이라는 이름은 하스켈 커리(Haskell Curry)에서 이름을 가져온 것입니다.
동기(Motivation)
커링은 주어진 다중 값을 함수로 계산하는 과정과 유사합니다.
예를 들어 f(x, y) = y / x
라는 함수가 주어졌다면 h(x) = y -> f(x, y)
로 정의할 수 있습니다. f(2, 3)을 계산하기 위해서 x의 값에 2를 대입합니다.
그 후에 y 함수의 결과로 새로운 함수 g(y) = h(2) = y -> f(2, y) = y / 2
로 정의할 수 있습니다
다음으로 y 값에 3을 대입하여 g(3) = f(2, 3) = 3 / 2
라는 과정을 도출하게 됩니다.
커링 정의
f:(X x Y) -> Z 타입의 함수 f는 curry(f): X->(Y->Z)로 만들 수 있습니다. curry(f)는 타입 X의 인자를 가지고 Y->Z 타입 함수를 반환합니다. 언커링은 역 변형입니다.
커링 함수 X->(Y->Z)는 X->Y->Z로 작성되기도 합니다.
코드로 보는 커링
두 개의 인자를 가지는 함수를 통해 점점 발전시켜 봅시다.
func add1(x: Int, y: Int) -> Int {
return x + y
}
위의 함수는 두 개의 인자를 가지고 합계를 반환합니다. 클로저를 이용하여 다른 방식으로 작성할 수 있습니다.
func add2(x : Int) -> (Int -> Int) {
return { y in return x + y }
}
위의 함수는 인자 하나만 가지고 두번째 인자 y로 예상하는 클로저를 반환합니다.
두 함수는 각각 다르게 작성됩니다.
add1(1, 2)
add2(1)(2)
첫번째 함수 add1는 클로저 대신 커링된 버전으로 정의할 수 있습니다.
func add3(x: Int)(y: Int) -> Int {
return x + y
}
대신 두번째 인자는 인자 명을 명시해주어야 합니다.
add3(2)(y: 1)
다시 돌아가서 앞에서 f(X x Y) -> Z
타입의 함수를 커링된 제네릭 함수로 정의할 수 있습니다.
func curry<X, Y, Z>(f: (X, Y) -> Z) -> X -> Y -> Z {
return { x in
{ y in f(x, y) }
}
}
func curry<A, B, C, D>(f: (A, B, C) -> D) -> A -> B -> C -> D {
return { a in { b in { c in f(a, b, c) } } }
}
또한, 여러 인자를 함수로 받아 커링된 버전으로 함수를 정의할 수 있습니다.
func curry1<X, Y, Z>(f: X -> Y, g: Y -> Z) -> X -> Z {
return { x in g(f(x)) }
}
infix operator >>> { associativity left }
func >>> <A, B, C>(f: A -> B, g: B -> C) -> A -> C {
return { x in g(f(x)) }
}
var f: Int -> Int = { $0 + 2 }
var g: Int -> Int = { $0 * 3 }
var myf1 = f >>> g
myf1(2) // 12
var myf2 = curry1(f, g)
myf2(2) // 12
정리
다중 인자를 갖는 함수를 단일 인자로 갖는 여러 함수로 변형시키는 것을 커링(Currying)이라고 부릅니다.
함수형 프로그래밍을 통해 로직이 견고하게 작성되도록 만들어 집니다.
참고 자료
- functional programming 3
- FP 3
- swift 143
- refactoring 2
- type 10
- lamda 3
- currying 2
- uncurrying 1
- haskell 2
- closure 20
- lazy 5