Language/Kotlin

[Kotlin 함수형 (1)] 람다식, 익명함수, 고차함수

728x90
반응형

함수형 프로그래밍 이란?

한마디로 요약하면

 

부수 효과가 없는 순수 함수를 1급 객체로 간주하여
파라미터로 넘기거나, 반환값으로 사용할 수 있으며,
참조 투명성을 지킬 수 있다.

 

더보기
더보기

여기서 부수효과(Side Effect)란 다음과 같은 변화 또는 변화가 발생하는 작업을 의미한다.

  • 변수의 값이 변경됨
  • 자료 구조를 제자리에서 수정함
  • 객체의 필드값을 설정함
  • 예외나 오류가 발생하며 실행이 중단됨
  • 콘솔 또는 파일 I/O가 발생함

그리고 1급 객체란 다음과 같은 것들이 가능한 객체를 의미한다.

  • 변수나 데이터 구조 안에 담을 수 있다.
  • 파라미터로 전달 할 수 있다.
  • 반환값으로 사용할 수 있다.
  • 할당에 사용된 이름과 무관하게 고유한 구별이 가능하다.

 

  • Java는 완벽한 객체지향 프로그래밍 언어이다.
  • (엄밀히 말하면 java 8 이후 함수형 프로그래밍, 람다, Stream Api 가 추가됨)
  • 따라서 모든 코드는 클래스를 설계하고 메서드를 만들어주고 클래스를 통해 객체를 생성해서 사용해야 한다. 
  • 반면, Kotlin은 함수만 만들어 사용하는 것(함수형 프로그래밍)을 지원한다. 
  • 코틀린은 함수 사용을 보다 편리하게 할 수 있도록 다양한 개념들이 제공된다.
fun main() {
    val r1 = testFun1(100, 200)
    println("r1 : $r1")

    val r2 = testFun2(100, 200)
    println("r2 : $r2")

    val r3 = testFun3(100, 200)
    println("r3 : $r3")

    val lambda1 : (Int, Int) -> Int = {a1:Int, a2:Int -> a1 + a2}

    val lambda2 = {a1:Int, a2:Int -> a1 + a2}

    val lambda3 : (Int, Int) -> Int = {a1, a2 -> a1 + a2}

    val r4 = lambda1(100, 200)
    println("r4 : $r4")

    val r5 = lambda2(100, 200)
    println("r5 : $r5")

    val r6 = lambda3(100, 200)
    println("r6 : $r6")

    val r7 = testFun4(100, 200)
    println("r7 : $r7")

    val lambda4 = {a1:Int, a2:Int ->
        val r1 = a1 + a2
        val r2 = a1 - a2

        r1 * r2
    }

    val r8 = lambda4(100, 200)
    println("r8 : $r8")
}

fun testFun1(a1:Int, a2:Int) : Int {
    return a1 + a2
}

fun testFun2(a1:Int, a2:Int) : Int = a1 + a2

fun testFun3(a1:Int, a2:Int) = a1 + a2

fun testFun4(a1:Int, a2:Int) : Int {
    val r1 = a1 + a2
    val r2 = a1 - a2
    val r3 = r1 * r2
    return r3
}


익명함수

  • 함수의 이름이 없는 함수이다.
  • 함수를 변수에 담아 관리할 때 사용한다.
  • 고차함수와 관련이 깊다.
fun main() {
    testFunction1()

    // val testFunction2 = testFunction1
    
    val testFunction2 = fun(){
        println("testFunction2 입니다")
    }

    testFunction2()
    
}

fun testFunction1() {
    println("testFunction1 입니다")
}

고차함수

  • 함수를 매개변수로 받거나, 반환 타입이 함수인 함수를 고차 함수라고 부른다.
  • 함수 호출 시 전달하는 함수와 반환 하는 함수는 람다식을 사용할 수도 있다.

 

 

1) 고차함수의 입력값으로 "익명함수"를 전달할 수 있다. 

 

익명함수를 t1 에 저장하고, 그 값을 입력으로 하는 고차함수 testFunc1을 호출한다. 

fun main() {

    val t1 = fun(x1:Int, x2:Int) : Int {
        return x1 + x2
    }

    testFunc1(t1, 100, 200)
}

fun testFunc1(m1:(Int, Int) -> Int, a1:Int, a2:Int){
    val r1 = m1(a1, a2)
    println("r1 : $r1")
}

근데 익명함수니까 아래 코드처럼.... 바로 익명함수를 고차함수의 입력 파라미터에 구현해도 된다. 

fun main() {

    testFunc1(fun(x1:Int, x2:Int) : Int {
        return x1 + x2
    }, 100, 200)
}

 

 

2) 고차함수의 입력값으로 "람다"식도 전달할수 있다. 

 

