코틀린 언어 정리 1-3

기본 - 함수

함수

함수를 선언하는 문법이 매우 다양합니다.


문법

일반 함수

fun 함수이름 ( 매개변수선언, ... ) : 리턴타입 { 함수 구현부 return 리턴값 }


람다 함수

{ 매개변수1, ... -> 함수 구현 }


람다 함수에는 기본적으로 return문을 사용할 수 없습니다.( 예외적으로 사용 가능한 경우도 있는데 이에 대한 설명은 인라인 고차함수 부분 참조 ) 함수 구현의 마지막 표현식이 람다함수의 리턴값으로 사용됩니다.


익명 함수

fun(arg:Type):ReturnType = ...


예제

fun test () {

   var fun_var = fun(x: Int, y: Int): Int = x + y

}



fun(arg:Type):ReturnType { ... return ... }


예제

fun test () {

   var fun_var = fun(x: Int, y: Int): Int {

       return x + y

   }

}


람다함수와 익명함수

비슷한점

  • 함수 선언과 동시에 객체가 생성되며 표현식으로 사용할 수 있습니다.


차이점

  • lambda 함수 변수는 타입을 명시하거나 생략할 수 있고 이에 따라 매개 변수 타입이 생략된 람다 함수 정의, 또는 매개 변수 타입이 포함된 완전한 람다 함수 정의를 변수에 대입합니다.  익명 함수 변수는 타입을 명시하지 않으며 함수 이름이 생략된 완전한 함수 정의(일반 함수 정의 구문, 단일 표현식 등)을 바로 대입합니다.

  • 람다함수가 인라인 고차함수의 parameter로 입력되는 경우 return문을 사용할 수 있는데 이 때 return은 인라인 고차함수를 호출한 함수의 return으로 사용됩니다.( non-local returns ) 하지만 익명 함수의 return은 익명함수 자체의 return으로 사용됩니다.

  • 고차함수에서 람다 함수를 입력 받는 매개변수가 함수 마지막에 위치한 경우 고차함수 호출 시 람다 함수 구현을 함수 외부(괄호 오른쪽)에 사용할 수 있으나 익명함수는 일반 매개변수와 동일한 문법으로 사용해야 합니다.


예제

fun func_normal(a: Int, b: Int, inlineFunc: (Int, Int) -> Int): Int {

   return inlineFunc(a, b)

}


fun test() {

   var func_lambda: ( Int, Int ) -> Int = { a, b -> a + b }

   var func_noname = fun ( a: Int, b: Int ): Int { return a + b }


   println(

       func_normal(10, 20, func_lambda)

   )

   println(

       func_normal(10, 20, func_noname)

   )

//    println(

//        func_normal(10, 20) fun ( a: Int, b: Int ): Int { return a + b } // error

//    )

   println(

       func_normal(10, 20) { a, b -> a + b }

   )

}


리시버(receiver)를 가지는 함수 구문

기본적으로 A.(B) -> C 와 같은 형식으로 생각할 수 있으며 A는 리시버, B는 함수 매개변수, C는 리턴 타입 입니다.


람다 표현식으로 선언

var func1: Int.( a: Int, b: Int )->Int = { a, b-> a*2 + b*2 }


단일 표현식으로 익명 함수 선언

var func2 = fun Int.( a: int, b: Int ): Int = a*2 + b*2


함수 구문으로 익명 함수 선언

var func3 = fun Int.( a: Int, b: Int ): Int { return a*2 + b*2 }


위의 3가지 방식으로 선언한 변수의 형(함수 형)은 서로 호환됩니다. 추가로 위와 같이 리시버를 가지는 함수 구문은 리시버를 첫번째 매개변수로 전달 받는 일반 함수형과 호환됩니다.


리시버가 있는 확장 함수 변수와 리시버가 제외된 일반 함수 변수는 초기화 된 후(함수 구현을 변수에 대입한 후) 변수 끼리 대입할 때 서로 호환됩니다. 하지만 변수 초기화 단계에서는 호환되는 함수 구현을 직접 사용할 수는 없습니다.


예제

// 정상 동작

var func1: Int.(Int, Int) -> Int = fun Int.(a: Int, b: Int): Int { return a + b }

var func2: (Int, Int, Int)-> Int = { obj, a, b-> obj+a+b }


// 변수 타입 호환 확인

func2 = func1

func1 = func2


// 컴파일 에러

//    var func1_1: Int.( Int, Int )-> Int = { obj, a, b-> obj+a+b }

//    var func2_1: ( Int, Int, Int )-> Int = fun Int.( a: Int, b: Int ): Int { return a+b }

전체 예제

