실무 프로젝트로 배우는 Kotlin & Spring/코틀린 고급

람다로 프로그래밍하기

webmaster 2022. 10. 22. 22:42
728x90

함수형 프로그래밍

  • 함수형 프로그래밍은 수학의 함수적 개념을 참고해 만든 패러다임의 하나로, 깔끔하고 유지보수가 용이한 소프트웨어를 만들기 위해 함수를 사용한다.
  • 함수형 패러다임은 부수효과가 없고, 똑같은 input이 들어오면 항상 같은 output을 내놓은 순수 함수의 개념을 기반으로 람다, 고차 함수, 커리, 메모이제이션, 모나드 등의 개념을 포함한다.

함수를 값으로 사용하기


fun main() {
    /*
    //코틀린에서 제공하는 함수형 프로그래밍
    val list = mutableListOf(printHello) //리스트에 값으로 함수를 넣을 수 있다.
    //list[0]() //함수를 실행할 때는 함수 실행 표기법인 ()를 반드시 써야한다
    val func: () -> Unit = list[0]
    func()
     */
    //call(printHello)
    //컴파일 오류: fun으로 선언한 객체는 일급 객체의 특성을 가지지 않기 때문에 데이터 구조에 넣거나, 함수 인자로 전달할 수 없다
    /*
    val func = printNo
    val list = mutableListOf(printNo)
    call(printNo)
     */
    /*
    val result = plus(1, 3, 5)
    println(result)
     */
    val list = listOf("a", "b", "c")
    val printStr: (String) -> Unit = { println(it) }
    //forEachStr(list, printStr)

    list.forEach(printStr) //list에서 제공하는 forEach도 고차함수이다.
    val upperCase: (String) -> String = {
        it.uppercase()
    }
    println(list.map(upperCase))
}

val func: () -> Unit = {} //함수도 타입이라 이와 변수를 선언하듯이 선언 가능하다

//코틀린에서 함수는 일급 객체로, 변수에 대입할 수 있고, 데이터 구조에 저장할 수 있다.
val printHello: () -> Unit = { println("Hello") }

//함수를 인자로 받아 실행하는 함수
fun call(block: () -> Unit) {
    block() //call 함수 내부에서, 인자로 받은 함수를 실행할 수 있다.
}

fun printNo() = println("No!")

val printMessage: (String) -> Unit = { message: String -> println(message) }
//val printMessage2: (String) -> Unit = { message -> println(message) } //message 타입 추론 가능,
//val printMessage3: (String) -> Unit = { println(it) } //message 참조를 it와 같은 내부 참조로 변경 가능

//다수의 인자를 받아 처리하는 함수
//val plus: (Int, Int) -> Int = { a, b -> a + b }
val plus: (Int, Int, Int) -> Int = { a, b, c -> a + b + c }

fun forEachStr(collection: Collection<String>, action: (String) -> Unit) {
    //함수를 인자로 받아 내부적으로 함수를 실행하는것을 고차함수라고 한다
    for (item in collection) {
        action(item)
        //println(item) //현재는 이 동작과 동일하다
    }
}
  • 함수형 프로그래밍에서, 함수는 1급 객체로 분류
    • 1급 객체 : 함수를 인자에 넘기거나, 변수에 대입하거나, 함수를 반환하는 개념
  • 함수도 타입이라 변수를 선언하듯이 사용이 가능하며, 함수는 값이 될 수도, 값은 함수가 될 수도 있으므로, 함수에 인자로 넘기거나 데이터 구조에 저장할 수 있다.
    • 리스트에 저장해서 사용 가능하다.
    • 변수로 받아 사용할 때는 반드시 ()를 사용해서, 함수를 실행시켜 주어야 한다.
    • fun으로 선언된 함수는 값으로 다룰 수 없으며, 이를 변수에 대입하거나, 값으로 다룰 수 없다.
  • 다수의 인자를 받아 처리하는 함수도 정의할 수 있다.
  • 고차 함수는 함수를 인자로 받거나 결과로 돌려주는 함수를 의미한다.
    • filter, map, foreach 함수 같이, 이미 여러 곳에서 많이 사용되고 있는 함수들은 모두 고차 함수이다.

익명 함수와 람다

package advanced

