조용한 담장

코틀린(Kotlin) : 함수(Function) 본문

kotlin

코틀린(Kotlin) : 함수(Function)

iosroid 2020. 1. 2. 16:57

코틀린(kotlin) 의 함수 (funciton) 에 대해 살펴보자.

원문 https://kotlinlang.org/docs/reference/functions.html 을 보며 정리.

Function declarations

함수 정의는 fun 키워드를 사용한다.

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

Function usage

함수 호출

val result = double(2)

멤버 함수 호출

Stream().read() // create instance of class Stream and call read()

Parameters

함수 파라미터는 Pascal notation 을 써서 정의한다.

각 파라미터는 type 이 정의되어야 한다.

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

Default arguments

함수 파라미터는 argument 가 생략 된 경우에 할당되는 해당 파라미터의 기본 값을 가질 수 있다.

fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size) { /*...*/ }

기본값은 type 과 값 사이에 = 를 사용하여 정의한다.

overriding method 는 항상 base method 와 같은 기본 파라미터 값을 쓴다.

기본 파라미터 값을 가진 method 를 overriding 할 때는 기본 값 할당 표현을 생략할 수 있다.

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

class B : A() {
    override fun foo(i: Int) { /*...*/ }  // no default value allowed
}

함수의 인자 항목에서 기본 값이 있는 파라미터가 기본 값이 없는 파라미터 보다 앞에 위치하면, 기본 값은 named arguments (아래에서 설명) 로 함수를 호출할 때만 쓰이게 된다.

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

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

함수의 인자 항목에서 기본값을 가진 파라미터 뒤에 위치한 마지막 인자가 lambda 인 경우, named argument 나 outside the parentheses 로 전달 될 수 있다.

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 arguments

함수 호출 시 파라미터의 이름을 사용할 수 있다.

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

기본 arguments 와 사용할 때

reformat(str)

기본 값을 사용하지 않을 때

reformat(str, true, true, false, '_')

보기 좋게

reformat(str,
    normalizeCase = true,
    upperCaseFirstLetter = true,
    divideByCamelHumps = false,
    wordSeparator = '_'
)

일부 arguments 만 지정할 때

reformat(str, wordSeparator = '_')

함수가 positional and named arguments 와 함게 호출되면, 모든 positional arguments 는 첫번째 named argument 앞에 위치헤야 한다.

예를들어 f(1, y = 2) 는 맞지만, f(x = 1, 2) 은 틀리게 된다.

Variable number of arguments (vararg, 아래에서 설명) 은 spread operator 를 사용하여 named 형식으로 전달될 수 있다.

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

foo(strings = *arrayOf("a", "b", "c"))
On the JVM: the named argument syntax cannot be used when calling Java functions because Java bytecode does not always preserve names of function parameters.

Unit-returning functions

함수가 아무 의미있는 값을 리턴하지 않을 때의 return type 은 Unit 이 된다.

Unit 은 단 하나의 값(Unit) 을 가진 타입 이다.

이 값은 항상 리턴할 필요는 없다.

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

Unit 리턴 타입 정의는 생략해도 되며 위의 코드는 아래 코드와 동일하다.

fun printHello(name: String?) { ... }

Single-expression functions

함수가 single expression 을 리턴할 때에는 { } 는 생략될 수 있고 함수의 바디는 = 뒤에 위치한다.

fun double(x: Int): Int = x * 2

컴파일러가 expression 에서 리턴 타입을 유추할 수 있는 경우에는 return type 을 생략할 수 있다.

fun double(x: Int) = x * 2

Explicit return types

함수의 block body 가 있는 경우엔 항상 return type 을 명시해야 하며 그렇지 않으면 Unit 을 리턴하는 것으로 간주된다.

코틀린은 block body 가 있는 함수는 return type 을 유추하지 않는데, 이는 함수의 코드가 복잡하거나 return type 을 명확히 판단할 수 없기 때문이다.

Variable number of arguments (Varargs)

함수의 파라미터 (보통 마지막에 오는) 는 vararg 로 표시될 수 있는데, 여러개의 argument 를 함수에 전달할 수 있게 해

