코틀린 (Kotlin) 함수형 프로그래밍


함수형 프로그래밍

  • 불변성(immutable)
  • 참조 투명성(referential transparency)
  • 일급 함수(first-class-function)
  • 게으른 평가(lazy evaluation)

순수한 함수

  • 동일한 입력->동일한 결과
  • 부수효과 없음
  • 부수효과...
    • 전역 변수, 객체 상태변경
    • 파일, 네트워크 출력
    • 예외발생

일급 함수

  • 함수를 함수 매개변수로 사용
  • 함수를 반환값으로 사용
  • 함수를 변수에 저장 가능

게으른 평가

val lazy: String by lazy {
   //long work...
}

print(lazy)
print(lazy)

값을 사용할 때 lazy{...} 작업이 실행되어 할당된다.
코드가 실행될때 할당되지 않기때문에 오래걸리는 작업을 필요할때 실행하여 작업을 효율적으로 동작가능.
val 로만 가능하며 최초 한번만 할당되고 두번째부터는 값이 재사용된다.

시퀀스(sequence)

val infinite = generatieSequence(0) { it + 10 }
infinite.take(10).forEach {println("$it")}

실제 사용할때 사용되기때문에 무한대 자료구조로서 사용할 수 있다.

Unit

public object Unit {
override fun toString() = "kotlin.Unit"
}
Unit 은 자바의 void 처럼 아무것도 반환하지 않을때 사용.
Unit 은 참조형 타입이다.


확장함수

private fun String.addHelloPrefix(): String = "Hello, ${this}"

if 조건 표현식

조건문을 구문과 표현식으로 사용할 수 있다.
표현식으로 사용된 경우 else 가 필요하며 변수에 할당할 수 있다.

val x: Int = 1
val y: Int = 2
val max: Int = if (x > y) x else y

when 조건

switch 와 동일하게 사용가능.
패턴 매칭 사용 가능.

fun checkValue(value: Any) = when (value) {
    "kotlin" -> "kotlin"
    in 1..10 -> "1..10"
    11, 15 -> "11 or 15"
    is User -> "User"
    else -> "SomeValue"
}

if else 처럼 동일하게 사용 가능.
when 에 value 없이 사용.

fun checkCondition(value: Any) = when {
    value == "kotlin" -> "kotlin"
    value in 1..10 -> "1..10"
    value === User("Joe", 76) -> "=== User" //주소값 비교
    value == User("Joe", 76) -> "== User(Joe, 76)"//데이터equls비교
    value is User -> "is User"
    else -> "SomeValue"
}

List<Int> 처럼 <Int> 를 패턴매칭 할 수 없다.

fun sum(numbers: List<Int>): Int = when {
    numbers.isEmpty() -> 0
    else -> numbers.first() + sum(numbers.drop(1))
}

for문

for((index, item) in collection.withIndex()) {
    println($index, $item)
}

for(i in 1..3)) {...}
for(i until 1..3) {...}
for(i in 6 downTo 0 step 2) {...}

인터페이스(interface)


  • 다중 상속
  • 추상 함수
  • 함수 본문 구현
  • 여러 인터페이스에서 같은 이름 함수 가능
  • 추상 프로퍼티
interface Foo {
    fun printFoo()
    fun printFoo2() {
        println("Foo2")
    }
    fun printKotlin() { println("Foo Kotlin") }
}

interface Bar {
    fun printBar()

    fun printKotlin() { println("Bar Kotlin") }

}

class Kotlin: Foo, Bar {
    override fun printFoo() {...}
    override fun printBar() {...}
    override fun printFoo2() { println("Kotlin-Foo2") } //kotlin.printFoo2()->"Kotlin-Foo2" 출력
    //다중 상속에서 동일한 함수 호출시
    overrde fun printKotlin() {
        super<Foo>.printKotlin()
        super<Bar>.printKotlin()
    }


}

추상 프로퍼티

인터페이스에서는 프로퍼티 초기화할 수 없으나 get() 으로 사용 가능하며 get() 있으면 override 하지 않아도 됨.

interface Foo {
    val bar: Int
        get() = 3
}
class Kotlin : Foo {
    override val bar: Int = 3
}

클래스(class)

class User(var name:String, val: age: Int = 20)
getter(), setter() 를 자동으로 생성.
기본값 설정 가능.

데이터 클래스(data class)

getter, setter, hashCode, equals, toString, copy, componentN 자동 생성 됨.

enum 클래스

enum class Error(val num: Int) {
    WARN(2) {
         override fun getErrorName():String {
             retrun "WARN"
         }
    }
    abstract fun getErrorName(): String
}

sealed 클래스

