들어가기 전에
해당 블로깅은 코틀린 공식문서의 Functions를 번역하며 학습한 내용입니다.
학습중임에 따라 이해하는데 도움이 되는 부분들을 추가되고 의역된 부분이 있습니다. 혹시 잘못된 설명이 있다면 얼마든지 제보해주세요.
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 매개 변수가 목록의 마지막 매개 변수가 아니면 명명된 인수 구문을 사용하여 후속 매개 변수의 값을 전달하거나,
매개 변수가 함수 유형을 가진 경우 괄호 외부에 람다를 전달할 수 있다.
- 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에 대해 지원된다.
더 보면 좋을것들 (참고)
- inline functions
- Extension functions 확장 함수
- High-order functions and lambda 고차함수 및 람다
'코틀린 > 코틀린 공식문서' 카테고리의 다른 글
[코틀린 공식문서] Classes; 클래스 (0) | 2023.08.10 |
---|---|
[코틀린 공식문서] High-order functions and lambdas; 고차함수 및 람다 (0) | 2023.07.31 |
[코틀린 공식문서] Extensions; 확장 (0) | 2023.07.27 |
[코틀린 공식문서] 배열 ; Arrays (0) | 2023.07.27 |
[코틀린 공식문서] KDoc 문서화 코드 (0) | 2023.07.21 |