본문 바로가기
깡샘 코틀린

05-1 람다 함수와 고차 함수

by 농농씨 2023. 6. 15.

람다 함수(람다식)

코틀린 뿐만 아니라 많은 프로그래밍 언어에서 익명함수(anonymous function)를 정의할 때 사용됨

고차함수는 매개변수나 반환값으로 함수를 이용하는데, 람다 함수는 주고받을 함수를 간단하게 정의할 때 사용함.

즉, 그 자체도 가치가 있지만 고차 함수를 이해하고 사용하려면 꼭 알아야 한다!

 

람다 함수 선언과 호출

일반적인 함수는 fun 키워드로 선언하지만, 람다함수는 중괄호 { } 이용

fun 함수명(매개변수) { 함수 본문 } // 함수 선언 형식
{ 매개변수 -> 함수 본문 } // 람다 함수 선언 형식

코틀린에서의 람다 함수 사용 규칙

  • 람다 함수는 { }로 표현함
  • { } 안에 화살표(->)가 있으며 화살표 왼쪽은 매개변수, 오른쪽은 함수 본문임
  • 함수의 반환값은 함수 본문의 마지막 표현식임
// 일반함수 선언
fun sum(no1: Int, no2: Int): Int { // 매개변수 있고 반환값이 Int타입인 함수
	return no1 + no2
}
 // 람다함수 선언
 val sum = {no1: Int, no2: Int -> no1 + no2} // 화살표 좌측은 매개변수, 우측은 함수 본문
 // 함수 이름도 없음. 변수 sum에 함수의 반환값인 no1 + no2의 값이 저장됨

그런데 람다 함수는 이름이 없으므로 함수명으로 호출할 수 없다. 그래서 보통은 변수에 대입해서 사용한다(위의 sum처럼)

하지만 항상 변수에 대입해서 사용해야 하는 건 아니다.

만약 람다 함수를 선언하자마자 호출하고자 한다면 다음과 같이 한다.

// 람다 함수를 선언과 동시에 호출하기
{no1: Int, no2: Int -> no1 + no2} (10, 20)
// 중괄호 부분이 람다함수, 소괄호가 호출문

함수는 어디선가 소괄호 ( )를 이용해 호출해 주어야 실행됨. 람다함수에 선언한 매개변수에 맞추어 인자 전달하면 됨~

 

함수에 매개변수가 항상 있어야 하는 것은 아니다. 매개변수 없으면 화살표(->) 왼쪽을 비워두면 된다. 화살표도 생략해도 됨.

// 매개변수 없는 람다함수
{-> println("function call")}
// 화살표도 생략한 람다 함수
{println("function call"))}

 

매개변수가 1개일 때는 매개변수를 선언하지 않아도 함수로 전달된 값을 다음과 같이 쉽게 이용할 수 있다.

it 키워드를 사용할 수도 있다!

// Int타입의 매개변수가 1개인 람다함수
fun main() {
	val some = {no: Int -> println(no)}
    some(10)
}

// it 키워드 사용
fun main() {
	val some: (Int) -> Unit = {println(it)}
    some(10)
}
// 두가지 표현방법 모두 눈에 익혀두자

it 키워드 사용예시에서, 람다함수 중괄호 안에 화살표가 없어서 매개변수가 없어보인다.

하지만 람다함수 앞에 (Int) -> Unit매개변수가 1개인 람다 함수임을 알려준다.(타입을 지정해서 그 타입의 변수가 하나로 지정되는건가)

이렇게 람다함수 매개변수가 1개일 때는 매개변수 선언을 생략하고 println(it) 처럼 it 키워드로 매개변수를 이용할 수 있다.

이때 만약 (Int) -> Unit 을 생략하면?

val some1 = {println(it)} // 타입을 식별할 수 없어 오류 남!!
val some2: (Int) -> Unit = {println(it)} // 성공!

람다함수에서 it 사용하는 건 해당 매개변수 타입을 식별할 수 있을 때에만 가능.

두번째 줄에서 람다 함수의 매개변수 타입을 Int로 선언해서 it이 가리키는 데이터가 Int 타입임을 알 수 있는 것이다.

 

 

람다 함수의 반환

람다함수도 함수이므로 결괏값을 반환해야 할수도 있다. 그런데 람다함수는 return문을 못쓴다!

그 대신, 람다함수의 반환값은 마지막 줄의 실행 결과이다.

// return 문으로 람다함수 반환하면 오류남
val some = {no1: Int, no2: Int -> return no1 * no2} // 오류!

// 그 대신 마지막줄 실행결과 반환하는 형식
fun main() {
	val some = {no1: Int, no2: Int -> // 람다함수 선언함
    	println("in lambda function")
    	no1 * no2 // 두 매개변수의 곱이 반환됨
    }
    println("result : ${some(10,20)}") // some이라는 객체에 인자를 전달하면서 람다함수 호출함
}
실행결과:
in lambda function
result : 200

 

 

함수 타입과 고차함수

코틀린에서는 함수를 변수에 대입해 사용할 수 있다.

그런데 변수는 타입을 가지며, 타입을 유추할 수 있을 때를 제외하고는 생략할 수 없다. 변수에 함수를 대입하려면 변수를 함수 타입으로 선언해야 한다.

 

함수 타입 선언

함수타입이란? 함수를 선언할 때 나타나는 매개변수와 반환 타입

// 일반 함수 선언
fun some(no1: Int, no2: Int) { // Int형 매개변수를 2개 받아서
	return no1 + no2 // 결괏값을 Int 타입으로 반환하는 함수
}

이를 (Int, Int) -> Int로 표현할 수 있다.

