수학적 함수와 명령형 프로그래밍에서 사용되는 함수

  • 명령형 프로그래밍의 함수
    • 프로그램의 상태 값을 바꿀 수 있는 부수 효과가 생길 수 있음
    • 참조 투명성이 없고, 같은 코드라도 실행되는 프로그램의 상태에 따라 다른 결과값이 나옴
  • 함수형 프로그래밍의 함수(수학적 함수)
    • 함수의 출력 값은 함수에 입력된 인수에만 의존
    • 인수 x에 같은 값을 넣고 함수 f를 호출하면 항상 f(x)라는 결과가 나옴(참조 투명성)
    • 부수 효과를 제거하면 프로그램 동작 이해와 예측이 쉬움

함수형 프로그래밍 기본 요소

  • pure functino
    • 부수 효과(side-effect)가 없는 함수
    • thread-safe하여 병렬 계산 용이
  • anoymous function: 익명 함수 (람다 함수)
    • 코틀린에서는 x → x*x
  • higher-order functino: 고차(고계) 함수, 함수를 인자나 리턴으로 다루는 함수
    • 코틀린에서는 (1.10).map{it * it}

+& 코틀린은 순수한 함수형 언어가 아님, 함수형 언어의 요소 뿐 아니라 명령형 언어, 객체 언어 패러다임을 모두 가지고 있음

Lambda

data class MyClass(val a:Int, val b:String)

fun lambdaTest(a:(Int) -> Int): Int{
    return a(10)
}

fun main()
{
    val sum = {x:Int, y:Int -> x+y}
    println("sum = ${sum(10,20)}")

    val array = arrayOf(MyClass(10,"class1"),MyClass(20,"class2"),MyClass(30,"class3"))
    println(array.filter({c:MyClass -> c.a < 15}))

    array.filter() {c:MyClass -> c.a < 15 }
    array.filter { c -> c.a <15 }
    // 람다 파라미터가 하나일 경우 it 으로 생략이 가능함
    array.filter { it.a < 15 }

    print(lambdaTest { it+10 })

    val title = "Num:"
    val list = listOf(1,2,3,4)
    list.forEach{ println("$title $it")}
}
  • 어원: 람다 대수
  • 이름이 없는 함수
  • 형식: {파라미터 → 함수 바디}
    • {x: Int, y: Int → x+y}
    • 함수 바디에서 마지막 수식(expression)이 리턴 값이 됨
  • Lambda에서 로컬 변수 참조
    • val, var 구분 없이 모든 로컬 변수 참조가 가능함

Collection filter, map, groupBy와 lambda

data class Student(val name: String,val age:Int)

fun main(){
    val data = listOf(Student("Jun",21),Student("James",25)
    ,Student("Tom",21),Student("Jane",23),Student("John",23))
    println(data.filter { it.age >=22})
    println(data.map{it.age - 20})
    println(data.filter { it.age>=22 }.map (Student::name))
    // data.filter{it.age>22}.map{it.name}
    println(data.groupBy { it.age })

    val words = arrayOf("hello","hi","hot","aple","orange","access","order","about")
    println(words.groupBy { it.first() })
}
  • 함수형 프로그래밍에서 Collection을 다루는 방법
  • lambda가 편리하게 사용됨
  • filter, map
  • filter: 특정 조건을 만족하는 원소만 포함하는 Collection을 생성/리턴
    • filter에 주어진 람다를 모든 원소에 수행하여 true를 리턴하는 경우만 모음
    • filter에 넘겨주는 람다는 Boolean을 결과로 하는 수식이어야 함
  • map: 모든 원소에 대해 특정 연산을 수행한 결과를 모아서 Collection을 생성/리턴
    • map에 주어진 람다를 모든 원소에 대해 수행하고 그 결과를 모음
  • groupBy: 주어진 조건에 따라 Collection을 그룹으로 나눈 후 map을 생성/리턴
  • 참고: Collection은 언어 문법이 아니고 표준 라이브러리임

Collection all, any, count, find와 lambda