준다.

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)

함수 안에서 T 타입의 vararg 파리미터는 T 값들을 가진 array 로 보이게 되며, ts 변수는 Array<out T> 타입을 가진다.

한개의 파라미터만 vararg 로 표시될 수 있다.

vararg 파라미터가 함수 인자 목록의 마지막이 아닌 경우에는 그 뒤의 파라미터의 값들을 named argument 를 써서 전달하거나, 파라미터가 funciton type 이면 ( ) 밖에 lambda 를 써서 전달할 수 있다.

vararg 함수를 호출할 때 asList(1, 2, 3) 처럼 argument 를 하나씩 전달하거나, 이미 array 가 있고 그 내용을 함수에 전달하고 싶을때는 spread operator * 를 array 의 prefix 로 사용할 수 있다.

val a = arrayOf(1, 2, 3)
val list = asList(-1, 0, *a, 4)

Infix notation

infix 키워드를 가진 함수는 .( ) 를 생략하고 호출하는 infix notation 을 사용하여 호출될 수 있다.

infix function 은 아래의 사항을 만족해야 한다.

 

  • 멤버 함수 이거나 확장 함수 이어야 한다.
  • 단 하나의 파라미터를 가진다.
  • 파라미터는 고정되지 않은 개수의 arugment 를 가질 수 없고 기본 값을 가질 수 없다.
infix fun Int.shl(x: Int): Int { ... }

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

// is the same as
1.shl(2)

infix function 호출은 arithmetic operators, type casts, and the rangeTo operator 보다 우선순위가 낮다.

아래의 예제들은 모두 동등하다.

  • 1 shl 2 + 3 is equivalent to 1 shl (2 + 3)
  • 0 until n \* 2 is equivalent to 0 until (n * 2)
  • xs union ys as Set<\*> is equivalent to xs union (ys as Set<\*>)

boolean operators && and ||, is- and in-checks, and some other operators 보다는 우선 순위가 높다.

아래의 예제들은 모두 동등하다.

  • a && b xor c is equivalent to a && (b xor c)
  • a xor b in c is equivalent to (a xor b) in c

참조 : Grammar reference

 

infix funcitons 는 항상 receiver 와 지정된 파라미터가 필요하다.

infix notation 을 사용하여 현재의 receiver 에서 method 를 호출할 때는 this 를 명확히 써야 하며 일반 method 호출과 다르게 생략될 수 없다.

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

코틀린에서 함수는 파일의 상위 레벨에 정의될 수 있는데 이는 함수가 반드시 클래스에 속할 필요는 없다는 뜻이다.

멤버 함수 와 확장 함수로써 로컬(중첩)의 위치에 선언될 수 있다.

Local functions

코틀린은 함수 안의 함수를 지원한다.

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

함수 이름 앞에 < > 를 사용하여 지정된 generic parameters 를 가질 수 있다.

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

Inline functions

다음에 ...

Extension functions

참조 : link

Higher-order functions and lambdas

다음에 ...

Tail recursive functions

코틀린은 tail recursion 라고 알려진 함수형 프로그래밍 스타일을 지원한다.

이는 특정 알고리즘의 경우 재귀형 함수 대신 루프를 사용할 수 있고, stack overflow 의 위험을 피할 수 있게 해준다.

함수가 tailrec 로 표시되고 특정 조건들을 만족할 때 컴파일러는 재귀구문을 효과적인 루프 기반의 버전으로 최적화 한다.

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))

이 코드는 Math.cos 를 1.0 에서 시작해서 결과가 더이상 바뀌지 않을때까지 반복적으로 호출하여 eps 0.7390851332151611 값을 넘긴다.

이는 좀더 전통적인 아래의 코드와 동등하다.

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 에 좀더 맞게 하려면 함수는 자신이 마지막 호출 대상이 되어야 한다.

재귀 호출 이후의 더이상의 코드가 없어야 tail recursion 을 쓸 수 있으며, try/catch/finally 구문과 함께 쓸 수 없다.

 

 

 

Comments