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

K015. 코틀린 함수(Functions) 깊게 살펴보기 - Functions 파트6, 람다(Lambdas) 1

by K-인사이터 2024. 2. 16.
반응형

안녕하세요

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)

람다, 함수 타입 (Function types), 함수 타입의 기본

코틀린은 함수 타입을 제공합니다. 함수 타입은 함수가 어떤 타입을 인자로 받으며 어떤 타입을 반환하는지 설명합니다. 
함수 타입을 통해 함수를 변수에 저장 및 전달이 가능하여 유연하게 함수를 응용할 수 있습니다. 

함수 타입은 아래 처럼 표현됩니다. 

(Int, String) -> String

 

위 표기를 해석하면 다음과 같습니다. 

  • 인자로 Int, String 타입을 입력받고, String 타입을 리턴하는 함수를 나타냅니다.

그렇다면, 인자를 입력받지도 않고 리턴값을 반환하지 않는 함수의 타입은 어떻게 정의할까요? 

괄호로 둘러쌓인 입력 인자들은 비워두면 됩니다. 단, 리턴값을 반환하지 않는 경우 반드시 Unit 을 사용해야하며 이는 생략할 수 없습니다.

() -> Unit

 

람다, 함수 타입 (Function types), 리시버를 활용한 함수타입

리시버(receiver)는 코틀린에서 람다나 확장 함수를 사용할 때 함수를 호출하는 객체를 의미합니다. 

람다 표현식을 예시로 리시버를 우선 이해해봅니다. 

 

아래 코드의 str 변수는 람다 표현식의 리시버 역할을 합니다.

람다 표현식 내에서는 it 키워드를 통해 str 변수의 속성과 메소드에 접근합니다. (this 와 같은 역할)

val str = "Hello, Kotlin!"

str.let {
    println(it.length) // 15 출력
}

 

그렇다면, 이러한 리시버를 활용하는 함수 타입을 어떻게 선언할 수 있을까요? 

아래의 구조를 따르는 형태로 선언할 수 있습니다. 

리시버.(인자타입1, 인자타입2) -> 반환타입

 

그렇다면, 간단한 예시를 통해서 리시버를 활용하는 함수의 타입을 선언하고 활용하는 방법을 보겠습니다. 

// typealias 키워드를 통해 함수의 타입을 선언하고 이름을 지정할 수 있습니다. 
typealias OptionalReceiverFunction = String.() -> Unit

// showMessage 변수에 함수의 타입에 대응하는 람다 표현식(lambda expression)를 정의합니다. 
val showMessage: OptionalReceiverFunction = {
    println("Message: $this")
}

// 아래는 리시버를 사용하지 않는 함수의 예시입니다. 
val calculateLength: (String) -> Unit = {
    println("Length: ${it.length}")
}

// 리시버를 사용하는 것과 그렇지 않은 것은 호출할 때 차이가 있습니다. 
fun main() {
    // 리시버를 사용할 때 함수 호출 예시 
    "Hello".showMessage() // 출력: Message: Hello

    // 리시버를 사용하지 않을 때 함수 호출 예시 
    calculateLength("Kotlin") // 출력: Length: 6
}

 

람다, 함수 타입 (Function types), suspend(서스펜드)를 사용하는 함수타입

코틀린에서 서스펜드(suspend, 중단가능한)를 사용하는 함수 타입을 정의할 수 있습니다.

서스펜드 함수란 말그대로 중단 가능한 함수를 말하며 이 함수는 일시 중단 및 다시 시작될 수 있는 함수 입니다. 

 

함수 타입을 정의할 때 suspend 키워드를 이용해서 타입을 정의할 수 있습니다. 

예를 들어 아래와 같이 서스펜드 함수를 정의합니다. 

이 함수를 변수에 저장하여 다른 객체들도 사용할 수 있도록 함수 타입을 정의해보겠습니다. 

suspend fun fetchData(): String {
    // 비동기 작업 수행
    delay(1000)
    return "데이터 수신 완료"
}

 

함수 타입을 정의할 때 suspend 키워드를 사용합니다. 인자 타입과 리턴 타입에 변화는 없습니다. 

val suspendFunction: suspend () -> Unit = {
    // 중단 가능한 동작 수행
    delay(500)
    println("서스펜드 함수 실행 완료")
}

// 서스펜드 함수는 또다른 서스펜드 함수 내에서 호출되거나 
// runBlocking 과 같은 빌더 함수 내에서 실행가능합니다. 
suspend fun main() {
    suspendFunction()
}

 

람다, 함수 타입 (Function types), 문서화(documentation)를 위한 이름 지정

함수 타입을 정의할 때 인자의 이름을 지정할 수 있습니다.

인자의 이름을 지정하여 문서화를 할 때 의미를 명확하게 전달할 수 있습니다. 

 

typealias 키워드를 이용해 함수 타입에 이를 지정합니다. 

그리고, 일반적인 함수의 인자와 리턴을 정의하는 것처럼 인자의 이름을 지정합니다. 

// 함수 타입 선언: 인자에 이름을 지정하여 매개변수의 의미를 명확히 함
typealias Calculator = (x: Int, y: Int) -> Int

// 함수 정의: 덧셈을 수행하는 함수
// 타입 선언에서 사용된 이름을 반드시 따라야하는 것은 아님.
val add: Calculator = { x2, y -> x2 + y }

// 함수 정의: 곱셈을 수행하는 함수
val multiply: Calculator = { x, y -> x * y }

fun main() {
    val sumResult = add(5, 3)
    println("덧셈 결과: $sumResult")

    val productResult = multiply(4, 2)
    println("곱셈 결과: $productResult")
}

 

위의 예시에서 함수 타입 선언 시 사용된 인자의 이름을 반드시 따르지 않아도 됨을 알 수 있습니다.

add 함수의 정의에서 x 대신 x2 라고 변수의 이름을 명명해도 컴파일 에러가 발생하지 않습니다. 

하지만 이것은 가독성을 저해하기 때문에 바람직한 코딩은 아닙니다. 

 

람다, 함수 타입 (Function types), 함수 타입의 nullable (널 가능) 지정

코틀린은 타입을 지정하는 언어인 만큼 널 가능(nullable)을 직접 명시해야합니다.

함수 타입 또한 널 가능(nullable)을 지정할 수 있으며 아래와같이 정의합니다. 

 

?(물음표)를 사용하여 nullable 임을 표혐하며 null 값을 할당할 수 있게 되었습니다.

var nullableFunction: ((Int, Int) -> Int)? = null

 

만약, nullable 이 아님에도 null 값을 할당하면 어떻게 될까요? 

컴파일러에서 "Null can not be a value of a non-null type (Int, Int) -> Int" 와 같은 에러가 발생하고 실행되지 않습니다.

// 함수 타입이 nullable인 변수 선언
var nullableFunction: ((Int, Int) -> Int)? = null
// 컴파일 에러 발생: Null can not be a value of a non-null type (Int, Int) -> Int
// var notNullableFunction: ((Int, Int) -> Int) = null

// 실제 함수를 할당
nullableFunction = { a, b -> a + b }

// 함수 호출
val result = nullableFunction?.invoke(3, 4)

// 결과 출력
println(result) // 출력: 7

 

람다, 함수 타입 (Function types), 지금까지 배운 내용을 실습 

람다의 함수 타입에 대해서 배웠습니다.

아래의 플레이그라운드에서 코드를 변경해가면서 실습하면 이해를 확장할 수 있습니다. 

 

 

이상입니다.

K-IN 올림. 

반응형