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) -> Mediator, Mediator : (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 구조를 요약한 것임
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가 찍히는 것을 볼 수 있다
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 |