본문 바로가기

Study/Kotlin Study

[Kotlin] 함수형 프로그래밍

+ 항공대학교 김철기 교수님의 객체 지향 프로그래밍 과목 내용를 정리한 글입니다.

고차 함수

- 함수를 파라미터로 받는 함수이다.

fun initializeIntArray(n: Int, initializer: (Int) -> Int): IntArray {
    // initializer라는 (Int) -> Int 함수 타입의 변수를 파라미터로 받는다.
    // (Int) -> Int는 Int를 파라미터로 받아서 Int를 반환하는 함수 타입

    val arr = IntArray(n) // 0으로 초기화 된 n개의 정수를 갖는 배열

    for (i in 0 until n) {
        arr[i] = initializer(i)
        // initializer 함수에 i를 넣은 값을 arr[i]에 저장
    }
    return arr
}

fun main() {
    val squares_1 = IntArray(5) {it * it} // 각 요소가 인덱스의 제곱으로 초기화
    val squares_2 = IntArray(5, {it -> it * it})
    // {it -> it * it} 라는 람다 함수가 IntArray() 생성자의 두 번째 파라미터
    // {it -> it * it}는 it를 인자로 받아서 it * it 값을 반환하는 람다 함수

    println(squares_1.joinToString()) // 0, 1, 4, 9, 16
    println(squares_2.joinToString()) // 0, 1, 4, 9, 16

    val squares_3 = initializeIntArray(5, {it -> it * it})
    println(squares_3.joinToString()) // 0, 1, 4, 9, 16
    val squares_4 = initializeIntArray(5) {it -> it * it} // 인자를 분리할 수 있다.
    println(squares_3.joinToString()) // 0, 1, 4, 9, 16
}

고차 함수의 예 (1)

- 정수 배열의 요소들 중 조건에 만족하는 요소의 개수를 반환하는 함수

=> 바뀐게 크지 않은 비슷한 함수를 여러 개 생성하여 낭비가 크다.

fun countEven(arr: IntArray): Int { // 짝수 개수
    var count = 0

    for (elem in arr) {
        if (elem % 2 == 0) count ++
    }
    return count
}

fun countMinus(arr: IntArray): Int { // 음수 개수
    var count = 0

    for (elem in arr) {
        if (elem < 0) count ++
    }
    return count
}

fun countZero(arr: IntArray): Int { // 0의 개수
    var count = 0

    for (elem in arr) {
        if (elem == 0) count ++
    }
    return count
}

fun main() {
    val arr = intArrayOf(-2, -1, 0, 1, 2, 3, 4, 5)
    println(countEven(arr)) // 4
    println(countMinus(arr)) // 2
    println(countZero(arr)) // 1
}

 

- 고차 함수를 이용하여 여러 함수를 통합하고 낭비를 줄일 수 있다.

fun countIntarray(arr: IntArray, isTrue: (Int) -> Boolean): Int {
    // isTrue 함수를 파라미터로 받는다.
    // isTrue: Int형을 인자로 받아서 Boolean을 반환하는 함수

    var count = 0 // 조건에 만족하는 요소 개수

    for (elem in arr) {
        if (isTrue(elem)) count ++
    }
    return count
}

fun main() {
    val arr = intArrayOf(-2, -1, 0, 1, 2, 3, 4, 5)
    println(countIntarray(arr, {elem -> elem % 2 == 0})) // 4
    println(countIntarray(arr, {elem -> elem < 0})) // 2
    println(countIntarray(arr, {elem -> elem == 0})) // 1
}

고차 함수의 예 (2)

- 정수 배열의 요소들을 모두 연산한 값을 반환하는 함수

=> 바뀐게 크지 않은 비슷한 함수를 여러 개 생성하여 낭비가 크다.

fun sum(numbers: IntArray): Int { // 배열의 요소들의 합을 구하는 함수
    var result = 0

    for (elem in numbers) {
        result += elem
    }
    return result
}

fun prod(numbers: IntArray): Int { // 배열의 요소들의 곱을 구하는 함수
    var result = 1

    for (elem in numbers) {
        result *= elem
    }
    return result
}

fun max(numbers: IntArray): Int { // 배열의 요소들 중 가장 큰 값을 구하는 함수
    var result = Int.MIN_VALUE // Int로 표현할 수 있는 가장 작은 값

    for (elem in numbers) {
        result = if (result > elem) result else elem // 더 큰 값으로 갱신
    }
    return  result
}

fun main() {
    val arr = intArrayOf(-2, -1, 1, 2, 3, 4, 5)
    println(sum(arr)) // 12
    println(prod(arr)) // 240
    println(max(arr)) // 5
}

 

- 고차 함수를 이용하여 여러 함수를 통합하고 낭비를 줄일 수 있다.

fun aggregate(arr: IntArray, initvalue: Int, op: (Int, Int) -> Int): Int {
    var result = initvalue

    for (elem in arr) {
        result = op(result, elem)
    }
    return result
}

fun main() {
    val arr = intArrayOf(-2, -1, 1, 2, 3, 4, 5)
    println(aggregate(arr, 0, {result, elem -> result + elem})) // 12
    println(aggregate(arr, 1, {result, elem -> result * elem})) // 240
    println(aggregate(arr, Int.MIN_VALUE, { result, elem -> if (result > elem) result else elem})) // 5
}