fun test() {

   println("main start")

   // [리시버가 있는] 람다 표현식

   var funDif: Int.(a: Int) -> Int = { it * 3 }

   var fun1: Int.(a: Int, b: Int) -> Int = { a, b -> this * 2 + a * 2 + b * 2 }


   // [리시버가 있는] 단일 표현식 익명 함수 선언 구문

   var fun2 = fun Int.(a: Int, b: Int): Int = this * 2 + a * 2 + b * 2


   // [리시버가 있는] 익명 함수 선언 구문

   var fun3 = fun Int.(a: Int, b: Int): Int { return this * 2 + a * 2 + b * 2 }


   println(3.fun1(1, 2))

   println(3.fun2(1, 2))

   println(3.fun3(1, 2))


   // 리시버가 포함된 함수형을 일반 함수형으로 변환

   var fun1_1: (t: Int, a: Int, b: Int) -> Int = fun1

   var fun2_1 = fun(t: Int, a: Int, b: Int): Int = 0

   var fun3_1 = fun(t: Int, a: Int, b: Int): Int { return 0 }


   // fun2_1 = funDif // error: 함수 형이 다름

   fun2_1 = fun1 // 리시버가 포함된 함수형은 리시버를 받을 수 있는 매개변수가 추가된 일반 함수형과 동일하게 취급됨

   fun2_1 = fun2

   fun2_1 = fun3

   fun1_1 = fun2_1

   fun1 = fun3

   fun3 = fun3_1

   println(fun1_1(3, 1, 2))

   println(fun2_1(3, 1, 2))

   println("main end")

}


기타 특징

문법 요소 중 문맥 상 추론이 가능한 부분은 대부분 생략 가능합니다.


함수 선언에서 return type을 명시하지 않고 함수 구현에서 return을 사용하지 않을 경우 Unit 을 리턴하는 것으로 처리됩니다. ( C/C++/Java 등의 void 처럼 사용 )


Named Arguments

함수 호출 시 매개변수를 지정(명시)하여 인수를 할당할 수 있습니다.

예제

fun func1 ( x: Int, y: Int ): Int { return x + y }


fun test() {

   func1(1, y = 2)

   func1(x = 1, 2) // error...not allowed

}

주의할 점은, 모든 위치 기반 인수(positional arguments)는 이름을 명시한 첫번째 인수(first named arguments) 보다 앞쪽에 있어야 합니다. 예를 들면 다음과 같습니다. 

ex. func1(1, y = 2) is allowed, but func1(x = 1, 2)


vararg

함수에서 가변인수를 전달받기 위해 사용하는 키워드입니다. 매개변수 선언 앞에 vararg를 추가하면 동일한 형의 매개변수를 원하는 만큼 전달 받을 수 있습니다.

예제

fun varargTest(vararg args: Int): Int {

   var result = 0;

   println(args::class) //=> IntArray로 출력됨

   for (arg in args) {

       result += arg

   }

   return result

}

위의 예제에서 args의 type을 출력해 보면 IntArray로 나옵니다. 즉 vararg로 선언된 매개변수는 원래 선언된 타입의 배열로 처리됩니다.


* ( spread operator )

주로 vararg와 관련하여 사용됩니다. 가변인수를 전달받는 함수에 배열이나 리스트 등을 전달할 때 사용할 수 있는 연산자입니다.

예제

val list = listOf(1, 2, 3, 4, 5)

varargTest ( *list )

원래는 varargTest 함수 호출 시 list의 요소를 각각 직접 명시해야 하나 *(spread operator)를 사용하면 각각 명시 하지 않아도 동일하게 처리됩니다.

varargTest ( *list )는 varargTest ( 1, 2, 3, 4, 5 )로 처리됩니다.


infix

함수를 연산자 처럼 사용할 수 있게 처리해 줍니다. ( 자세한 내용은 연산자 부분 참조 )


tailrec

코틀린 코드를 자바코드로 변환할 때 꼬리재귀함수를 반복문으로 구현된 함수로 변환해 줍니다. 단 함수의 마지막 표현식이 자신(함수)을 호출하는 순수재귀함수여야 합니다.


예제

tailrec fun sum(n: Int): Int {

   if (n <= 0)

       return n

   else

       return n + sum(n - 1)

}

위의 예제는 마지막 표현식이 sum (...) 이 아니므로( "n + "와 같은 다른 표현식이 섞여 있음 ) 변환이 실패합니다.( 컴파일은 되지만 경고 출력됨 )


함수 정의 예제들

예제

fun test () {

   fun fun0() {}

   fun fun1(num: Int, str: String): Int {

       return 0

   }


   // 단일 표현식 함수 정의

   fun fun2(num: Int, str: String): Int = num + str.toInt()


   // 람다 함수를 함수 변수에 대입하는 방법

   var vFun2: (Int, String) -> Int = { num, str -> num + str.toInt() }


   // 람다 함수를 함수 변수에 대입하는 방법. 변수 부분의 타입 선언 생략 가능

   var vFun3 = { num: Int, str: String -> num + str.toInt() }


   // 변수에 구현된 함수 대입

   var vFun4 = ::fun2


   // 람다 함수의 인자가 1개인 경우 함수 구현부에서 매개변수를 명시하지 않아도 된다. 이런 경우 기본으로 it 라는 매개변수 이름을 사용하게 된다.

   var vFun1: (String) -> Int = { it.toInt() }

}


+ Recent posts