Home 코틀린으로 배우는 함수형 프로그래밍 - 4장 고차 함수
Post
Cancel

코틀린으로 배우는 함수형 프로그래밍 - 4장 고차 함수

범위

  • 4장 고차 함수

요약

  • 고차 함수, 부분 함수, 부분 적용 함수, 커링, 합성 함수는 함수형 프로그래밍을 하는데 기본적인 개념들이다.

개념 정리

  • 고차 함수(higher order function): 함수형 프로그래밍에서 다음 두 가지 조건 중 하나 이상을 만족하는 함수.
    • 함수를 매개변수로 받는 함수
    • 함수를 반환하는 함수.
  • 부분 함수: 모든 가능한 입력 중, 일부 입력에 대한 결과만 정의한 함수를 의미한다.
  • 커링(currying): 여러 개의 매개변수를 받는 함수를 분리하여, 단일 매개변수를 받는 부분 적용 함수의 체인으로 만드는 방법이다.
  • 합성 함수: 함수를 매개변수로 받고, 함수를 반환할 수 있는 고차 함수를 이용해서 두 개의 함수를 결합하는 것.
  • 포인트 프리 스타일(point free style) 프로그래밍: 함수 합성을 사용해서 매개변수나 타입 선언 없이 함수를 만드는 방식.

책에서 기억하고 싶은 내용

  • 고차 함수를 이용하면 기능을 확장하기도 쉽다.
  • 코틀린은 매개변수로 받는 값이 하나인 경우에 it으로 생략이 가능하다.
  • 커링으 장점 중 하나는 이런 부분 적용 함수를 다양하게 재사용할 수 있다는 점이다. 또한 마지막 매개변수가 입력될 때까지 함수를 실행을 늦출 수 있다.
  • 고차 함수, 부분 함수, 부분 적용 함수, 커링, 합성 함수는 함수형 프로그래밍을 하는데 기본적인 개념들이다.

연습 문제

4-1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
fun invokeOrElse(p: P, default: R): R = when {
    condition(p) -> f(p)
    else -> default
}

fun orElse(that: PartialFunction<P, R>): PartialFunction<P, R> =
    PartialFunction({ if(this.condition(it)) this.condition(it) else that.condition(it) } , {
        if(this.condition(it)) this.f(it) else that.f(it)
    })
    
fun main() {
    val condition: (Int) -> Boolean = { 0 == it.rem(2) }
    val body: (Int) -> String = { "$it is even" }

    val isEven = body.toPartialFunction(condition)
    val isOdd = { i: Int -> "$i is odd" }.toPartialFunction{ !condition(it) }
    
    println(listOf(1, 2, 3).map { isEven.invokeOrElse(it, "$it is odd") })    // [1 is odd, 2 is even, 3 is odd]
    println(listOf(1, 2, 3).map { isEven.orElse(isOdd).invoke(it) })    // [1 is odd, 2 is even, 3 is odd]
}

4-2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
fun <P1, P2, P3, R>((P1, P2, P3) -> R).partial1(p1: P1): (P2, P3) -> R {
    return { p2, p3 -> this(p1, p2, p3) }
}

fun <P1, P2, P3, R>((P1, P2, P3) -> R).partial2(p2: P2): (P1, P3) -> R {
    return { p1, p3 -> this(p1, p2, p3) }
}

fun <P1, P2, P3, R>((P1, P2, P3) -> R).partial3(p3: P3): (P1, P2) -> R {
    return { p1, p2 -> this(p1, p2, p3) }
}

fun main() {
    val func = { a: Int, b: Int, c: Int -> a + b + c }

    val partiallyAppliedFunc1 = func.partial1(1)
    require(6 == partiallyAppliedFunc1(2, 3))

    val partiallyAppliedFunc2 = func.partial2(2)
    require(6 == partiallyAppliedFunc2(1, 3))

    val partiallyAppliedFunc3 = func.partial3(3)
    require(6 == partiallyAppliedFunc3(1, 2)) is odd]
}

4-3

1
2
3
4
5
fun max(a: Int) = { b: Int -> if(a>=b) a else b }

