본문 바로가기
프로그래밍/코틀린

K017. 코틀린 함수(Functions) 깊게 살펴보기 - Functions 파트8, 람다(Lambdas) 3

by K-인사이터 2024. 3. 7.
반응형

안녕하세요

K-IN 입니다.

 

코틀린에 대해서 알아보겠습니다. 

전체 강의 목록은 아래의 링크를 클릭해주세요.

 

K000. 코틀린 시리즈 (연재물)

안녕하세요 K-IN 입니다. 요즘 코틀린을 이용한 개발 프로젝트가 늘어나고 있습니다. 이에, 코틀린에 대해서 상세하게 정리하는 간행물을 제작하고자 합니다. 여기에 있는 링크들은 모두 코틀린

k-in.tistory.com

 

 

코틀린 함수(Functions) 

코틀린에서 함수에 대해서 이해하려면 아래의 개념들을 숙지해야합니다. "☆" 표기는 중요도를 나타냅니다.

분량이 많아 이글에서는 12번 항목까지만 다룹니다. 

 

이전 내용 코틀린 함수를 자세히 배우려면 아래의 링크들을 참고해주세요.

  1. 기본적인 함수 구조 ☆
  2. 함수 사용 방법 ☆
  3. 기본 인자(Default Arguments) ☆☆
  4. 이름지정 인자(Named Arguments) ☆☆
  5. 단일 표현식 함수(Single-expression Functions) ☆☆☆
  6. 명시적 리턴 타입(Explicit Return Types)
  7. 가변인자 (varargs) ☆☆☆
  8. 중위 표기법 (Infix Notation) ☆☆☆
  9. 함수의 범위 (Function Scope) ☆☆☆
  10. 제너릭 함수 (Generic Functions) ☆☆☆
  11. 꼬리 재귀 함수 (Tail Recursive Functions) ☆☆
  12. 람다 (Lambdas) ☆☆
  13. 인라인 함수 (Inline Functions) ☆☆☆
  14. 연산자 오버로딩 (Operator overloading) ☆☆
  15. 빌더 (Builders) ☆☆

함수타입 인스턴스 호출

함수 타입의 값(=인스턴스)는 invoke 연산자를 이용해 호출할 수 있습니다. 

f.invoke(x) 또는 단순히 f(x) 와 같이 호출이 가능합니다. 

 

// 함수 타입 변수를 정의 
val stringPlus: (String, String) -> String = String::plus

// invoke 연산자를 이용한 호출 
println(stringPlus.invoke("<-", "->"))
// 일반 함수처럼 호출
println(stringPlus("Hello, ", "world!"))

 

리시버 타입을 가지는 함수 타입의 값이 있고 이를 호출한다면 반드시 리시버 객체가 첫번째 인자로 전달되어야 합니다. 

또다른 방식으로 리시버와 같은 타입의 확장함수인 것처럼 수신자 객체를 앞에 붙이는 방식입니다. 

 

예시를 보면서 이해해보겠습니다. 

// Int 타입을 리시버로 가지는 함수 타입 변수
val intPlus: Int.(Int) -> Int = Int::plus

// invoke 를 통해 호출되며 리시버 타입이 첫번째 인자로 위치 
println(intPlus.invoke(1, 1))
// 일반적인 방식으로 호출되며 리시버 타입이 첫번째 인자로 위치 
println(intPlus(1, 2))
// 리시버 타입과 일치하는 타입에 대해 확장 함수 처럼 호출 가능
println(2.intPlus(3))

 

람다 표현식

람다 표현식과 익명함수는 그자체로 함수 리터럴(literals)입니다. 

함수 리터럴이란 앞서 시간에서 설명하였듯이 함수 표기법입니다. 

 

이를 더욱 쉽게 해설하면, 함수를 정의(declare)하지 않고 표현식처럼 즉시 전달이 가능한 형태를 말합니다. 

리터럴이라는 용어는 생소하지만 다음의 예시를 보면 이해가 쉽습니다. 

// compare 함수의 정의입니다. 
fun compare(a: String, b: String): Boolean = a.length < b.length

// 이 함수는 정의 후에 아래처럼 사용이 가능할 것입니다. 
// 즉, 한번 정의후에 참조를 통해서 전달하는 것입니다. 
max(strings, ::compare)

// 함수 리터럴은 그자체로 표현식으로 전달이 가능함을 말하며 정의가 필요하지 않습니다. 
// 람다 표현식은 그자체로 함수 리터럴이므로 아래처럼 정의를 생략하고 전달이 가능합니다. 
max(strings, { a, b -> a.length < b.length })

 

