일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- springcloud
- 설계
- Eureka
- spring cloud netflix zuul
- api-gateway
- dfs
- zuul
- 단위테스트
- spring cloud netflix
- Java
- netflix eureka
- java #jvm #reference #gc #strong reference
- forkandjoinpool #threadpool #jvm #async #non-blocking
- 탐색
- test
- spring cloud netflix eureka
- microservice architecture
- reactive
- container image #docker #layer #filesystem #content addressable
- netflix
- Dynamic Routing
- unit
- 서비스스펙
- unittest
- docker
- code refactoring
- BFS
- Spring Data Redis
- spring cloud
- Today
- Total
phantasmicmeans 기술 블로그
11. Kotlin - Higher Order Functions 본문
Higher-Order Functions
코틀린의 함수는 1급 객체이다. 즉 인자로 전달 될 수 있고, 다른 함수의 리턴값으로도 사용될 수 있다. 또한 변수에 부여될 수 있다.
아래 2가지 조건 중 하나를 만족하는 함수를 Higher-Order-Function(고차원 함수)
라 부른다.
-
함수를 인자로 받는 함수
-
리턴값으로 함수를 사용하는 함수
자바의 경우 Collections.sort()
를 예로 들 수 있다.
첫 번째 인자로 list, 두번째 인자로 Comparator를 받는데 이는 Functional Interface이다.
public static <T> void sort(List<T> list, Comparator<? super T> c)
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
...
}
자바는 Functional Interface를 통해 람다식에 접근할 수 있고, 람다식은 결국 익명함수를 만드는 목적이므로 Comparator 또한 일단 함수
라 생각하고 아래 예를 보면 될 것 같다.
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
Collections.sort(list, (a, b) -> a.compareTo(b))
직접 예를 만들면 아래와 같을 수 있다.
public Integer test(BiFunction<Integer, Integer, Integer> biFunction, Integer a, Integer b) {
return biFunction.apply(a, b);
}
test((a, b) -> a + b, 1, 2);
위 예시들은 자바의 Functional Interface 중 몇가지의 경우에 불과하기에, 때에 따라서는 Function, Supplier, TriFunction 등 굉장히 다양한 Functional Interface를 활용해야한다.
코틀린과 비교하면 큰 단점이라고 볼 수 있을 것 같다. 코틀린도 자바처럼 Higher-Order-Function을 지원하지만 Funcional Interface에 국한되지 않는다.
단순히 아래처럼 람다식을 바로 사용해서 Higher-Order-Function들을 정의할 수 있다. 아래 함수들은 모두 조건을 만족한다.
fun simpleHigherOrderFunction(sum: (Int, Int) -> Int, a: Int, b: Int): Int = sum(a, b)
val sumInts: (a:Int, b:Int) -> Int = { a, b -> a + b }
var t0: (a: Int, b: Int, c:Int) -> Int = { a, b, c -> a + b + c}
var t1 = { a:Int, b:Int, c:Int -> a + b + c }
var t2 = { a:Int, b:Int, c:Int, d:Int -> a + b + c * d }
var t3 = { a:Int, b:Int, c:Int, d:Int -> a + b + c - d }
sumInts
의 경우 Function Type을 가지고 있는 것인데, Int 타입의 인자 2개를 받아 Int 타입을 리턴하겠다는 명시적 표기법이다.
파라미터가 없는 경우 () -> A 처럼 생략 가능하지만, 리턴타입이 Unit인 경우는 생략이 불가능하다.
Function Type
코틀린은 함수를 다룰 때 타입 선언에 친숙하고 이를 Function Type이라 부른다.
해석은 단순하게 보면 된다. a, b 두 인자를 Int 타입으로 받고, Int 타입의 무언가를 리턴하겠다는 뜻이다.
(a:Int, b:Int) -> Int
예를 들면 아래처럼 사용할 수 있다.
val onClick: () -> Unit = ...
추가로 Function Type은 수신 객체 타입
을 가질 수 있다.
아래는 수신 객체인 A가, B 타입의 인자를 받으며 C 타입의 리턴값을 내보내겠다는 의미이고, 이 함수의 호출은 A라는 객체에서 행해지게 된다.
extension function과 동일하게 보일 수 있으나 조금 다르다. 추후에 설명할 내용이니 일단은 다르다는 것만 알아두고 넘어가도 좋다.
A.(B) -> C
A
: receiver() -> String
: FunctionType
이러한 방법은 Function literals with receiver
와 함께 사용된다. 아래를 보자
Function Literals with receiver
kotlin에서 Function literal은 둘 중 하나이면 된다.
- Lambda expression
- Anonymous function
어쨌든 function literal을 사용하며 receiver를 가지고 싶을 때, Function Type을 사용한다.
function literal을 들여다보면 수신 객체가 this
로 변하게 되어 지정자 없이 사용할 수 있고, 원한다면 this를 붙일 수 있다.
무슨 말인지 이해가 안된다면 아래 예제를 보자.
좌항을 보면 Int 타입의 수신 객체가 Int 타입의 인자를 받고, Int 타입의 리턴 값을 나타내는 Function Type을 가지고 있다.
literal로 쓰인 우측의 람다식은 other -> plus(other)
와 같은 형태로 이루어져 있다. 바로 위에서 함수의 호출은 수신 객체
에서 이루어진다고 하였으니 아래 식에서 plus(other)
호출자는 수신 객체
가 된다.
val sumOther: Int.(Int) -> Int = { other -> plus(other) }
/* 1이 수신 객체, 2가 인자 / 수신 객체가 this 이므로 plus 함수는 수신 객체가 호출하게 됨 */
val sumOtherResult = 1.sumIt(2)
function literal은 수신 객체가 this
로 변하게 되어 지정자 없이 사용할 수 있다고 했으니 이를 다시쓰면 아래처럼 된다.
위와 동일한 코드이다.
val sumThis: Int.(Int) -> Int = { other -> this.plus(other) }
val sumThisResult = 1.sumThis(2)
It
keyword와 함께 사용하려면 아래처럼 작성할 수 있다. (이번에도 plus 호출은 당연히 수신객체)
val sumIt: Int.(Int) -> Int = { plus(it) }
val sumItResult = 1.sumIt(2)
this, it
를 함께 쓰고싶으면 아래처럼 구성할 수 있다.
val test : Int.(Int) -> Int = { if (this > 0) this * it else this - it }
val testResult = 10.test(3)
추가로, labmda 식을 변수에 대입하지 않고 function 그대로 사용할 수도 있다.
fun doSomethingOnTwo(f: Int.(Int) -> Int) = f(2, 2) // 순서대로 (receiver, parameter)
val multiple = doSomethingOnTwo { this * it }
val plus = doSomethingOnTwo { this + it }
val minus = doSomethingOnTwo { this - it }
val divide = doSomethingOnTwo { this % it }
println("$multiple $plus $minus $divide")
spring webflux의 router 또한 이런 방식으로 이루어져 있으니 참고 정도 해두면 될 것 같다.
fun router(routes: RouterFunctionDsl.() -> Unit) = RouterFunctionDsl(routes).build()
@Bean
fun route() = router { // lambda
/** GET 또한 고차원 함수이다
* fun GET(pattern: String, f: (ServerRequest) -> Mono<out ServerResponse>)
**/
GET("/route") {
ServerResponse.ok().body(fromValue(arrayOf(1,2,3)))
}
}
TypeAlias
FunctionType을 매번 정의하기 귀찮으니 typealias
를 통해 미리 정의해놓을 수 있다.
typealias ClickHandler = (Button, ClickEvent) -> Unit
'Programming > Kotlin' 카테고리의 다른 글
12. Kotlin - Extension Function VS Function literal with receiver (0) | 2021.01.22 |
---|---|
10. Kotlin - Functional Interface (0) | 2021.01.22 |
9. Kotlin - Interface (0) | 2021.01.22 |
8. Kotlin - Backing Field (0) | 2021.01.22 |
7. Kotlin - Properties (0) | 2021.01.22 |