fun main() {
    require(30 == max(10)(30))
}

4-4

val min = { a: Int, b: Int -> if(a <= b) a else b }
val curriedMin = min.curried()

fun main() {
    val min = { a: Int, b: Int -> if(a <= b) a else b }
    val curriedMin = min.curried()
    println(curriedMin(10)(30))    // 10
}

4-5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fun power(a: Int): Int = a * a

tailrec fun max(inputs: List<Int>, acc: Int = Int.MIN_VALUE): Int = when {
    inputs.isEmpty() -> acc
    else -> max(inputs.drop(1), max(inputs.first(), acc))
}

fun max(a: Int, b: Int) = if(a>=b) a else b

fun powerWithMax(list: List<Int>) = power(max(list))

fun main() {
    val list = listOf(1, 2, 3, 4, 5, 6, 7)
    val list2 = listOf(10, 2, 13, 4, 0, 6, 1)

    require(powerWithMax(list) == 49)
    require(powerWithMax(list2) == 169)
}

4-6

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
29
30
fun power(a: Int): Int = a * a

tailrec fun max(inputs: List<Int>, acc: Int = Int.MIN_VALUE): Int = when {
    inputs.isEmpty() -> acc
    else -> max(inputs.drop(1), max(inputs.first(), acc))
}

fun max(a: Int, b: Int) = if(a>=b) a else b

fun powerWithMax(list: List<Int>) = power(max(list))

infix fun <P1, P2, R> ((P2) -> R).compose(g: (P1) -> P2): (P1) -> R {
    return { gInput: P1 -> this(g(gInput)) }
}

val power = { i: Int -> power(i) }
val max = { list: List<Int> -> max(list) }
val powerComposedMax = power compose max

fun main() {
    val list = listOf(1, 2, 3, 4, 5, 6, 7)
    val list2 = listOf(10, 2, 13, 4, 0, 6, 1)

    val power = { i: Int -> power(i) }
    val max = { list: List<Int> -> max(list) }
    val powerComposedMax = power compose max

    require(powerComposedMax(list) == 49)
    require(powerComposedMax(list2) == 169)
}

4-7

1
2
3
4
5
6
7
8
9
10
11
tailrec fun <P> takeWhile(func: (P) -> Boolean, list: List<P>, acc: List<P> = listOf()): List<P> = when {
    list.isEmpty() -> acc
    else -> {
        val newAcc = if(func(list.first())) acc + list.first() else acc
        takeWhile(func, list.drop(1), newAcc)
    }
}

fun main() {
    require(listOf(1, 2) == takeWhile({ p -> p < 3 }, listOf(1, 2, 3, 4, 5)))
}

4-8

1
2
3
4
5
6
7
8
tailrec fun <P> takeWhile2(func: (P) -> Boolean, sequence: Sequence<P>, acc: List<P> = listOf()): List<P> = when {
    !func(sequence.first()) -> acc
    else -> takeWhile2(func, sequence.drop(1), acc + sequence.first())
}

fun main() {
    require(listOf(1, 2, 3, 4, 5, 6, 7, 8, 9) == takeWhile2({ p -> p < 10 }, generateSequence(1) { it + 1 }))
}
1
2
3
4
5
6
7
8
9
10
11
12
13
// 인터넷에서 주워온 코드를 수정한 것.
// 가장 심플하고, 입력값을 수정하는 것이 아닌 신규 Array를 생성하여 리턴한다.
fun getSortedArrayByQuickSort(array: IntArray): IntArray {
    if (array.size < 2) {
        return array
    }

    val pivot = array.first()
    val left = array.filter { it < pivot }.toIntArray()
    val right = array.filter { it > pivot }.toIntArray()

    return getSortedArrayByQuickSort(left) + pivot + getSortedArrayByQuickSort(right)rkqt
}
This post is licensed under CC BY 4.0 by the author.

코틀린으로 배우는 함수형 프로그래밍 - 3장 재귀

코틀린으로 배우는 함수형 프로그래밍 - 5장 컬렉션으로 데이터 다루기