본문 바로가기

프로그래밍/코틀린

[코틀린] 람다 표현식

람다식 또는 람다함수는 프로그래밍 언어에서 사용되는 개념이다.

익명함수라고도 부르며, 함수형 프로그래밍을 목적으로 하지 않더라도

간결함을 주 목적으로 사용된다.

 

함수형 프로그래밍에서는 람다 함수가 고차 함수의 매개변수나 반환값으로 사용되므로

더욱 중요하다.

 

코틀린에서 함수의 정의는 fun 예약어를 사용한다.

fun 함수이름(매개변수) { 함수내용 }

람다 함수는 fun과 함수이름을 명시하지 않고 축약형으로 선언한다.

{매개변수 -> 함수 내용}

람다 함수는 {}안에 매개변수와 함수 내용을 선언하는 함수로 아래와 같은 규칙을 정의한다.

1. 람다 함수는 항상 {}로 감싸서 표현한다.

2. {} 안에 -> 표시가 있으며 -> 왼쪽은 매개변수, 오른쪽은 함수 내용이다.

3. 매개변수 타입을 선언해야 하며 추론할 수 있을때는 생략할 수 있다.

예를 들어서) { x: Int, y: Int -> x + y }라고 할때

Int 타입을 추론할 수 있으면 {x, y -> x + y}로 생략이 가능하다는 말이다.

4. 함수의 반환값은 함수 내용의 마지막 표현식이다.

 

예를 들어서 람다 함수 사용 방법을 살펴보자.

아래의 소스코드는 일반 함수람다함수가 구현되어 있는 소스 코드이다.

둘다 두개의 매개변수 값이 들어가게 되고, 반환값으로 두 값을 더한 값을 반환한다.

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

val sum1 = {x1: Int, x2: Int -> x1 + x2}

fun main() {
    println("일반 함수 : ${sum(1,2)}")
    println("람다 함수 : ${sum1(3,4)}")
}
[결과값]
일반 함수 : 3
람다 함수 : 3

람다 함수는 위와 같이 fun 예약어를 사용하지 않고 함수 이름도 없으며, -> 기준으로 왼쪽으로는 매개변수들이 오른쪽에는 함수내용이 그리고 함수내용의 마지막값은 반환값으로 사용된다.

 

그럼 여기서 잠깐, 람다 함수를 이용하려면 위의 코드처럼 항상 변수에 대입해야할까?

대답은? 그렇지 않다.

 

람다 함수는 이름이 없는 익명 함수이므로, 변수에 대입하지 않으면 이후에 람다 함수를 사용할 수가 없다.

그런데, 람다 함수를 정의하고 바로 호출하면 변수에 대입하지 않고도 바로 호출이 가능하다.

fun main() {
    println("합계는 :${{x:Int, y:Int -> x + y}(1,2)}")
    run{println("바로 호출한다.")}
}
[결과값]
합계는 :3
바로 호출한다.

첫 번째는 {...}() 람다함수 뒤에 () 호출 구문을 추가해서 바로 호출한 예이다.

두 번째는 run()함수에 대입하여 바로 람다 함수를 호출 실행하는 예이다.

 

다음은 매개변수가 없는 람다 함수의 정의 하는 방법이다.

val sum2 = { -> 10 + 20 }
val sum3 = { 10 + 20 }

위와 같이 왼쪽이 매개변수이므로, 매개변수를 비워두면 된다.

이때 ->는 생략이 가능하다.

 

다음은 함수타입에 대해서 알아볼것이다.

우리는 변수를 선언할 때 아래와 같이 콜론(:)을 구분자로 오른쪽에 타입을 명시한다.

val name:String = "park"
val age:Int = 28

 

함수의 경우는 매개변수가 두개 있고, 반환값은 Boolean 타입으로 정의했다.

fun myFun(x1:Int, x2:Int):Boolean{
	return x1 > x2
}

 

람다함수 또한 함수 타입을 선언하고 그 타입에 맞는 람다 함수를 정의해 대입할 수 있다.

val lambdaFun: (Int, Int) -> Int = { x, y -> x + y }

fun main() {
    println("값은 : ${lambdaFun(10,20)}")
}
[결과값]
값은 : 30

(Int, Int) -> Int함수타입이 되며, { x, y -> x + y} 함수대입이된다.

 

함수 타입을 정의할 때 typealias 키워드를 이용해서 반복해서 사용되는 함수 타입을 쉽게 정의할 수도 있다.

typealias myType = (Int, Int) -> Int

val addFun:myType = {x, y -> x + y}
val minusFun:myType = {x, y -> x - y}
val multiFun:myType = {x, y -> x * y}

fun main() {
    println("값은 : ${addFun(10,20)}")
    println("값은 : ${minusFun(10,20)}")
    println("값은 : ${multiFun(10,20)}")
}
[결과값]
값은 : 30
값은 : -10
값은 : 200

typealias 키워드를 이용해서 myType이라고 (Int, Int) -> Int 함수 타입을 만들고

세가지의 람다함수에 함수타입으로 myType으로 선언하고 사용했다.

 

람다 함수를 정의할 때 매개변수가 하나일 때는

별도의 매개변수를 선언하지 않고 함수에서 it으로 매개변수를 지칭할 수 있다.

val lambdaFun3: (Int) -> Int = { x -> x + 10}
val lambdaFun4: (Int) -> Int = { it + 20 }
fun main() {
    println("값은 : ${lambdaFun3(10)}")
    println("값은 : ${lambdaFun4(10)}")
}
[결과값]
값은 : 20
값은 : 30

* 하지만 유의할 점은 함수 타입이 정의되어 있는 곳에서만 사용이 가능하다는 점이다.

 

람다 함수를 정의할 때 멤버 참조(Member Reference)를 이용하면 조금 더 쉽게 작성할 수 있다.

멤버 참조는 콜론 두개(::)을 이용하여 타입을 명시하는 기법으로 정확하게 리플렉션 기법이다.

 

람다 함수를 멤버 참조로 정의하려면 함수의 매개변수로 객체 타입을 하나 받고,

그 객체의 멤버를 그대로 반환해야한다.

 

fun main() {
    class User(val name:String, val age:Int)

    //일반적인
    val lambda1: (User) -> Int = {user:User -> user.age}

    //it을 이용한
    val lambda2: (User) -> Int = { it.age }

    //멤버 참조를 이용한
    val lambda3: (User) -> Int = User::age

    println(lambda1(User("park",28)))
    println(lambda2(User("kim",29)))
    println(lambda3(User("lee",30)))
}
[결과값]
28
29
30

 첫 번째, 일반적으로 타입을 모두 명시하는 경우

(User) -> Int라는 함수 타입을 선언하고, 함수 대입의 내용에 user:User -> user.age를 선언하고 있다.

 

두 번째, 하나의 매개변수가 넘어오기에 it으로 대체가 가능하고 it을 이용하여 it.age로 선언하고 있다.

 

마지막으로 멤버 참조는 User::age를 함수 대입으로 받고 있는데

User 타입의 매개변수 하나를 받고, 함수 내에서 해당 객체의 멤버를 그대로 반환하는 함수의 축약형으로 보면 된다.