fun main(){
    val nums = arrayOf(-5,-4,-3,-2,-1,0,1,2,3,4,5)

    println(nums.all { it is Int })
    println(nums.all {it >0})
    println(nums.any{it>0})
    println(nums.count{it>0})
    println(nums.find{it>0})
}
  • all, any, count, find
  • all: 모든 원소가 특정 조건을 만족하면 true, 그렇지 않으면 false
  • any: 한 원소라도 특정 조건을 만족하면 true, 그렇지 않으면 false
  • count: 특정 조건을 만족하는 원소의 갯수를 리턴
  • find: 특정 조건을 만족하는 가장 처음 원소를 리턴

Collection flatMap과 lambda

fun main(){
    val lsit = listOf("abc","cde","efg")

    println(lsit.flatMap { it.toList() })
    println(lsit.flatMap { it.toList() }.toSet())

    class Classes(val name:String, val students:List<String>)
    val classes = listOf(Classes("Cprog", listOf("james","john","greg")),
        Classes("OS", listOf("james","john","jane","ton")),
        Classes("Net",listOf("john","jane","alex","sam")))

    println(classes.flatMap { it.students}.toSet().sorted())
}
  • 중첩된 Collection을 하나의 리스트로 생성/리턴
  • flatMap에 주어진 람다는 iterable 객체를 리턴
    • 이 iterable을 모두 연결하여 하나의 List로 만든다.

Collection asSequence

fun main(){
    val hugeNums = (1..1000000).toList()

    val elapsedTime = measureTimeMillis {
        hugeNums.map{it*it}.filter { it>10 }.map { it*it }
    }
    val elapsedTime2 = measureTimeMillis {
        hugeNums.asSequence().map { it*it }.filter { it>10 }.map { it*it }
    }
    val elapsedTime3 = measureTimeMillis {
        hugeNums.asSequence().map { it*it }.filter { it>10 }.map { it*it }.toList()
    }

    println("elapsedTime = ${elapsedTime}")
    println("elapsedTime2 = ${elapsedTime2}")
    println("elapsedTime3 = ${elapsedTime3}")

}
  • filter, map 등의 함수를 부를 때, 바로 다른 리스트를 생성하게 됨
  • 이런 함수를 매우 큰 데이터에 대해 여러 번 이어서 사용하면 매우 느려질 것임
  • 이런 경우 Sequence를 사용하면
    • 리스트 생성을 최대한 늦추게 하는 방법임
    • 실제 리스트 생성 작업을 최대한 늦추기 때문에 lazy 연산이라고 보통 부름
  • Seqence를 사용하여 연산을 끝낸 후에, 다시 toList()로 Collection을 바꾸어 사용함

lambda와 SAM

fun interface doInterface{
    fun doIt()
}

fun doSomething(di:doInterface)=di.doIt()

fun main(){
    doSomething(object :doInterface{
        override fun doIt() {
            println("Java-like way")
        }
    })

    doSomething(doInterface { println("SAM") })
    // 익명 함수 생략
    doSomething{ println("SAM 1")}
    val doi = { println("SAM 2")}
    doSomething(doi)
}
  • SAM: Single Abstract Method의 줄임말, 인터페이스가 하나의 메소드만 가진 경우
    • View.OnClickListener, Runnable 등 많은 인터페이스가 하나의 메소드만 가짐
  • 코틀린에서 이런 SAM인 경우 무명 클래스 인스턴스를 만드는 것이 아니라 lambda로 처리할 수 있음

Scope 함수

