초보 개발자의 성장기

[부스트코스] [부스트코스][코틀린 프로그래밍 기본 1/2] 한아아지와 함께하는 kotlin - 함수형 프로그래밍 (2) 순수함수와 람다식과 1급 객체 그리고 고차함수 본문

IT 강의 리뷰/코틀린

[부스트코스] [부스트코스][코틀린 프로그래밍 기본 1/2] 한아아지와 함께하는 kotlin - 함수형 프로그래밍 (2) 순수함수와 람다식과 1급 객체 그리고 고차함수

개발자 김케빈 2021. 1. 17. 23:29

이번 챕터에서는 함수형 프로그래밍 PART 2

저번 포스트에 이어서 함수형 프로그래밍을 공부해보겠습니다.

 

www.boostcourse.org/mo132/lecture/59974

 

코틀린 프로그래밍 기본 1

부스트코스 무료 강의

www.boostcourse.org

www.boostcourse.org/mo132/lecture/62509

 

코틀린 프로그래밍 기본 1

부스트코스 무료 강의

www.boostcourse.org


 

1. 순수 함수

✍️ 순수 함수란?

어떤 함수가 같은 인자에 대해서 항상 같은 결과를 반환하면 부작용이 없는 함수라고 말합니다.

그리고 부작용이 없는 함수가 함수 외부의 어떤 상태도 바꾸지 않는다면 순수 함수이며

즉 순수 함수는 부작용이 없어 값이 예측 가능해 결정적이라고도 부릅니다.

→ 순수 함수는 스레드에 사용해도 안전하고 코드의 테스트가 쉽다는 장점이 있습니다.


순수 함수의 조건

❗ 같은 인자에 대하여 항상 같은 값을 반환합니다.

❗ 함수 외부의 어떤 상태도 바꾸지 않습니다.


// 순수 함수의 예시 1
fun sum(a: Int, b: Int): Int {
    return a + b // 동일한 인자인 a, b를 입력 받아 항상 a + b를 출력(부작용이 없음)
}

// 순수 함수가 아닌 것
fun check() {
	val test = User.grade() // check() 함수에 없는 외부의 User 객체 사용
    if (test != null) process(test) // 변수 test는 User.grade()의 실행 결과에 따라 달라짐
}

const val global = 10

fun main() {
    val num1 = 10
    val num2 = 3
    val result = noPureFunction(num1, num2)
    println(result)
}

fun noPureFunction(a: Int, b: Int): Int {
	return a + b + global // 입력 값과 무관하게 외부 변수 사용
}

✍️ 순수 함수 왜 사용 할까?

- 입력과 내용을 분리하고 모듈화 하므로 재사용성이 높아진다. (여러 함수들과 조합해도 부작용이 없음)

- 특정 상태에 영향을 주지 않으므로 병행 작업 시 안전하다.

- 함수의 값을 추적하고 예측 할 수 있기 때문에 테스트, 디버깅 등이 유리하다.

2. 람다식

✍🏻 람다란?

람다는 자바 8 부터 제공되는 익명 함수 (Anonymous Functions)를 지칭하는 용어입니다.

람다식은 코드의 간결함을 주 목적으로 이용되고 고차 함수의 매개변수나 반환 값으로 자주 사용됩니다.

 

✍🏻 람다식의 표현식은 아래와 같습니다. (함수의 이름이 없고 화살표가 사용되었습니다)

     { x, y -> x + y } // { 매개변수 -> 함수내용}

// 람다식 선언과 할당
fun main() {

    var result: Int

    // 일반 변수에 람다식 할당
    val multi = {x: Int, y: Int -> x * y}
    // 람다식이 할당된 변수는 함수처럼 사용 가능
    result = multi(10, 20)
    println(result)
}


// 변수의 자료형 생략
val multi: (Int, Int) -> Int = {x: Int, y: Int -> x * y} // 생략되지 않은 전체 표현
val multi = {x: Int, y: Int -> x * y}  // 선언 자료형 생략
val multi: (Int, Int) -> Int = {x, y -> x * y} // 람다식 매개변수 자료형의 생략


// 함수의 매개변수가 없을 때
val greet: ()->Unit = { println("Hello World!") }
val square: (Int)->Int = { x -> x * x }

 

✍🏻 람다 함수는 { } 안에 매개변수와 함수 내용을 선언하는 함수로 다음 규칙에 따라 정의합니다.

 

  • 람다 함수는 항상 { }으로 감싸서 표현해야 한다.
  • { } 안에 -> 표시가 있으며 -> 왼쪽은 매개변수, 오른쪽은 함수 내용이다.
  • 매개변수 타입을 선언해야 하며 추론할 수 있을 때는 생략할 수 있다.
  • 함수의 반환값은 함수 내용의 마지막 표현식이다.

✍🏻 람다식 선언 방법

 

🟡 람다식 함수에 매개변수가 없으면 사용시 '->' 가 사용되지 않습니다. 그리고 소괄호는 생략할 수 있습니다.

fun main() {
    // 매개변수 없는 람다식 함수
    noParam({ "Hello World!" })
    noParam { "Hello World!" } // 위와 동일 결과, 소괄호 생략 가능
}

// 매개변수가 없는 람다식 함수가 noParam 함수의 매개변수 out으로 지정됨
fun noParam(out: () -> String) = println(out())

