[Kotlin] 4-2. 다양한 함수의 종류

leeeha·2022년 6월 30일
0

코틀린

목록 보기
11/28
post-thumbnail

출처: https://www.boostcourse.org/mo132/lecture/59987

익명(anonymous) 함수

함수의 이름이 없는 것!

// 함수의 이름이 생략된 익명 함수
fun (x: Int, y: Int): Int = x + y 
package chap04.section3

fun main() {
	// 익명 함수를 사용한 add 선언
    val add: (Int, Int) -> Int = fun(x, y) = x + y 
    val result = add(10, 2) // add의 사용
    println(result) // 12 
}
package chap04.section3

fun main() {
    val add = fun(x: Int, y: Int) = x + y // 일반 익명 함수
    val addLambda = { x: Int, y: Int -> x + y } // 람다식

    println(add(5, 3))
    println(addLambda(5, 3))
}

cf) 일반 익명 함수에서는 return, break, continue 사용이 가능하지만, 람다식에서는 사용하기 어렵다. (라벨 표기법과 같이 사용해야 함)


인라인(inline) 함수

인라인 함수란?

  • 함수가 호출되는 곳에 내용을 모두 복사함.
  • 함수 호출에 의한 분기 없이 처리 → 성능 증가

일반 함수

package chap04.section3

fun shortFunc(){
    println("Hello")
}

fun main() {
    shortFunc()
}

Tools > Kotlin > Show Kotlin Bytecode > Decompile 해서 자바 클래스를 확인해보면, main 함수에서 shortFunc 함수를 호출하고 있음을 확인할 수 있다.

인라인 함수

package chap04.section3

inline fun shortFunc(){
    println("Hello")
}

fun main() {
    shortFunc()
}

반면에, 인라인 함수를 디컴파일 해보면, shortFunc 함수를 호출하는 것이 아니라 그 내용을 모두 복사한다는 걸 확인할 수 있다!!

Inlining works best for functions with parameters of functional types
👉 이 경고 문구를 보면, 인라인 함수는 아래 예시처럼 람다식을 매개변수로 사용할 때 성능이 가장 좋다는 걸 알 수 있다.

package chap04.section3

inline fun shortFunc(a: Int, out: (Int) -> Unit){
    println("Hello")
    out(a)
}

fun main() {
    //shortFunc(3, { a -> println("a: $a") })
    //shortFunc(3) { a -> println("a: $a") }
    shortFunc(3) { println("a: $it") }
}

Hello
a: 3

인라인 함수의 제한과 금지

  • 인라인 함수가 여러 번 호출될 경우, 그 내용이 반복적으로 복사되어 한 함수에 들어가는 코드량이 많아질 수 있으므로 주의해야 한다.
  • noinline 키워드: 일부 람다식 함수를 인라인 되지 않게 하기 위해 사용
  • noinline이 있는 람다식 함수는 인라인으로 처리되지 않고 분기되어 호출된다.

inline fun sub(out1: () -> Unit, noinline out2: () -> Unit) { ... }

inline fun shortFunc(a: Int, noinline out: (Int) -> Unit){
    println("Hello")
    out(a) // noinline
}

fun main() {
    shortFunc(3) { println("a: $it") }
}

인라인 함수와 비지역 반환

package chap04.section3

inline fun shortFunc(a: Int, out: (Int) -> Unit){
    println("Hello")
    out(a)
    println("Goodbye")
}

fun main() {
    shortFunc(3) {
        println("a: $it")
    }
}

Hello
a: 3
Goodbye

여기서 실수로 람다식에 return문을 작성하면 어떻게 될까?

package chap04.section3

inline fun shortFunc(a: Int, out: (Int) -> Unit){
    println("Hello")
    out(a)
    println("Goodbye") // 실행되지 않음. 
}

fun main() {
    shortFunc(3) {
        println("a: $it")
        return // 람다 함수의 종료 (비지역 반환) 
    }
}

Hello
a: 3

람다 함수 out이 종료되어 그 이후의 내용이 실행되지 않는다! 이를 비지역 반환이라 부른다.

비지역 반환의 금지

람다식 매개변수 앞에 crossinline 키워드를 붙여주면, 해당 람다식 함수를 호출할 때 return문을 실행할 수 없게 된다. 즉, 비지역 반환을 금지하는 것이다!

📌 부연 설명
코틀린에서는 익명 함수를 종료시키기 위해 return을 사용할 수 있다. 이때 특정 반환값 없이 return만 사용해야 한다. 그렇다면, 람다식 함수를 빠져나오려면 어떻게 해야 할까? 람다식 함수를 인자로 사용하는 함수는 의도치 않게 람다식 함수 바깥에 있는 함수도 같이 반환이 되어버릴 수 있는데, 이를 비지역 반환이라고 한다. 이를 금지하려면, 람다식 함수 앞에 crossline 키워드를 사용해, 함수의 본문 블록에서 return이 사용되는 것을 금지할 수 있다.