fun main() {
    /*
    outerFunc()() //익명 함수를 실행하기 위해서는 함수 표현식을 2번 써야한다.
    val func = outerFunc()
    func()
     */
    //후행 람다 전달 : 함수 전달 인자가 하나이거나, 파라미터가 가장 뒤에 있는경우, 함수를 호출하면서, 람다식을 구현체로 전달 할 수 있다.
    val list = listOf("a", "b", "c")
    forEachStr(list) {
        println(it)
    }

    arg1 {
        it.length
        it.first()
    }
    arg2 { a: String, b: String ->
        //it 키워드 사용 안된다
        a.length
        b.first()
    }
}

fun forEachStr(collection: Collection<String>, action: (String) -> Unit) {
    //함수를 인자로 받아 내부적으로 함수를 실행하는것을 고차함수라고 한다
    for (item in collection) {
        action(item)
        //println(item) //현재는 이 동작과 동일하다
    }
}


/*
fun outerFunc(): () -> Unit {
    return fun() { //이름 없는 익명 함수를 반환했다
        println("이것은 익명함수!")
    }
}
 */
/*
fun outerFunc(): () -> Unit {
    return { //람다식 변경
        println("이것은 익명함수!")
    }
}
 */
fun outerFunc(): () -> Unit = { println("이것은 익명함수!") } //람다 함수를 생략해서 구현

//람다 표현식의 전체 식
val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y }

//최대한 생략한 람다 식
val sum2 = { x: Int, y: Int -> x + y } //가독성을 판별해서, 적절하게 선택해서 사용하자

fun arg1(block: (String) -> Unit) {}
fun arg2(block: (String, String) -> Unit) {}
  • 함수의 이름이 없는 함수를 익명 함수라고 한다.
    • 익명 함수를 람다식으로 변경하면, 좀 더 가독성 좋고, 코드가 줄어든다
    • 단, 람다식은 많은 것을 추상화하기 때문에, 개발자가 선택해서, 가독성이 좋게 작성하는 것이 중요하다.
  • 함수의 마지막 인자가 함수인 경우 후행 람다 전달을 사용할 수 있다.
    • 가장 많이 사용해왔던 표현식으로, 함수의 정의를 따로 하지 않고, 람다식으로 바로 작성할 수 있다.
  • 람다 함수의 파라미터가 1개인 경우에만 it 키워드를 사용할 수 있다.
    • 2개 이상을 파라미터가 존재한다면, 파라미터를 명시적으로 작성해서 사용해야 한다

람다 레퍼런스 

fun main() {
    //val callReference: () -> Unit = { printHello() }
    val callReference = ::printHello //람다 레퍼런스 구문을 사용
    callReference()()

    val numberList = listOf("1", "2", "3")
    //numberList.map { it.toInt() }.forEach { println(it) }
    numberList.map(String::toInt).forEach(::println) //람다 페퍼런스를 사용해 쉽게 표현가능
    //탑레벨 함수일 경우 람다 레퍼런스 앞에 아무것도 없이 사용하면 되고, 확장함수나,클래스 맴버 함수일 경우 람다 레퍼런스 앞에 클래스 이름을 쓰면 된다.

    //코틀린에서는 애로우라는 함수형 라이브러리를 적용한다면, 쉽게 함수형 프로그래밍을 작성할 수 있다.
}

val printHello: () -> Unit = { println("Hello") }
  • 람다 레퍼런스를 사용하면 가독성 좋게 함수를 인자로 넘길 수 있다(::)
  • 탑-레벨, 로컬 함수는 ::연산자 앞에 아무것도 없이 ::함수명 을 사용하고, 클래스의 멤버, 확장 함수일 경우 ::연산자 앞에 클래스 지시자를 사용해 클래스::함수명을 사용한다.

애로우

https://arrow-kt.io/

 

Λrrow

Functional companion to Kotlin's Standard Library

arrow-kt.io

  • 코틀린에서 함수형 프로그래밍을 위한 라이브러리이다.
  • 함수형 프로그래밍의 다양한 개념 및 기능들을 제공하고, 점점 사용 사례가 증가하고 있다.
728x90

'실무 프로젝트로 배우는 Kotlin & Spring > 코틀린 고급' 카테고리의 다른 글

고급 예외처리  (0) 2022.10.22
스코프 함수  (0) 2022.10.22
페어와 구조분해할당  (0) 2022.10.16
지연 초기화  (0) 2022.10.16
제네릭  (0) 2022.10.16