Do it! 코틀린 프로그래밍

YW K·2023년 4월 14일

Kotlin

목록 보기
1/2

Chapter 03. 함수와 함수형 프로그래밍

03-3 고차 함수와 람다식

1) 고차 함수의 형태

  • 일반 함수를 인자나 반환값으로 사용하는 고차 함수

(1) 인자에 일반 함수 사용

package chap03.section3.funcargs

fun main() {
	val res1 = sum(3, 2) // 일반 인자
    val res2 = mul(sum(3, 3), 3) // 인자에 함수를 사용
    
    println("res: $res1, res2: $res2")
}

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

(2) 반환값에 일반 함수 사용

package chap03.section3.funcargs

fun main() {
	println("funcFunc: ${funcFunc()}")
}

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

fun funcFunc() : Int { // 함수의 반환값으로 함수 사용
	return sum(2, 2)
}

(3) 변수에 할당하는 람다식 함수 작성하기

  • 람다식도 일종의 함수이기 때문에 람다식 자체를 일반 변수에 사용할 수 있다.
package chap03.section3

fun main() {
	var result: Int
    
    val multi = {x: Int, y: Int -> x * y} // 일반 변수에 람다식 할당
   
  	result = multi(10, 20) // 람다식 할당된 변수는 함수처럼 사용 가능 
    // 변수이지만 두 개의 인자가 필요하다.
    println(result)
}

2) 람다식의 구성

(1) 변수에 지정된 람다식

(2) 표현식이 2줄 이상일 때

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

📌 어떤 결과가 실행될까?

fun main() {
	val result: Int
    
    val multi = {x: Int, y: Int ->
    	println("x * y")
        x // 마지막 표현식이 반환되기 때문에 x를 작성해도 아무런 문제가 발생하지 않음
        x + y // 반환식이 2줄 이상일 경우 마지막 표현식이 반환됨
   	}
   
   	// multi는 변수의 이름이고, 실제 람다식은 이름이 없는 익명 함수이다. 
   	result = multi(10, 20) 

    println(result)
}

🔑 실행결과

x + y
200

(3) 자료형의 생략

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, y -> x * y} // 에러 발생! 자료형 추론이 가능하지 않음

(4) 반환 자료형이 없거나 매개변수가 하나 있을 때

// 반환 자료형이 없기 때문에 소괄호()에 아무것도 쓰여있지 않다.
// 전달한 인자가 없기 때문에 화살표 또한 사라진다.  
val greet: () -> Unit = { println("Hello World") }

// 매개변수가 하나 있을 때 
val square: (Int) -> Int = { x -> x * x }

📌 주의하기

val greet: () -> Unit = { println("Hello") }

println(greet) 
greet()

🔑 실행결과

// greet가 가지고 있는 Hello가 출력되는 게 아니라 람다식 자체가 나온다. 
// 인자가 없고 반환 데이터의 자료형이 Unit라는 람다식이 들어있음을 코틀린이 알려준다.

() -> kotlin.Unit
Hello

// greet 변수에 들어있는 처리문만 수행한 다음에 아무것도 반환하지 않고 여기서 끝난다. 

(5) 람다식 안에 람다식이 있는 경우

val nestedLambda: () -> () -> Unit = { { println("nested") } }

📌 주의하기

val nestedLambda: () -> () -> Unit = { { println("World") } }

nestedLambda() // 함수 안에 또다른 함수가 있는 것으로 생각해야 한다.
nestedLambda()() // 따라서 호출하기 위해서는 괄호는 두 번 입력해주어야 한다. 

🔑 실행 결과


World!

(6) 선언부의 자료형 생략

val greet = { println("Hello World") } // 추론 가능
val square = { x: Int -> x * x } // 선언 부분을 생략하려면 x의 자료형을 명시해야 함
val nestedLambda = { { println("nested") } } // 추론 가능

(7) 매개변수에 람다식 함수를 이용한 고차 함수

fun main() {
	var result: Int
    
    result = highOrder({ x, y -> x + y}, 10, 20)
    println(result)
}

// 매개변수의 자료형 자체가 람다식 형태 
fun highOrder(sum: (Int, Int) -> Int, a: Int, b: Int): Int {
	return sum(a, b) 
}

🔑 실행 결과

30

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

(1) 값에 의한 호출

  • 함수가 인자로 전달될 경우
    • 람다식 함수는 으로 처리되어 그 즉시 함수가 수행된 후 값을 전달
fun main() {
	val result = callByValue(lambda()) // 람다식 함수를 호출
   	println(result)
}

fun callByValue(b: Boolean): Boolean { // 일반 변수 자료형으로 선언된 매개변수
	println("callByValue function")
    return b
}

val lambda: () -> Boolean = { // 변수 lambda
	println("lambda function") // 람다 표현식이 두 줄
    true // 마지막 표현식 문장의 결과가 반환
}