클래스를 묶은 클래스.
하위클래스는 sealed class와 동일한 파일에서만 선언 가능.


sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()

fun getSum(p1: Double, p2: Double): Sum {
    return Sum(Const(p1), Const(p2))
}

when 으로 패턴매칭이 가능하며 else 를 작성할 필요 없다.
fun eval(expr: Expr): Double = when(expr) {
    is Const -> expr.number
    is Sum -> eval(expr.e1) + eval(expr.e2)
    NotANumber -> Double.NaN
}

객체 분해

componentN 함수가 객체 분해시 사용.

val user: User = User("kotlin", 20)
val (name, age) = user
val (name, _) = user //_는 사용안 할때
for ((name, age) in users) { ... }
{ p1, p2 -> "$p1, $p2" } // 매개변수 두개를 받음
{ (p1, p2) -> "$p1 $p2" } //튜플 하나로 받아 객체 분해

컬렉션

불변: List, Set, Map...
가변: MutableList, MutableSet, Mutablemap...

val list: List<Int> = listOf(1,2,3,4,5)
list.add(6) //컴파일 오류. List,Set 은 불변이기때문에 add함수가 없다
val newList = list.plus(6) //새로 생성해서 추가
list 출력 -> 1,2,3,4,5
newList 출력 -> 1,2,3,4,5,6

val list: MutableList<Int> = mutableListOf(1,2,3,4,5)
list.add(6) //정상

맵(map)

val map1 = mapOf(1 to "One", 2 to "Two") //{{1=one, 2=two}
val map2 = map1.plus(Pair(3, "Three")) //{1=one, 2=two, 3=three}

val mutablemap = mutableMapOf(1 to "One", 2 to "Two")
mutablemap.put(3, "Three")

제네릭

fun <T> head(list: list<T>) : T { ...}

let 함수

자신을 파라메터로 넘겨 마지막 블럭값을 리턴.
널처리에 사용.

fun<T,R> T.let(block: (T) -> R): R
val name = user?.let { it.lastName + it.firstname } ?: "error"

with 함수

fun <T,R> with(receiver: T, block: T.() ->R): R

여기서 T.() 는 람다 리시버로 block 함수에서 this를 사용하지않고 함수나 프로퍼티에 접근할 수 있다.

val stringToInt : String.() -> Int = { toInt()  //String 함수 바로 접근 가능 }
val result = with(person) {
    name = "kotlin"
    age = 20
    this //객체 자체 접근시 this 사용
}

run 함수

1. fun <T,R> T.run(block: T.() ->R) : R
val result = person.run {
    name="kotlin"
    age = 20
    this
}

2. fun <R> run(block: () -> R): R
객체를 생성하기 위한 명령문을 하나의 블록으로 묶는 용도
코드 가독성 향상
val person = run {
    val name = "kotlin"
    val age = 20
    Person(name, age)
}

apply 함수

fun <T> T.apply(block: T.() -> Unit): T
block 함수 내에 반환값이 없고 자신을 반환.
객체만 변경할때 유용.

val result = person.apply {
   name = "kotlin"
   age = 20
}
run 함수와 다르게 this 없이 person객체를 바로 할당 가능


also 함수

fun <T> T.also(block: (T) -> Unit): T
객체 자체를 변경할 때 사용.
val result = person.also {
    it.name = "kotlin"
    it.age = 20
}

*apply, also 는 빌더패턴과 동일한 용도로 사용


close 함수

closeable object 를 제공하는 객체에 대해 클로즈 작업을 자동으로 해줌
FileInputStream("...").use { property.load(it) } // 자동 close

공변 <out T>

타입 S가 T의 하위 타입일 때 Box[S]가 Box[T] 의 하위 타입인 것
kotlin < jvm < language 상속 관계가 존재할 때,

covariant(languageBox) //컴파일 오류
covariant(jvmBox)
covariant(kotlinBox)

fun covariant(value: Box<out JVM>)  {...}

반공변 <int T>

타입 S가 T의 하위 타입일 때 Box[S]가 Box[T] 의 상위 타입인 것

covariant(languageBox)
covariant(jvmBox)
covariant(kotlinBox) //컴파일 오류

fun covariant(value: Box<in JVM>)  {...}

in, out 키워드에서 in은 입력값(set) 에서만 사용할수 있고 out은 반환값(get)에서만 사용할 수 있음


댓글

이 블로그의 인기 게시물

코틀린 (Kotlin) filter, map, all, any, count, find, groupBy, flatMap 함수 정리

코틀린 (Kotlin) 인터페이스 정리

RecyclerView 에서 notifyItemChanged()의 payload 이해하기