max 함수는 두번째 인자로 람다 표현식을 인자로 받으며 이를 고차함수(high-order functions)라고 합니다. 

고차함수는 쉽게 말해 함수를 인자로 받는 함수를 말합니다. 

 

람다 표현식 문법

람다 표현식의 전체적인 구문의 형태는 다음을 참고합니다. 

여기서 람다 표현식의 구체적인 규칙을 파악할 수 있습니다. 

  • 람다 표현식은 항상 {} (대괄호, curly braces)로 둘러쌓입니다. 
  • 인자 정의는 대괄호 내에 위치하며 선택적으로 함수 타입 정보를 제공합니다. (즉, :(Int, Int) -> Int 를 생략해도 된다는 의미입니다.)
  • 로직을 정의하는 몸통(body, 바디)는 -> 화살표 기호 다음에 위치합니다. 
  • 추론된 리턴 타입이 Unit 이 아닐 경우, 마지막 표현식이 리턴 타입으로 간주됩니다. 
// 완전한 문법의 람다 표현식 예시 
val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y }

// 함수 타입 정보를 생략해도 됨. 
val sum = { x: Int, y: Int -> x + y }

 

후행 람다 전달 방식 (passing trailing lamdbas)

코틀린 규칙에 따라 마지막 인자가 함수인 경우 람다식을 괄호 밖에 배치할 수 있습니다. 

괄호를 신경쓰지 않아서 코딩하기에 편리합니다. 

// 정석적인 방식
val product = items.fold(1, { acc, e -> acc * e }) 

// 코틀린에서 허용하는 방식 
val product = items.fold(1) { acc, e -> acc * e }

 

이러한 문법은 후행 람다(trailing lambdas)라고도 알려져 있습니다.

만약에 람다 표현식이 유일한 인자라면 괄호자체도 생략이 가능합니다. 

 

run { println("...") }

람다 표현식의 숨겨진 인자 이름 it 

람다 표현식에서 인자가 단 하나인 경우는 매우 흔합니다. 

이경우 -> 또한 생략이 가능합니다. 그리고 파라미터는 it 이라는 이름으로 암시적으로 선언됩니다. 

ints.filter { x: Int -> x > 0 }
// 위 람다 표현식은 아래처럼 축약될 수 있습니다. 
ints.filter { it > 0 }

 

 

람다 표현식에서 값의 리턴 

람다 표현식에서는 정규화된 반환 문법(qualified return syntax)를 사용하여 명시적으로 값을 반환하거나 

혹은, 암시적으로 마지막의 표현식이 값으로 반환됩니다. 

// 리턴 구문을 사용하지 않아 마지막의 표현식인 shouldFilter 가 암시적으로 리턴됩니다. 
ints.filter {
    val shouldFilter = it > 0
    shouldFilter
}

// 정규화된 반환 문법을 사용하여 filter 에 대해 리턴을 합니다. 
ints.filter {
    val shouldFilter = it > 0
    return@filter shouldFilter
}

 

이러한 암시적인 반환 스타일을 통해 LINQ-style 의 코드를 작성할 수 있습니다. 

 

strings.filter { it.length == 5 }.sortedBy { it }.map { it.uppercase() }

 

위 구문은 .(점)이 연결되어 있어 구분이 어려워보이지만 이해하면 쉽습니다. 

 

  1. strings.filter { it.length == 5 } 는 길이가 5인 문자열만 필터링을 합니다. 
  2. 다음으로 .sortedBy { it } 를 통해 문자열을 정렬합니다. 
  3. 마지막으로 .map { it.uppercase() } 는 문자열을 대문자로 변환합니다. 

따라서 함수의 결과로 길이가 5인 문자열을 찾고 이를 사전순으로 정렬한 후에 대문자로 변환한 처리결과를 얻게됩니다. 

 

사용하지 않는 변수들을 위한 언더스코어

언더스코어는 _ (밑줄, undercore)를 의미합니다. 다른 프로그래밍 언어에서도 밑줄 표기는 사용하지 않는 변수를 지칭합니다. 

필요치 않는 인자가 있다면 아래의 예시처럼 언더스코어를 배치하면 굳이 이름을 고민하지 않아도 됩니다. 

map.forEach { (_, value) -> println("$value!") }

 

 

지금까지 람다 표현식의 문법과 사용법에 대해 자세히 다루어보았습니다. 

다음 시간에는 익명함수를 다루도록 하겠습니다. 

 

이상입니다.

K-IN 올림. 

반응형