# Kotlin 문법 - 함수

# 기본 함수 형태

public fun makeText(): String {
    return "x = $x y = $y"
}

//코틀린에선 Unit이 void
fun printText(): Unit {
    print("x = $x y = $y")
}

# 람다식

람다식이란 식별자 없이 실행가능한 함수를 의미한다.

//람다식은 왼쪽이든 오른쪽이든 자료형을 지정해줘야 한다.
fun a(): Int = 1

//람다식, 자료형 지정
var sum: (Int, Int) -> Int = { a: Int, b: Int -> a + b }
//람다식, 자료형 오른쪽에만 지정
var sum1 = { a: Int, b: Int -> a + b }
//람다식, 자료형 왼쪽에만 지정
var sum2: (Int, Int) -> Int = { a, b -> a + b }

//람다식, 리턴값이 없을경우 예제
val funcSayHi: (String) -> Unit = { name: String -> println("Hi $name") }

//메소드에 람다식을 인수로 받는 예제
fun main(args: Array<String>) {
    temp(1, 2, { a: Int, b: Int -> a + b })
    temp(1, 2) { a: Int, b: Int -> a + b }
    temp(1, 2, ::sum3)
    emptyrambda(3) { 7 }
    emptyrambda(3, { 7 })
    emptyrambda(3, { -> 7 })
    emptyrambdas(3) { a: Int -> a + 7 }
}

fun temp(a: Int, b: Int, c: (Int, Int) -> Int) = c(a, b)
fun sum3(a: Int, b: Int): Int {
    return a + b
}

fun emptyrambda(a: Int, c: () -> Any) = c()
fun emptyrambdas(a: Int, c: (a: Int) -> Any) = c(a)

# 익명 함수

람다식 안에서 return 할 수 없는 경우가 있다.
처리 중간에 조건을 주고 중간에 빠져나오고 싶지만 return이 없는 람다식의 경우다.

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

numbers.forEach { number ->
    if (number % 2 == 1) {
        // 여기에서 처리를 끝내고 싶은 경우
    }
    ...
}

//이러한 경우에는 람다식이 아닌 익명 함수를 사용하는 것이 좋다.
numbers.forEach(fun(number: Int) {
    if (number % 2 == 1) {
        return // 처리 종류
    }

    ... 
})

# 고차 함수

고차함수란 함수의 인자나, 반환값이 람다식인경우를 말한다.
예를들면 list의 filter 나 map은 인자값으로 람다를 받기 때문에 고차함수다.

class Order(val itemCount: Int) 

fun getShippingCostCalculator(reqOpt: Boolean): (Order) -> Double { 
    if (reqOpt == true) { 
        return { order -> 6 + 2.1 * order.itemCount } 
    } 
        return { order -> 1.2 * order.itemCount } 
} 

fun main(args: Array) { 
    val calculator = getShippingCostCalculator(true) 
    println("Shipping costs ${calculator(Order(3))}") 
}

# inline 함수

고차 함수를 사용할 때 java 1.6 과의 호환성을 위해 런타임에 패널티가 있고.
각 함수는 객체고, 클로저를 가지고 있다. 따라서 메모리를 차지하고 함수 콜을 위한 런타임 오버헤드 가 있다. 이 때 inline 키워드를 사용하면 컴파일시 bytecode가 복사되어 들어가기때문에 이를 방지할 수 있다.

inline fun test(){
    println(123)
    println(456)
    println(789)
}
fun testfun(){
    test()
}

// 컴파일시엔 아래처럼 변환된다.

fun testfun(){
    println(123)
    println(456)
    println(789)
}

# 확장 함수

코틀린에서는 상속, 디자인 패턴을 사용하지 않고 새로운 기능을 가지는 클래스로 확장할 수 있는 기능을 제공한다.
extension 이라 부르는데 아래 예제를 봐보자.

var strs = "string"

val addStr = fun String.(str: String): String {
    return this + str
}

fun String.deleteLastStr(successor: String): String {
    return this + successor
}
// 여기서 리시버타입은 String이된다. 즉 확장하는 자기 자신의 타입인것
//책에서 나오는 리터럴은 람다를 넘겨준 식. 즉 표현식 그 자체임
//         여기 부분
private fun String.deleteLastStr(): String {
    return this.substring(0,this.length-1)
}

private fun Any.deleteLastStrㄴ(): String {
    return this.toString().substring(0)
}
var s = 1
// str = str.addStr("하기 딱 좋은 날씨로구나.")

//확장함수 심화
fun extendFun() {
    fun String.lastChar1(): Char = this.get(this.length - 1)

    //this 생략 가능
    fun String.lastChar2(): Char = get(length - 1)

    fun <T> Collection<T>.convertToString(
        separator: String = ",",
        prefix: String = "(",
        postfix: String = ")"
    ): String {
        var result = prefix
        this.map { it.toString()+separator }.forEach { result = result + it }
        // == this.forEach { result = result + it + separator }
        return result.deleteLastStr() + postfix
        
        //String 제네릭
        fun Collection<String>.join(
            separator: String = ",",
            prefix: String = "(",
            postfix: String = ")"
        ): String = this.toString() + " numbers"
        //사용
        val intlist = listOf(1, 2, 3, 4)
        //intlist.join() //에러!!! Int 타입은 불가능하다
        // 사용
        val list = listOf("1", "2", "3")
        print(list.joinToString())
}

//확장 프로퍼티
class extendProperty {
    //get() 구현
    val String.lastChar: Char
        get() = get(length - 1)
    //  get() { return last().toString()}

    //리스트인경우 get(), set() 구현
    var List<Any>.name: String
        get() = name
        set(value) {
           name = last().toString()
        }

    fun test() {
        "zerog".lastChar        //확장 프로퍼티 호출
        val list = listOf("a", "b", "c")
        list.name              //get()
        list.name = "d"      //set()
    }
}

# getter, setter 오버라이딩

코틀린에서는 getter와 setter 를 오버라이딩 할 수 있다.

//getter 오버라이딩을 이용해 nullcheck 처리 및 uppercase 반환 예제
class nullcheck {
    var string: String? = null
        get() = if (field == null) "null" else field

    var stringupper: String? = null
        get() = if (field == null) "null" else field.toString().toUpperCase()
}
//when을 적용해서 쓸 수도 있다.
class FakeAge {
    var age: Int = 0
        set(value) {
            field = when {
                value < 18 -> 18
                value in 18..30 -> value
                else -> value - 3
            }
        }

}

class getexample {
    val array = mutableListOf<Int>(1, 2, 3)
    val isListBig: Boolean
        get() = array.size > 2

    var name = "test"
        get() = field.toUpperCase()    }

    fun getsetExample() {
        getexample().isListBig
    }
}

# vararg, 가변인자

코틀린에서는 가변인자를 받고싶을 때 아래와 같이 하면 된다.

fun varags() {
    val list = arrayOf("as", "as1", "as2", "as3")
    varargTest(*list)
}

fun varargTest(vararg numbers: Int) {
    numbers.map { it ->
        println(it)
    }
}

fun varargTest(vararg a: String) {
       for (a_ in a) {
        println(a_)
    }
}