본문 바로가기
개발/Kotlin

[코틀린] 함수형 프로그래밍 - 고차 함수, 람다식

by 윤호 2021. 12. 10.

함수형 프로그래밍이란

함수형 프로그래밍(FP)

순수 함수를 작성하여 프로그램의 부작용을 줄이는 기법

정의와 특징

  • 순수 함수를 사용해야 한다.
  • 람다식을 사용할 수 있다.
  • 고차함수를 사용할 수 있다.

순수 함수

같은 인자에 대하여 항상 같은 값을 반환한다 (부작용이 없는 함수)

함수 외부의 어떤 상태도 바꾸지 않는다

람다식

수학에서 람다 대수는 이름이 없는 함수로 2 개 이상의 입력을 1개의 출력으로 단순화한다는 개념

FP에서는 다음을 의미

  • 다른 함수의 인자로 넘기는 함수
  • 함수의 결괏값을 반환하는 함수
  • 변수에 저장하는 함수

일급 객체에 이름이 없는 경우, 이를 '람다식' 함수 또는 '람다식'이라 부름

일급 객체

FP에서는 함수를 일급 객체로 생각한다

일급 객체의 특징

  • 일급 객체는 함수의 인자로 전달할 수 있다
  • 일급 객체는 함수의 반환값에 사용할 수 있다
  • 일급 객체는 변수에 담을 수 있다

고차 함수

다른 함수를 인자로 사용하거나 함수를 결과값으로 반환하는 함수를 의미

고차 함수와 람다식

고차 함수의 형태

인자에 일반 함수 사용, 반환값에 일반 함수 사용

fun main(){
    val res2 = mul(sum(3,3), 3)
    funFunc()
}

fun sum(a: Int, b: Int) = a + b
fun mul(a: Int, b: Int) = a * b

fun funcFunc(): Int{
    return sum(2,2)
}

변수에 할당하는 람다식 함수

val multi = {x: Int, Int -> x * y} // 일반 변수에 람다식 할당
val result = multi(10, 20) // 람다식이 할당된 변수는 함수처럼 사용 가능

함수 내용에 표현식이 2줄 이상이라면 마지막 표현식이 반환값이 됨

val multi2: (Int, Int) -> Int = {x: Int, y: Int ->
		println("x * y")
		x * y // 마지막 표현식 반환
}

람다식 선언 자료형, 매개변수 생략

val multi: (Int, Int) -> Int = {x: Int, y: Int, x*y} // 생략되지 않은 전체 표현ㅍ

val multi = {x: Int, y: Int -> x * y} // 선언 자료형 생략
val multi: (Int, Int) -> Int = {x, y -> x * y} // 람다식 매개변수 자료형의 생략

val multi = {x, x -> x * y} // 오류 : 추론 불가능함

반환 자료형이 없는 경우, 매개변수가 하나인 경우, 람다식 안에 람다식인 경우

val greet: () -> Unit = {println("Hello World!")}
val greet = {println("Hello World!") // 자료형 생략 가능

val square: (Int) -> Int = {x -> x * x}
val square = {x: Int -> x * x} // x의 자료형을 명시해야 추론 가능

val nestedLambda: () -> () -> Unit = {{println("nested")}}
val nestedLambda = {{println("nested")}} // 자료형 생략 가능

람다식과 고차 함수 호출하기

이름에 의한 람다식 호출

fun main() {
    val result = callByName(otherLambda) // 람다식 이름으로 호출
    println(result)
}

fun callByName(b: () -> Boolean): Boolean { // 람다식 함수 자료형으로 선언된 매개변수
    println("callByName function")
    return b()
}

val otherLambda: () -> Boolean = {
    println("otherLambda function")
    true
}

람다식 자체가 매개변수 b에 복사되고, 사용되기 전까지는 람다식이 실행되지 않음.

callByName 함수에서 b를 실행할 때 비로소 람다식이 실행됨.

이름에 의한 호출 방법을 통해 필요할 때만 람다식이 작동하도록 만들 수 있다.

참조에 의한 호출 방식으로 일반 함수 호출하기 - 콜론 두 개(::)로 일반 함수를 람다식처럼 사용할 수 있다.

fun main() {
    // 1. 인자와 반환 값이 있는 함수
		// val res1 = funcParam(3, 2, sum) 오류 : sum은 람다식이 아님
    val res1 = funcParam(3, 2, ::sum)

    // 2. 인자가 없는 함수
    hello(::text) // 반환값이 없음
    hello({ a, b -> text(a, b) }) // 위의 식과 동일한 표현
    hello { a, b -> text(a, b) } // 위의 식과 동일한 표현, 소괄호 생략 가능

    // 3. 일반 변수에 값처럼 할당
    val likeLambda = ::sum
}

fun sum(a: Int, b: Int) = a + b

fun text(a: String, b: String) = "Hi! $a $b"

fun funcParam(a: Int, b: Int, c: (Int, Int) -> Int): Int {
    return c(a, b)
}

fun hello(body: (String, String) -> String): Unit {
    println(body("Hello", "World"))
}

람다식의 매개변수

매개변수가 1개 있는 람다식 - it으로 매개변수를 대체 가능

onParam({a -> "Hello World! $a"})
onParam {"Hello World! $it"} // 화살표 표기를 생략하고 it으로 대체할 수 있음, 소괄호 생략 가능

2 개 이상의 람다식을 사용하는 경우

twoLambda({a, b -> "First $a $b"}, {"Second $it"})
twoLambda({a, b -> "First $a $b"}) {"Second $it"} // 소괄호 분리 가능

일반 매개변수와 람다식 매개변수 같이 사용하기

withArgs("Arg1", "Arg2", {a, b -> "Hello World! $a $b})
// 함수 인자의 마지막이 람다식인 경우 소괄호 분리 가능
withArgs("Arg1", "Arg2") {a, b -> "Hello World! $a $b}

 


reference : Doit! 코틀린 프로그래밍

댓글