본문 바로가기
코틀린/코틀린 공식문서

[코틀린 공식 문서] Functions; 함수

by 측면삼각근 2023. 7. 27.
728x90
반응형

들어가기 전에

 

해당 블로깅은 코틀린 공식문서의 Functions를 번역하며 학습한 내용입니다.

학습중임에 따라 이해하는데 도움이 되는 부분들을 추가되고 의역된 부분이 있습니다. 혹시 잘못된 설명이 있다면 얼마든지 제보해주세요.


 

 

Functions | Kotlin

 

kotlinlang.org

 

Functions

fun 키워드로 정의 할 수 있다.

fun double(x: Int): Int {
    return 2 * x
}

표준 접근 방식을 통하여 호출할 수 있다.

val result = double(2)

Parameters

파라미터는 파스칼 표기법을 통해 [이름: 타입]의 형식으로 정의해야 하며, 타입은 반드시 정의되어야 한다.
파라미터의 구분은 쉼표로 이루어 지는데, 후행쉼표(trailing comma)도 사용이 가능하다.

fun powerOf(number: Int, exponent: Int): Int { /*...*/ }

fun powerOf(
    number: Int,
    exponent: Int, // trailing comma (후행 쉼표)
) { /*...*/ }

Default arguments 

매개변수는 해당 인수를 건너 뛸 때 사용되는 기본 값을 가질 수 있다.
=를 사용하여 설정한다.

fun read(
    b: ByteArray,
    off: Int = 0,
    len: Int = b.size,
) { /*...*/ }

오버라이딩 한 메서드의 경우, 항상 부모 메서드의 기본 값을 상속받는다.
따라서 기본 매개변수 값이 있는 메서드를 재 정의 할 때, 기본 매개변수는 생략 되어야 한다.

open class A {
    open fun foo(i: Int = 10) { /*...*/ }
}

class B : A() {
    override fun foo(i: Int) { /*...*/ }  // 기본 값(default value)이 허용되지 않음
}

/*
class B : A() {
    override fun foo(i: Int = 20) { /*...*/ }  // 이 경우 에러 발생
}
*/

기본 매개 변수가 기본값이 없는 매개 변수 앞에 오는 경우, 기본값은 명명된 인수(named argument)로 함수를 호출해야만 사용할 수 있다.

fun foo(
    bar: Int = 0,
    baz: Int,
) { /*...*/ }

foo(baz = 1) // The default value bar = 0 is used

기본 매개변수 이후 마지막 인자가 람다인 경우, 명명된 인자(named argument) 혹은  괄호 밖으로 전달 할 수 있다.

fun foo(
    bar: Int = 0,
    baz: Int = 1,
    qux: () -> Unit,
) { /*...*/ }

foo(1) { println("hello") }     // Uses the default value baz = 1
foo(qux = { println("hello") }) // Uses both default values bar = 0 and baz = 1
foo { println("hello") }        // Uses both default values bar = 0 and baz = 1

named Argument

함수를 호출 할 때 함수의 파라미터의 이름을 지정할 수 있다.
함수 파라미터의 인자가 너무 많고, 특히 혼동이 쉬운 null이나, boolean일때 도움 될 수 있다.

호출시 named arguments를 사용하면, 나열된 순서를 자유롭게 변경 할 수 있으며, 생략할 경우 기본값(default value)을 사용한다.

아래 reformat함수를 예시로 보면, 

fun reformat(
    str: String,
    normalizeCase: Boolean = true,
    upperCaseFirstLetter: Boolean = true,
    divideByCamelHumps: Boolean = false,
    wordSeparator: Char = ' ',
) { /*...*/ }

모든 인자의 이름이 있을 필요는 없다.

reformat(
    "String!",
    false,
    upperCaseFirstLetter = false,
    divideByCamelHumps = true,
    '_'
)

기본값을 사용한다면 스킵해도 괜찮다.

reformat("This is a long String!")
// 하지만 중간 인자를 생략하였을 경우 그 이후 모든 인자를 이름 붙여 사용하여야 한다.
reformat("This is a short String!", upperCaseFirstLetter = false, wordSeparator = '_')