⭐ 확장(extension) 함수

클래스에는 다양한 함수가 정의되어 있다. 이것은 클래스의 멤버 메서드라고도 불린다. 그런데 기존 멤버 메서드는 아니지만 내가 원하는 함수를 하나 더 포함시켜 확장하고 싶을 때가 있을 것이다. 코틀린에서는 이처럼 클래스처럼 필요로 하는 대상에 함수를 더 추가할 수 있는 확장 함수(extension function)라는 개념을 제공하고 있다. 요약하자면, 확장함수는 클래스의 멤버 함수를 외부에서 더 추가할 수 있는 개념이다!

fun 확장대상.함수명(매개변수, ...): 반환값 {
	...
    return 값 
}
package chap04.section3

// String을 확장하여 getLongString이라는 멤버 함수 추가 
fun String.getLongString(target: String): String =
    if(this.length > target.length) this else target

fun main() {
    val source = "Hello World!"
    val target = "Kotlin"
    println(source.getLongString(target)) // 더 긴 문자열을 출력함. 
}

this는 확장 대상에 있던 자리의 문자열인 source 객체를 나타낸다. 기존의 표준 라이브러리를 수정하지 않고도 클래스의 멤버 함수를 확장할 수 있어서 매우 유용한 개념이다!

확장 함수를 만들 때 만일 확장하려는 대상에 동일한 이름의 멤버 함수 혹은 메서드가 존재한다면, 항상 확장 함수보다 멤버 메서드가 우선으로 호출된다는 것도 추가적으로 알아두자.


중위 함수

중위 표현법 (infix notation)

클래스의 멤버 호출 시 사용하는 점(.)을 생략하고, 함수 이름 뒤에 소괄호를 생략해, 직관적인 이름을 사용할 수 있는 표현법

📌 중위 함수의 조건

  • 멤버 메소드 또는 확장 함수여야 한다.
  • 하나의 매개변수를 가져야 한다.
  • infix 키워드를 사용해서 정의한다.

중위 함수를 이용해 연산자처럼 사용하기

package chap04.section3

fun main() {
    // 일반 표현법
    //val multi = 3.multiply(10)
    
    // 중위 표현법
    val multi = 3 multiply 10
    println("multi: $multi")
}

// Int를 확장해서 multiply() 함수 추가 
infix fun Int.multiply(x: Int): Int { // 중위 함수 
    return this * x
}

이처럼 중위 함수는 일종의 연산자를 구현할 수 있는 함수인데, 특히 비트 연산자에서 자주 사용한다고 한다.


재귀 함수

재귀(recursion)란?

  • 자기 자신을 다시 참조
  • 재귀 함수는 자기 자신을 호출하는 함수

📌 재귀 함수의 필수 조건

  • 무한 호출에 빠지지 않도록 탈출 조건을 만들어 둔다.
  • 스택 영역을 이용하므로 호출 횟수를 무리하게 많이 지정하지 않는다.
  • 코드를 복잡하게 만들지 않아야 한다.

꼬리 재귀 함수 (tail recursive function)

  • 스택에 계속 쌓이는 방식이 아니라, 꼬리를 무는 형태로 반복한다.
  • 코틀린 고유의 tailrec 키워드를 사용해 선언

일반적인 팩토리얼의 재귀 함수

package chap04.section3

fun factorial(n: Int): Long {
    return if(n == 1) n.toLong() else n * factorial(n - 1)
}
fun main() {
    val number = 4
    val result: Long = factorial(number)
    println("Factorial: $number! => $result") 
}

Factorial: 4! => 24

여기서 n이 너어어어어무 커지면, 계속해서 스택 프레임에 그 내용이 쌓이면서 스택 오버플로우가 발생할 수 있다!

꼬리 재귀로 스택 오버플로우 방지하기

꼬리 재귀를 사용하면 팩토리얼의 값을 그때 그때 계산하므로 스택 공간을 낭비하지 않아도 되므로, 일반 재귀함수보다 훨씬 더 안전한 코드가 된다.

package chap04.section3

tailrec fun factorial(n: Int, run: Int = 1): Long {
    return if(n == 1) run.toLong() else factorial(n - 1, run * n)
}
fun main() {
    val number = 5
    println("Factorial: $number! => ${factorial(number)}")
}

📌 꼬리 재귀에 대한 영상을 봤는데,, 아직 완전히 이해 못 했다,, 다음에 꼭 다시 보자!!!

profile
꾸준히!

0개의 댓글