코틀린(Kotlin, Java) / / 2022. 11. 3. 15:19

Functional Paradigm Pattern in Kotlin

1. Currying 이해

 

아래의 예를 보자.

fun <T> optionalCombinator(button : Int, getVal : (String) -> (String) -> T) : (String) -> T
{
    return when(button)
    {
        0    -> getVal("$button returnable")
        else -> getVal("other returnable")
    }
}

val f = optionalCombinator(0){ combA ->

    println(combA)
    return@optionalCombinator { combB ->
        println(combB)
        combB.length
    }
}

println(f("second returnable"))

val f2 = optionalCombinator(1){ combA ->
    println(combA)
    return@optionalCombinator { combB ->
        println(combB)
        combB.reversed()
    }
}

println(f2("second returnable"))

1> optionalCombinator은 getVal 인자에 대해 (String) -> (String) -> T 타입을 받아 (String) -> T를 리턴하는 메소드이다.

2> (String) -> (String) -> T 는 (String) -> MediatorMediator : (String) -> T이다. 즉 getVal은 input 으로 String을 하나 받고 이에 대한 리턴 형식은 String -> T 가 된다.

 

3> optionalCombinator 메소드에서 정의할 부분은 getVal의 input 부분이 됨. output 부분은 외부 lambda에서 해줄 부분이되고 그 type은 (String) -> T가 되어야 한다.

 

4> 다시한번, 아래의 이미지를 참조해서 이해를 해보면, getVal argument의 input 부분은 optionalCombinator 내에서 입력하고, 이에 대한 output 부분은 optionalCombinator 를 호출할때 lambda안에 싣어 보내야한다. 결과적으로 getVal의 output은 메소드의 내부에서 소모되고, 이에 대한 구현은 외부에서 (여기서는 val f 선언시) 이루어 짐. 반대로 getVal의 input은 메소드의 내부에서 생산되고, 외부 람다에서 소비 된다고 볼 수 있음

 

5> 아래 flow는 본인이 이해하고 있는 functional 인자에 대한 call 구조를 요약한 것임

functional argument call flow

 

2. Class 내에 functional extension fuction에 대한 정의

1> 이렇게 한다면 Builder 외부에서 fuctional argument 를 줄 때 Builder에 정의해놓은 variable들과 method들에 대한 호출을 할 수 있다.

2> 이에 대한 응용 예제는 invokeStuff를 실행시켜보면 알 수 있다.

class Builder (val multiplier: Int) {

    fun invokeStuff(action: (Builder.(String) -> Int)) {
        println(this.action("hello"))
    }

    fun multiply(value: Int) : Int {
        return value * multiplier
    }
}

실행 코드

val builder = Builder(10)
builder.invokeStuff {
    println(multiplier)
    val result = multiply(1)
    println("$it $result")
    result
}

3> 위와 같이 invokeStuff에 lambda로 (String) -> Int 타입의 함수블록을 전달한다. 그런데 이 블록의 context는 Builder Class안에 있도록 타입을 Builder.(String) -> Int 로 해놓으므로 Builder의 메소드들(multiply)이나 변수(multiplier:Int)에 대한 호출이 가능하다. 따라서 위와 같이 실행코드에서 lambda블록안에 변수와 함수를 호출한 부분을 볼 수 있다.

4> 위의 실행코드는 아래 스타일과 같이 Anonymous function을 사용하지 않는 스타일로도 사용할 수 있다.

 

만약에 Builder의 extension function으로 지정하지 않고 내부 메소드들을 사용하려고 한다면, 아래와 같이 compiler error log가 찍히는 것을 볼 수 있다

invokeStuff2는 Builder Context를 모르기에 multiply 호출이 되지않는다

 

 

3. Excercise 

맞춰보세요.

 

data class ChangeListVersions(
    val topicVersion: Int = -1,
    val authorVersion: Int = -1,
    val newsResourceVersion: Int = -1,
)

fun updateChangeListVersion(update: ChangeListVersions.() -> ChangeListVersions) : ChangeListVersions {
    return update(
        ChangeListVersions(
            topicVersion = 1,
            authorVersion = 1,
            newsResourceVersion = 1
        )
    )
}


fun main(){
    val result = updateChangeListVersion{
        this.copy(
            topicVersion = this.topicVersion + 1,
            authorVersion = this.authorVersion + 2,
            newsResourceVersion = this.newsResourceVersion + 3
        )
    }
}

문제1. 위와 같이 구현했을때 동작 순서와 result가 어떻게 되었을지에 대해 생각해봅시다.

문제2. ChangeListVersions.() -> ChangeListVersions 에서 ChangeListVersions.()를 ()로 바꾸었을 때 제대로 실행이 되는지와, 안된다면 왜 안되는지에 대해서 생각해봅시다.

 

 

 

 

 

 

답1: 추후 업데이트

 

답2 : ChangeListVersions.()라고 하지않으면 context를 알기위한 instance가 필요없다. 그런데 context가 없으면 () -> ChangeListVersions 를 한다는게 말이 안된다. 따라서 반드시 ChangeListVersions. 를 붙여주어야한다. 굳이 실행을 시켜보고 싶다면 미리 context를 부여해도 된다. 2.Class 내에 functional extension fuction에 대한 정의의 예제와 비슷한 형식으로 아래와 같이 먼저 context를 주면됨

참고 : 위의 연습문제 예제는 https://witcheryoon.tistory.com/346

'코틀린(Kotlin, Java)' 카테고리의 다른 글

get Data class Field value by Reflect  (0) 2022.05.22
Array 배열값 행렬 재배치  (0) 2022.02.27
Array 순회, for문 만들 때  (0) 2022.02.27
2차원 배열 초기화  (0) 2022.02.24
Regex trial in kotlin  (0) 2022.02.23
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유