함수의 타입

- op라는 파라미터 함수는 Int와 Int를 첫 번째와 두 번째 인자로 받고, Int 값을 반환한다.

fun aggregate(arr: IntArray, initvalue: Int, op: (Int, Int) -> Int)

 

- 반환 값 앞에 -> 를 사용한다. (':' 대신)

 

- 함수의 인자가 여러 개일 경우, 선언 시에 꼭 괄호를 붙여야 한다.

(사용 시에는 괄호를 사용하지 않는다.)

 

- 반환 값이 없을 때-> Unit 을 사용하여 반드시 명시해야 한다.

fun decorativePrintArray(arr: IntArray, prt: (Int) -> Unit)

 

- 파라미터에 이름을 붙이는 것도 가능하다. (프로그래머에게 힌트 제공)

fun aggregate(arr: IntArray, initvalue: Int, op: (resultSoFar: Int, nextValue: Int) -> Int): Int

람다 함수

- resultSoFar와 nextValue를 파라미터로 갖는 람다 함수

{resultSoFar, nextValue -> resultSoFar + nextValue}

- resultSoFar와 nextValue의 타입은 함수의 파라미터 선언에 명시되기 때문에 명시하지 않는다. (타입 추론)

- 반환 값의 타입도 파라미터의 선언으로부터 유추 가능하므로 명시하지 않는다.

 

- 다른 인자를 분리하여 람다 함수 앞에서 괄호를 닫고, 그 뒤에 람다 함수를 작성하는 것이 더 바람직하다.

(가독성이 더 높다.)

 

 

- 람다 함수의 마지막 문장의 식이 람다 함수의 결과 값이다.

fun aggregate(arr: IntArray, initvalue: Int, op: (resultSoFar: Int, nextValue: Int) -> Int): Int {
    var result = initvalue

    for (elem in arr) {
        result = op(result, elem)
    }
    return result
}

fun main() {
    val arr = intArrayOf(-2, -1, 1, 2, 3, 4, 5)
    println(aggregate(arr, 0) {result, elem ->
        println("$result += $elem")
        result + elem}) // 12
}

단순한 파라미터의 람다 함수

- 파라미터가 없는 람다 함수는 -> 기호의 생략이 가능하다.

fun measureTime(action: () -> Unit): Long { // 작업의 실행 시간을 측정
    val start = System.nanoTime() // 현재 시간을 ns단위로 측정
    action() // 작업 실행
    return System.nanoTime() - start // 작업 후 시간과 차이 반환
}

fun main() {
    val time = measureTime {1 + 2} // 파라미터가 없는 람다 함수이므로 -> 기호 생략
    println(time)
}

 

- 파라미터가 하나인 람다 함수는 ->를 생략할 경우 파라미터를 it이라고 지칭한다.

val arr1 = IntArray(10) {n -> n * n}
val arr2 = IntArray(10) {it * it} // 위 식을 간략화

람다 함수 내의 사용하지 않는 파라미터

- 사용하지 않는 파라미터는 밑줄 기호(_)로 지정한다.

fun check(s: String, condition: (Int, Char) -> Boolean) : Boolean {
    // 문자열 s의 각 문자들 중 condition을 만족하는 문자가 있다면 false를 반환
    for (i in s.indices) {
        if (condition(i, s[i])) return false
    }
    return true
}

fun main() {
    println(check("Hello") { _, c -> !c.isLetter() }) // 글자인지 체크 (i가 필요 없다.)
    println(check("Hello") { i, c -> i != 0 && c.isUpperCase() }) // 첫 글자외 문자가 소문자라면 true
    // 첫 글자를 조건에서 제외하기 위해 i 사용
}

익명 함수 (람다 함수의 대안)

- fun 예약어를 이용하여 함수 이름만 제외하여 함수를 선언한다.

 

- 람다 함수의 형태

val total = aggregate(array) {res, elm -> res + elm}

 

- 익명 함수의 형태

val total = aggregate(array, fun(res, elm) = res + elm)

또는

val total = aggregate(array, fun(res, elm): Boolean {return res + elm})

 

- 익명 함수는 맨 뒤 파라미터라 해도 괄호를 미리 닫을 수 없다.

val total = aggregate(array) fun(res, elm): Boolean {return res + elm} // 불가

이미 존재하는 함수를 파라미터로 넘기기

- 이미 add라는 함수가 존재할 때, ::를 사용하여 함수의 파라미터로 add 함수 자체를 넘길 수 있다.

fun aggregate(arr: IntArray, initvalue: Int, op: (Int, Int) -> Int) :Int {
    var result = initvalue

    for (elem in arr) {
        result = op(result, elem)
    }
    return result
}

fun add(a: Int, b: Int): Int {
    return a + b
}

fun main() {
    val arr = intArrayOf(1, 2, 3, 4, 5)
    println(aggregate(arr, 0) {result, elm -> add(result, elm)})
    println(aggregate(arr, 0, ::add))
}

'Study > Kotlin Study' 카테고리의 다른 글

[Kotlin] 영역 함수  (0) 2023.10.09
[Kotlin] 확장  (0) 2023.10.09
[Kotlin] 객체 (Object)  (0) 2023.09.17
[Kotlin] 단순한 변수 이상인 프로퍼티  (0) 2023.09.17
[Kotlin] 널 가능성  (0) 2023.09.12