🟡 람다식 함수에 매개변수가 한 개 있을 경우에는 람다식에 화살표 기호(->) 왼쪽에 필요한 변수를 써 줘야 합니다.
     하지만 이렇게 매개변수가 한 개인 경우에는 화살표 표기를 생략하고 it으로 대체할 수 있습니다.

fun main() {
    // 매개변수 없는 람다식 함수
...
    // 매개변수가 하나 있는 람다식 함수
    oneParam({ a -> "Hello World! $a" })
    oneParam { a -> "Hello World! $a" } // 위와 동일 결과, 소괄호 생략 가능
    oneParam { "Hello World! $it" }  // 위와 동일 결과, it으로 대체 가능
}
...
// 매개변수가 하나 있는 람다식 함수가 oneParam함수의 매개변수 out으로 지정됨
fun oneParam(out: (String) -> String) {
    println(out("OneParam"))
}

🟡  매개변수가 두개일 때는 it을 사용해 변수를 생략할 수 없습니다.
      모두 나타내야 하지만 만일 필요한 경우 _(언더바)를 사용해 특정 매개변수를 생략할 수 있습니다. 

fun main() {
...
    // 매개변수가 두 개 있는 람다식 함수
    moreParam { a, b -> "Hello World! $a $b"} // 매개변수명 생략 불가
...
}
// 매개변수가 두 개 있는 람다식 함수가 moreParam 함수의 매개변수로 지정됨
fun moreParam(out: (String, String) -> String) {
    println(out("OneParam", "TwoParam"))
}

3. 1급 객체

✍🏿 아래 3 가지조건을 충족한다면 1급 객체라고 할수 있습니다.

No 조건
1 함수의 인자로 전달할 수 있어야 한다.
2 함수의 반환 값에 사용될 수 있어야 한다.
3 변수에 담을 수 있어야 한다.

✍🏿 코틀린 함수는 1급 객체지만 자바 함수는 1급 객체가 아닌 이유 [변수나 데이타에 할당 할 수 있어야 한다.]

/* 코틀린 함수와 자바 함수 비교 */

object Main {
    @JvmStatic
    fun main(args: Array<String>) {
        val a = test
    }

    val test: () -> Unit = { println("kotlin") }
}


public class java {

    public static void test(){
        System.out.println("java");
    }

    public static void main(String[] args) {
        System.out.println("java");
//        Object a = test;
    }
}

// kotlin은 a 에 type이 () -> Unit 인 test 함수 할당이 가능하지만, Java는 불가능

4. 고차 함수

✍🏽 고차 함수란?

다른 함수를 인자로 사용하거나 함수를 결과 값으로 반환하는 함수

즉 일급 객체 혹은 일급 함수를 서로 주고 받을 수 있는 함수가 고차함수

// 고차 함수 예시 1
fun main() {
    println(highFunc({ x, y -> x + y }, 10, 20)) // 람다식 함수를 인자로 넘김
}

fun highFunc(sum: (Int, Int) -> Int, a: Int, b: Int): Int = sum(a, b) // sum 매개변수는 함수  


// 고차 함수 예시 2
fun highFunc2 (x1: Int, argFun: (Int) -> Int) { // 함수 타입 선언 : argFun: (Int) -> Int
 val result = argFun(10)
println("x1 : $x1, someFun1 : $result")
}

highFunc2(10, {x -> x * x }) // 함수 대입 : {x -> x * x }

✍🏽 고차 함수 호출

 

🔸 값에 의한 호출은 함수가 인자로 전달될 경우 람다식 함수는 값으로 처리되어 그 즉시 함수가 수행된 후 값을 전달

fun main() {
    val result = callByValue(lambda()) // 람다식 함수를 호출
    println(result)
}

fun callByValue(b: Boolean): Boolean { // 일반 변수 자료형으로 선언된 매개변수
    println("callByValue function")
    return b
}

val lambda: () -> Boolean = {  // 람다 표현식이 두 줄이다
    println("lambda function")
    true 		    // 마지막 표현식 문장의 결과가 반환
}

🔸 람다식 함수의 이름을 인자로 넣어 사용하는 경우에는 람다식 자체가 매개변수에 복사되고
     해당 함수가 호출되어 사용되기 전까지는 람다식 함수가 실행되지 않습니다. 함수가 호출될 때 함수 내용이 실행됨

fun main() {
    val result = callByName(otherLambda) // 람다식 이름으로 호출
    println(result)
}

fun callByName(b: () -> Boolean): Boolean { // 람다식 함수 자료형으로 선언된 매개변수
    println("callByName function")
    return b()
}

val otherLambda: () -> Boolean = {
    println("otherLambda function")
    true
}

🔸 두 개의 콜론기호(::)를 함수 이름 앞에 사용해 소괄호와 인자를 생략하고 사용하는 경우
     일반 함수를 참조에 의한 호출로 사용할 수 있게 됩니다.

fun main() {
    // 1. 인자와 반환값이 있는 함수
    val res1 = funcParam(3, 2, ::sum)
    println(res1)

    // 2. 인자가 없는 함수
    hello(::text) // 반환값이 없음

    // 3. 일반 변수에 값처럼 할당
    val likeLambda = ::sum
    println(likeLambda(6,6))
}

fun sum(a: Int, b: Int) = a + b

fun text(a: String, b: String) = "Hi! $a $b"

fun funcParam(a: Int, b: Int, c: (Int, Int) -> Int): Int {
    return c(a, b)
}

fun hello(body: (String, String) -> String): Unit {
    println(body("Hello", "World"))
}
Comments