람다식을 t1에 저장하고, testFunc1 고차함수의 입력 파라미터로 전달한다.

fun main() {

    val t1 = {x1:Int, x2:Int -> x1 + x2}
    testFunc1(t1, 100, 200)
}

 

람다도 아래 코드처럼 .... 바로 람다를 고차함수의 입력 파라미터에 구현해도 된다. 

fun main() {

    testFunc1({x1:Int, x2:Int -> x1 + x2}, 200, 100)
}

 

 

3) 고차함수의 반환값을 "익명함수"로 정의할 수 있다. 

 

고차함수 testFunc2의 입력 파라미터는 없고,

출력 파라미터를 익명함수로 바로 정의했다. 

 

t2 에는 리턴값으로 정의한 익명함수가 전달되게되고, 

고차함수의 반환 값인.. t2 익명함수의 입력값을 100, 200 으로 준 함수를 r2라고 정의했다. 

fun main() {
    
    val t2 = testFunc2()
    
    val r2 = t2(100, 200)
    
    println("r2 : $r2")

}



fun testFunc2() : (Int, Int) -> Int {

    return fun(x1:Int, x2:Int) : Int {
        return x1 + x2
    }
}

 

4) 고차함수의 반환값 "람다"로 정의할 수 있다. 

 

고차함수 testFunc2의 입력파라미터는 없고, 

출력 파라미터로 람다식을 저의해주었다.

 

t2에는 고차함수 testFunc2의 반환값으로 정의된 람다식이 저장되고,

고차함수 반환값인 ...t2 람다식의 입력값으로 100과 200을 준 익명함수를 r2라고 하였다. 

fun main() {

    val t2 = testFunc2()
    
    val r2 = t2(100, 200)
    
    println("r2 : $r2")
}

fun testFunc2() : (Int, Int) -> Int {
    return {x1:Int, x2:Int -> x1 - x2}
}

 

5) 연습 - 고차함수를 사용하면 편한 예제 : it, Unit 등 사용

 

 

* 고차함수 매개변수의 람다식의 입력 매개변수가 한개 일 때,

입력 매개변수를 생략하고, 출력 매개변수를 it으로 표현

 

어떤 값이 입력으로 들어오면 그 입력값에 100을 더해서 출력하는 람다식을 만들었다. 

fun main() {

    testFunc4({x1:Int -> x1 + 100}, 200)
}

fun testFunc4(m1:(Int) -> Int, a1:Int){
    val r4 = m1(a1)
    println("r4 : $r4")
}

 

이때 입력 매개변수인 람다식의 입력값이 하나이면 그 입력값을 it으로 정의하여,

입력값을 생략하고, 출력값만 표현하여 람다식을 만들 수 있다. 

fun main() {

    testFunc4({it + 100}, 200)
}

fun testFunc4(m1:(Int) -> Int, a1:Int){
    val r4 = m1(a1)
    println("r4 : $r4")
}

 

* 고차함수 매개변수의 함수형 입력 매개변수를 맨뒤에 구현할 때,

고차함수 입력 중괄호 닫고, 뒤에 추가 함수로 표현 가능하다. 

 

함수를 받는 매개변수를 맨 뒤로 구현했을 경우는 다음과 같다. 

fun main() {

    testFunc5(100, 200, {x1:Int, x2:Int -> x1 + x2})
}

fun testFunc5(a1:Int, a2:Int, m1:(Int, Int) -> Int){
    val r5 = m1(a1, a2)
    println("r5 : $r5")
}

 

이때 마지막 람다식으로 들어갈 함수형 매개변수를

중괄호 닫고, 뒤에 추가로 구현해주어도 된다.  

 

 

이 기능을 지원하는 이유는

만약 입력 람다식이 매우 길 경우

다음 줄로 내려서 구현하도록 하는것을 지원하기 위해서 이다.  

fun main() {

    testFunc5(100, 200) {x1:Int, x2:Int ->
        x1 + x2
    }
}

fun testFunc5(a1:Int, a2:Int, m1:(Int, Int) -> Int){
    val r5 = m1(a1, a2)
    println("r5 : $r5")
}

 

 

 

*고차함수 매개변수의 함수형 매개변수의 반환값이 없는 경우,

Unit으로 표현 가능하다. 

 

fun main() {

    testFunc6({x1:Int -> println(x1)})
}

fun testFunc6(m1:(Int) -> Unit){
    m1(100)
}

 

앞에서 설명한것과 더해서 표현하면

매개변수가 하나밖에 없으니까 입력 매개변수 생략하고,  출력 매개변수를 it으로도 표현해보면 다음과 같다.

fun main() {

    testFunc6{println(it)}
}

fun testFunc6(m1:(Int) -> Unit){
    m1(100)
}

 

 

 

 

 

 

 

 

 

 

 

 

728x90
반응형