fun main(){
    val r1 = "hello".let {
        println("it = ${it}")
        it.length
    }
    val r2 = "hello".run {
        println("this = ${this}")
        this.length
    }
    val r3 = "hello".also {
        println("it = ${it}")
    }.length

    println("r1 = ${r1}")
    println("r2 = ${r2}")
    println("r3 = ${r3}")
}
fun main(){
    val str = with(StringBuilder()){
        // this. 생략
        append("Hello, ")
        append("Hello, ")
        append("Hello, ")
        append("Hello, ")
        toString()
    }
    println(str)

    val str2 = StringBuilder().apply{
        append("Hello, ")
        append("Hello, ")
        append("Hello, ")
        append("Hello, ")
    }.toString()
    println("str2 = ${str2}")
}
  • 객체의 이름을 반복하지 않고 그 객체에 대해 여러 연산을 수행할 수 있음
  • 종류: let, run, with, apply, alse
    • 주의: with 함수는 주어진 객체를 이용하여 블록 안에서 수행하는 작업을 처리한 후, 마지막 표현식의 값을 반환한다. (unit)
    • 주의: apply 함수는 주어진 객체를 이용하여 블록 안에서 수행하는 작업을 처리한 후, 객체 자신을 반환한다. (this)
  • 함수 인자로 lambda를 전달하는 데, 이 lambda 내에서 객체를 it으로 또는 this로 지칭
  • 함수의 리턴 값은 객체 자체 또는 lambda의 결과
  • 참고: Scope 함수는 언어 문법이 아니고 표준 라이브러리임

Collectino 연산자 오버로딩

data class Three(var x:Int, var y:Int, var z:Int):Iterable<Int>{
    operator fun get(idx:Int):Int{
        return when(idx){
            0 -> x
            1 -> y
            2 -> z
            else ->
                throw IndexOutOfBoundsException("Invaild index $idx")
        }
    }
    operator fun set(idx:Int, value:Int){
        when(idx){
            0 -> x = value
            1 -> y = value
            2 -> z = value
            else ->
                throw IndexOutOfBoundsException("Invaild index $idx")

        }
    }
    operator fun contains(value:Int) = (x==value||y==value||z==value)

    inner class MyIterator:Iterator<Int>{
        var curIdx = 0
        override fun next(): Int {
            val ret = this@Three[curIdx]
            curIdx++
            return ret
        }

        override fun hasNext(): Boolean {
            return curIdx <= 2
        }
    }

    override fun iterator(): Iterator<Int> = MyIterator()
}

fun main(){
    val three = Three(1,2,3)
    println("three[0] = ${three[0]}")
    println("three[1] = ${three[1]}")
    println("three[2] = ${three[2]}")
    println((3 in Three(1,2,3)))
    for (i in three)
        println(i)
}
  • []
    • get(idx), set(idx, value)
  • in
    • contains(value)
  • iterator
    • iterator Implementation
    • iterator method

가변 인자

fun main(){
    val list = listOf(1,2,3,4)

    val args = arrayOf("1","2","3","4")
    val list2 = listOf("1",*args)
    println(list2)
}
  • 가변 인자
    • varag를 매개 변수 이름 앞에 사용함
    • spread 연산자: *를 배열앞에 붙여서 가변 인자를 넘겨줄 수 있음

Infix Function

fun main(){
    // Any를 확장 하여 infix to를 정의한 것
    infix fun Any.to(other: Any) = Pair(this,other)
}
  • 함수 이름을 인자 중간에 넣어서 호출
  • 예) mapOf(1 to “one, 2 to “two”) 에서 to와 같은 함수
  • fun plus(1,2) → fun 1 plus 2

Destructuring declaration

data class Result(val result:Int, val status:String)
fun calcSomething(): Result{
    return Result(404,"Not Found")
}

fun main(){
    val (num,str) = 1 to "one"
    println("$num, $str")
    val collection = mapOf(1 to "one", 2 to "two")
    for((a,b)in collection){
        println("$a, $b")
    }
    val (result, status) = calcSomething()
    println("$result,$status")
}
  • 값 2개를 리턴 받아 2개의 변수에 대입

'Development > Kotlin&Android' 카테고리의 다른 글

액티비티와 인텐트  (0) 2023.04.13
안드로이드-레이아웃  (0) 2023.03.30
Kotlin 문법 Part 3  (0) 2023.03.16
Kotlin 문법 Part 2  (0) 2023.03.02
Kotlin 문법 Part 1  (0) 2023.02.28

+ Recent posts