🔑 실행 결과

lambda function
callByValue function
true

(2) 람다식 이름을 사용한 호출 이용하기

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
}

📌 비교해보기

  • callByValue(b: Boolean): Boolean VS callByName(b: () -> Boolean): Boolean

🔑 실행 결과

callByName function
otherLambda function
true

🔥 위 두 개의 코드 차이점 잘 기억하기!!!

  • 이름만으로 호출하게 되면 해당 이름이 람다식 자체가 복사가 되어 람다식이 사용되는 시점에 작동한다는 점

(3) 다른 함수의 참조에 의한 호출

fun sum(x: Int, y: Int) = x + y // 람다식이 아니다. 

funcParam(3, 2, sum) // 오류! sum은 람다식이 아니다!

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

// sum이 람다식이 아니기 때문에 그대로 호출하게 되면 오류가 발생한다. 
// 따라서 sum을 람다식처럼 호출시키기 위해 참조 방식으로 ::를 사용한다.
funcParam(3, 2, ::sum)

(4) 참조에 의한 호출 방식으로 함수 호출하기

fun main() {
	// 1. 인자와 반환값이 있는 함수
	val res1 = funcParam(3, 2, ::sum)
    println(res1)
    
    // 2. 인자가 없는 함수
    hello(::text) // 반환값이 없음
    
    // 3. 일반 변수에 값처럼 할당
    val likeLambda = ::sum // likeLamvda를 함수처럼 사용 가능
    println(likeLambda(6, 6))
}

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

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

// 람다식 형태로 되어있는 매개변수 c
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"))
}

4) 람다식 함수의 매개변수

(1) 매개변수 개수에 따라 람다식을 구성하는 방법

  • 매개변수가 없는 경우
fun main() { 
	// 매개변수 없는 람다식 함수
	noParam({ "Hello World" })
    noParam { "Hello World" } // 위와 동일 결과, 소괄호 생략 가능
}

// 매개변수가 없는 람다식 함수가 noParam 함수의 매개변수 out으로 지정됨
fun noParam(out: () -> String) = println(out())

🔑 실행 결과

Hello World!
Hello World!
  • 매개변수가 한 개인 경우
fun main() {
	// 매개변수가 없는 람다식 함수
    
    // 매개변수가 하나 있는 람다식 함수
	oneParam( { a -> "Hello World! $a" } )
    oneParam  { a -> "Hello World! $a" } ) // 소괄호 생략 가능
    oneParam {"Hello World! $it } // 위와 동일 결과, it으로 대체 가능
}

// 매개변수가 하나 있는 람다식 함수가 oneParam함수의 매개변수 out으로 지정됨
fun oneParam(out: (String) -> String) {
	println(out("OneParam"))
}
  • 인자가 하나만 있는 경우 인자와 화살표를 생략하고 처리구문은 it으로 대체할 수 있다. it이 받아드리고자 하는 인자 하나를 가리키고있다. 그렇기에 인자가 하나인 경우 조금 더 편리하게 사용가능하다.

  • 매개변수가 두 개 이상인 경우

fun main() {
	moreParam { a, b -> "Hello World! $a $b"} // 매개변수명 생략 불가
}

fun moreParam(out: (String, String) -> String) {
	println(out("OneParam", "TwoParams"))
}

  • 매개변수를 생략하는 경우
// 언더바(_)를 통해 비워둔다. 
moreParam { _, b -> "Hello World! $b"} // 첫 번째 문자열은 사용하지 않고 생략
  • 일반 매개변수와 람다식 매개변수를 같이 사용
fun main() {
	// 인자와 함께 사용하는 경우
	withArgs("Arg1", "Args2", { a, b -> "Hello World! $a $b" })
    // withArgs()의 마지막 인자가 람다식인 경우 소괄호 바깥으로 분리 가능
    withArgs("Args1", "Args2) { a, b -> "Hello World! $a $b" }
}

// withArgs 함수는 일반 매개변수 2개를 포함, 람다식 함수를 마지막 매개변수로 가짐
fun withArgs(a: String, b: String, out: (String, String) -> String) {
	println(out(a, b))
}

🔥 마지막에 람다식이 구성되어 있다면, 바깥으로 빼낼 수 있다.

(2) 두 개의 람다식을 가진 함수의 사용

fun main() {
	twoLambda( { a, b -> "First $a $b" }, {"Second $it"} )
    twoLambda( { a, b -> "First $a $b" }) {"Second $it"} // 위와 동일
}

// 매개변수로서 람다식이 두 개 
fun twoLambda(first: (String, String) -> String, second: (String) -> String) {
	println(first("OneParam", "TwoParam"))
    println(second("OneParam"))
}

0개의 댓글