variable number of arguments(vararg)매개 변수의 경우 spread operator를 사용해 인자 이름과 함께 전달 할 수 있다.

fun foo(vararg strings: String) { /*...*/ }

foo(strings = *arrayOf("a", "b", "c"))

위 예시의 경우 아래와 같이 활용 할 수 있지만,

foo("a", "b", "c")

별도의 인수(arguments)가 있는 경우 다른 인수와 혼돈되어 반드시 명명된 인수를 사용하여야 한다.

JVM에서 JAVA함수를 호출 할 때, JAVA 바이트 코드가 항상 함수 매개변수 이름을 보존해 주지 않기 때문에 명명된 인수를 사용할 수 없다.

Unit-returning Function

함수 반환값이 없을경우, 반환 유형은 Unit이다. 이 값을 명시적으로 반환할 필요는 없다.
명시적인 Unit 표기또한 필수적이지 않다.

fun printHello(name: String?): Unit {
    if (name != null)
        println("Hello $name")
    else
        println("Hi there!")
    // `return Unit` or `return` is optional
}

fun printHello(name: String?) { ... }
// Unit 반환이 필수적이지 않음

Single-expression functions

함수의 본문이 단일 표현식일 경우, 괄호를 생략하고 = 기호 뒤에 본문을 작성 할 수 있다.

fun double(x: Int): Int = x * 2
// 여기에서 반환 유형을 작성해주는 것 또한 선택사항이다.
fun double(x: Int) = x * 2

Explicit return types (명시적 반환 타입)

블럭된 본문(block body)가 있는 함수는 Unit을 반환하도록 작성되지 않는 한 항상 반환 유형을 명시적으로 지정해야 한다
(Unit 반환의 경우에만 기재가 선택사항이다.)

코틀린은 블록 바디가 있는 함수에 대한 반환 유형을 추론하지 않는다.

Variable number of arguments (varagrs)

함수의 파라미터(일반적으로 마지막) 를 varagrs로 표기할 수 있다.

fun <T> asList(vararg ts: T): List<T> {
    val result = ArrayList<T>()
    for (t in ts) // ts is an Array
        result.add(t)
    return result
}

이 경우 함수에 다음과 같이 다양히 인수를 전달 할 수 있다.

val list = asList(1, 2, 3)
  • vararg 파라미터는 Array<out T> 형식을 갖는다. (super class 대입 가능)
  • 하나의 매개변수만 vararg로 표현 할 수 있다.
    • ararg 매개 변수가 목록의 마지막 매개 변수가 아니면 명명된 인수 구문을 사용하여 후속 매개 변수의 값을 전달하거나,
      매개 변수가 함수 유형을 가진 경우 괄호 외부에 람다를 전달할 수 있다.
val a = arrayOf(1, 2, 3)
val list = asList(-1, 0, *a, 4)

vararg 함수를 호출할 때 List(1, 2, 3)와 같이 인수를 개별적으로 전달할 수 있다.
이미 배열이 있고 함수에 배열 내용을 전달하려면 스프레드 연산자를 사용하면 된다.

만약 원시 유형의 배열(primitive type array)을 vararg에 넣고싶다면, toTeypedArray함수를 통하여 regular (typed) array로 변경해 주어야 한다.

val a = intArrayOf(1, 2, 3) // IntArray is a primitive type array
val list = asList(-1, 0, *a.toTypedArray(), 4)

Infix notation (중위 표기법)

중위 키워드로 표시된 함수는 중위 표기법을 사용하여 호출할 수 있다.(호출에 대한 점과 괄호를 생략)

중위 함수는 다음 요구 사항을 충족해야 한다.

  • 이들은 멤버 함수 또는 확장 함수여야 한다.
  • 반드시 하나의 매개변수를 가져야 한다.
  • 가변 인수(varargs)를 허용하지 않으며, 기본 값이 없어야 한다.
infix fun Int.shl(x: Int): Int { ... }

// calling the function using the infix notation
1 shl 2