함수를 대입할 변수를 선언할 때 이러한 함수타입을 선언하고 그에 맞는 함수를 대입해야 한다.~

// 함수 타입을 이용해 함수를 변수에 대입
val some: (Int, Int) -> Int = {no1: Int, no2: Int -> no1 + no2}
// some은 함수를 대입한 변수! 콜론 뒤는 함수타입 선언!

 

타입 별칭 - typealias

함수타입을 typealias를 이용해 선언할 수 있다.

typealias란? 타입의 별칭을 선언한느 키워드로, 함수 타입뿐만 아니라 데이터 타입을 선언할 때도 사용함.

ex. 정수를 표현하는 코틀린의 타입은 Int 타입이지만, typealias를 이용해 정수를 표현하는 새로운 별칭을 선언할 수 있다.

변수보다 함수타입을 선언하는 데 주로 사용한다.

// 타입 별칭 선언과 사용
typealias MyInt = Int // Int타입에 별칭 선언
fun main() {
	val data1: Int = 10 // 기존 Int도 그대로 사용 가능
    val data2: MyInt = 10
}

// 함수 타입 별칭
typealias MyFunType = (Int, Int) -> Boolean // Int 타입 두개 전달받고 boolean값 반환하는 함수 형식

fun main() {
	val someFun: MyFunType = {no1: Int, no2: Int -> // 함수 타입을 지정할때 코드가 간편해질 수 있다!
    	no1 > no2
    }
    println(someFun(10,20)) // 람다함수의 객체를 호출함~
    println(someFun(20,10))
}

Q. MyInt와 Int는 호환이 안되나?

사실 Int처럼 이미 존재하는 거 말고 매개변수 여러개 전달받는 걸 타입별칭으로 축약한다든지 하는 활용으로 쓰긴 할듯

그러면...

 

매개변수 타입 생략

람다함수 정의할 때 매개변수의 타입을 유추할 수 있다면 타입선언을 생략할 수 있다.

// 매개변수 타입을 생략한 함수 선언
typealias MyFunType = (Int, Int) -> Boolean
val someFun: MyFunType = {no1, no2 -> // 타입별칭으로 지정한 타입이므로 매개변수 타입을 유추할 수 있어서
							// 람다함수 안에서의 매개변수의 타입 선언을 생략함!
	no1 > no2
}

이처럼 타입 유추에 따른 타입 생략 기법은 typealias를 이용할 때 뿐만 해당하는 것이 아니라 타입을 유추할 수 있는 상황이라면 어디서든 통한다.

// 매개변수 타입 선언 생략 예
val someFun: (Int, Int) -> Boolean = {no1, no2 -> // 람다함수의 매개변수 타입선언이 생략됨
	no1 > no2
}
// 반환형까지 변수 타입 지정할때 한번에 선언해줄 수 있다는 점 기억!

 위의 코드는 typealias를 이용하지 않았지만 someFun에 매개변수 타입을 Int로 선언했으므로 람다함수에서 매개변수의 타입을 생략할 수 있다.

또한 다음처럼 함수의 타입을 유추할 수 있다면 변수를 선언할 때 타입을 생략할 수도 있다.

// 변수 선언 시 타입 생략
val someFun = {no1: Int, no2: Int ->
	no1 > no2
}

위의 코드는 변수에 대입한 '함수'를 보면 타입을 얼마든지 유추할 수 있으므로 굳이 변수 선언부에 타입을 명시하지 않아도 된다.

(그니까... 원래는 변수에도 타입 지정하고 람다함수에도 매개변수의 타입 지정해야 하는데
둘 중 하나만 써도 타입이 유추되니까 나머지는 생략해도 된단 얘기인듯!)

 

 

고차함수(high order function)

함수를 매개변수로 전달받거나 반환하는 함수

일반적으로 함수의 매개변수나 반환값은 데이터이다. 그런데 데이터가 아닌 '함수'를 매개변수나 반환값으로 이용하는 함수를 고차함수라 한다. 앞서 보았듯 변수에 함수를 대입할 수 있기 때문에 함수를 매개변수나 반환값으로 이용할 수 있는 것이다!

// 고차 함수
fun hofFun(arg: (Int) -> Boolean): () -> String { 
// 'Int타입을 전달받고 Boolean 값을 반환하는 함수' 타입의 매개변수를 전달받아야 함(객체가 아니라 함수를 전달받는거 맞나)
// 그리고 반환타입은 '아무것도 전달받지 않고 String값을 반환하는 함수' 타입임
	val result = if(arg(10)) { // 전달받은 매개변수인 함수 arg가 boolean타입의 반환값을 가지며 
    						// 이를 if문을 이용하여 문자열을 변수에 저장
    	"valid"
    } else {
    	"invalid"
    }
    return {"hofFun result : $result"} // 문자열을 반환함
}
fun main() {
	val result = hofFun({no -> no > 0}) // '함수'타입의 매개변수를 인자로 전달해야하므로
    					// 중괄호가 인자에 사용되었음을 인지하기
  			// Int타입의 no를 매개변수로 하고 no > 0의 boolean값을 반환하는 람다'함수!!'를 
            // 함수 hofFun에 인자로 전달해서 
            // 그 반환값을 변수 result에 저장함
    println(result()) // 변수 result는 함수를 저장한 변수이므로 호출할 때는 생성자()를 함께 써줘야 함
}

 

'깡샘 코틀린' 카테고리의 다른 글

06-1 화면을 구성하는 방법  (2) 2023.06.15
05-2 널 안정성  (0) 2023.06.15
04-3 코틀린의 클래스 종류  (0) 2023.06.14
04-2 클래스를 재사용하는 상속  (0) 2023.06.14
04-1 클래스와 생성자  (0) 2023.06.13