본문 바로가기

프로그래밍 기술 노트/Functional Study

부분함수 (Currying) 에서 Generic 유지하기 in Kotlin / F#

함수형 패러다임에서는 OOP Style 로 변수들을 모아두는 대신, closure 캡쳐식으로 유지하는데, 보통 Currying 이 자동으로 적용되서, 부분함수를 쉽게 만들수 있다.

let add (a: int) (b: int) = a + b
let addOne = add 1
let result = addOne 2

parameter 2개를 받는 함수 add 에 파라미터를 한개만 넣으면, 자동으로 해당 값은 캡쳐되고 파라미터 한개를 받는 함수 (부분적 으로 적용된 함수 -> 즉 부분 함수 PartialFunction이다 )

그리고 이렇게 걍 파라미터를 들 적으면 알아서 부분함수가 되는게 Currying 이라고 한다.

Kotlin 은 어디까지나 기본 철학은 OOP 위에 쌓여있는 언어이기 때문에 Currying 이 자동으로 되지는 않는데, 적당히 람다나 inner function 을 리턴하는 식으로 직접 Currying 용 함수를 만들 수 는 있다.

fun add (a:Int) : (Int) -> Int {
    fun b (c:Int) : Int {
        return a + c
    }
    return ::b
}

val add1 = add(1)
val result = add1(2)

Kotlin 의 DSL 구축을 위한 여러 Feature 를 쓰까섞어서 멋들어진 코드를 작성할 수 있는데,

여기서 Generic Function 에 대한 부분 함수 처리에서 문제가 발생한다.

예를 들어 Currying 을 적용한 이후 Int가 아닌 T를 받는 함수를 만들고 싶다고 해보자

fun<T> add (a:Int) : (T) -> Int {
    fun b (c:T) : Int {
        if (c is String) 
            return a + c.toInt()
        if (c is Int)
            return a + c
        return 0
    }
    return ::b
}

val add1 = add<Int>(1) // 여기서 Type 을 정해야함!
val result = add1(2)
val result = add2("2") // 불가능!

변수(런타임)와 코드(컴파일타임)의 실체화 시간이 다르기 때문에

이렇게 add1 이라는 Generic 함수를 만들수 없고, add1 은 Geneic 함수가 아닌, Generic 을 통해 만들어진, "Int' 만 받는 함수가 된다.

해결방법은? 딱히 없다... 따라서 좀 Tricky 하게 해결해야 하는데... 바로 Invoke 오퍼레이터를 이용한 짭 함수를 만드는것..

data class Add(val a: Int) {
    operator fun<T> invoke(b: T): Int {
        if (b is String)
            return a + b.toInt()
        if (b is Int)
            return a + b
        return 0
    }
}

fun add (a:Int): Add  {
    return Add(a)
}

val add1 = add(1)
val result = add1(2)
val result2 = add1("2")

이런식으로 구현 할 수 있다. (이 경우에는 쓸모없는 add 함수를 지우고 Add class 명을 add 로 바꿔도 동일한 동작)

 

참고로 Kotlin 보다 더 FP 에 근간을 둔 F# 에서도 Generic Function 을 부분함수화 시키는건 안되더라..

f# - 부분적으로 적용된 함수 제네릭 유지 참고

F# 에서는 다음과 같이 해결한다.

let add (a: int) (b: 't) = 
    match box b with
    | :? int as i -> a + i
    | :? string as s -> a + int s
    | _ -> 0

let add1 b = add 1 b
add1 2
add1 "2"

그냥 함수를 새로 만든다 ;; 엄밀히 말하면 currying 은 아니고 그냥 함수인데 타입추정에 의하여 쉽게 bake 하는 방법..

728x90