// is the same as
1.shl(2)
중위함수는 산술연산자, 형식 캐스트, "rangeTo" 연산자보다 우선순위가 낮다.
- 1 shl 2 + 3 = 1 shl (2 + 3)
- 0 until n * 2 = 0 until (n * 2) 
- xs union ys as Set<*> = xs union (ys as Set<*>)

boolean operator와 is, in 연산자의 우선순위보다는 높다.
- a && b xor c = a && ( b xor c )
- a xor b in c = ( a xor b ) in c

중위함수는 항상 reciever와 매개변수를 모두 지정해야 한다.
명확한 구문 분석을 보장하기 위해, 현재 reciever에서 중위 표기법의 메서드를 사용하는 경우 명시적으로 사용한다.

class MyStringCollection {
    infix fun add(s: String) { /*...*/ }

    fun build() {
        this add "abc"   // Correct
        add("abc")       // Correct
        //add "abc"        // Incorrect: the receiver must be specified
    }
}

Function scope

코틀린 함수는 파일의 최상위 수준에 선언할 수 있기 때문에, 자바 C#, Scala와 같은 언어에서 수행해야 하는 함수를 위하여 클래스를 만들 필요는 없다.

최상위 함수 외에도 코틀린 함수를 로컬에서 맴버 함수 및 확장 함수로 선언할 수도 있다.

Local functions

다른 함수의 내부함수인 로컬 function을 지원한다.

fun dfs(graph: Graph) {
    fun dfs(current: Vertex, visited: MutableSet<Vertex>) {
        if (!visited.add(current)) return
        for (v in current.neighbors)
            dfs(v, visited)
    }

    dfs(graph.vertices[0], HashSet())
}

지역 함수는 외부 함수의 지역 변수(클로저)에 엑세스 할 수 있다.
해당 경우 지역변수이다.

fun dfs(graph: Graph) {
    val visited = HashSet<Vertex>()
    fun dfs(current: Vertex) {
        if (!visited.add(current)) return
        for (v in current.neighbors)
            dfs(v)
    }

    dfs(graph.vertices[0])
}

Member functions

맴버 함수는 클래스 또는 개체 내부에 정의된 함수이다.

class Sample {
    fun foo() { print("Foo") }
}

// 점 표기법으로 호출된다.
Sample().foo() // creates instance of class Sample and calls foo

Generic functions

함수에는 제네릭 메개변수가 있을 수 있으며, <>를 사용하여 지정된다.

fun <T> singletonList(item: T): List<T> { /*...*/ }

Tail recursive functions

코틀린은 tail recursive라는 함수형 프로그래밍 스타일을 지원한다.
일반적인 루프를 활용하는 일부 알고리즘의 경우 스택 오버플로우의 위험 없이 재귀 함수를 대신 사용할 수 있다.

함수가 tailrec modifier로 표시되고 필요한 형식 조건을 충족하면 컴파일러는 재귀를 최적화하여 대신 빠르고 효율적인 루프 기반 버전으로 남긴다.

val eps = 1E-10 // "good enough", could be 10^-15

tailrec fun findFixPoint(x: Double = 1.0): Double =
    if (Math.abs(x - Math.cos(x)) < eps) x else findFixPoint(Math.cos(x))

위 예시는 수학 상수 코사인(cosine)을 계산하는데, 결과가 더이상 변경되지 않을 때 까지 반복적으로 호출하여 지정된 정밀도에 대한 결과를 생성한다. 위 코드는 아래와 같다.

val eps = 1E-10 // "good enough", could be 10^-15

private fun findFixPoint(): Double {
    var x = 1.0
    while (true) {
        val y = Math.cos(x)
        if (Math.abs(x - y) < eps) return x
        x = Math.cos(x)
    }
}

tailrec를 사용할 수 있으려면, 함수가 수행하는 마지막 작업으로 자신을 호출해야 한다.
재귀 호출 후, try / catch / finally 블록 내거나, 함수 안에 더 많은 코드가 있는 경우 tail 재귀를 사용 할 수 없다. 

현재 꼬리 재귀는 JVM 및 Kotlin/Native에 대해 지원된다.


더 보면 좋을것들 (참고)

 

반응형