Groo

Kotlin 코틀린의 함수형 프로그래밍 본문

프로그래밍 언어/Kotlin

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

김주엽 2020. 1. 5. 16:54

안녕하세요 오늘은 Kotlin 코틀린 언어의 함수와 함수형 프로그래밍에 대해서 알아보도록 하겠습니다.
함수와 함수형 프로그래밍은 설명할 내용이 많아 내용들을 분할 시켜 여러 편으로 글을 작성하도록 하겠습니다.

👨‍💻 함수의 의미는?

함수의 개념과 기능은 Kotlin 코틀린 언어에서만 사용하는 것이 아닌 다양한 프로그래밍 언어에서 현재 많이 사용하고 있습니다. 그럼 함수의 의미는 무엇일까요? 함수는 여러 값(인자)을 입력받아 기능을 수행하고 결괏값을 반환하는 코드의 모음이라고 할 수 있습니다. 즉 함수를 사용한다면 코드의 재사용성이 높아지면서 프로그램의 효율성이 더 높아지기 때문에 함수를 많이 사용합니다. 또한 저희가 매번 프로그래밍을 할 때 볼 수 있는 main 함수 또한 함수의 일부입니다. 즉 저희는 프로그래밍을 한다면 함수를 안 쓰고 프로그래밍을 할 수가 없으므로 함수에 대한 개념과 이해를 잘하는 것이 중요합니다.

🙅‍♂️ 함수의 정확한 사용 방법은?

이전에 함수에 대한 개념이 정확히 잡혀있지 않았던 저는 함수를 사용하는 이유가 코드의 가독성 때문에 코드를 main 함수에 모두 작성하지 않고 기능 별로 함수를 구성하여 코드를 분리하였습니다. 그러나 이러한 작업은 옳지 않은 작업입니다. 함수를 사용하는 이유는 위에서도 말했듯이 코드의 재사용성 때문에 함수를 작성해야지 기능 별로 구분을 하기 위해 함수를 사용해서는 안됩니다. 따라서 코드의 기능 별로 함수로 따로 나누는 것 또한 틀린 것은 아니지만 함수를 사용하는 이유는 코드의 재사용성이라는 것을 프로그래밍을 할 때 기억을 하면서 프로그래밍을 하시는 것이 정말 중요하다고 저는 매번 생각을 하며 현재는 프로그래밍을 하고 있습니다.

쓰레기를 분리수거하여 재활용을 하는 것과 같이 함수 또한 재활용성을 의미한다.

🧏‍♂️ 함수를 효율적으로 사용해보자!

아래 4개의 코드 블럭은 모두 같은 목적, 같은 함수, 같은 값을 반환합니다. 그러나 아래로 내려갈수록 코드의 가독성은 높아지고 효율성 또한 높아집니다. 이와 같이 방법은 실무에서 또한 많이 사용하니 유의하시는 것이 중요할 것 같습니다. 아래의 코드에 대해서 간략하게 설명을 하면 sum이라는 함수가 있으며 매개변수로 a, b라는 Int 자료형을 가진 값들을 전달받고 있으며 전달받은 값을 더하여 Int 자료형으로 반환을 다시 하는 모습을 볼 수 있는 어렵지 않은 함수라고 할 수 있습니다.

fun sum(a : Int, b : Int) : Int{

    var sum = a+b
    return sum
}
fun sum(a : Int, b : Int) : Int{ // 지역 변수 sum 생략
    return a + b 
}
fun sum(a : Int, b : Int) : Int = a + b // 중괄호{} 생략 및 return 생략
fun sum(a : Int, b : Int) = a + b // 반환형 표시 생략

⛪ 함수의 매개변수, 인자의 차이점은?

함수를 사용하다보면 매개변수와 인자라는 단어를 많이 들어볼 것이다. 하지만 매개변수와 인자는 같은 의미라고 생각을 하시는 사람들이 많이 존재하였습니다. 하지만 매개변수와 인자는 다른 개념입니다. 함수를 선언할 때를 매개변수라고 부르며 함수를 호출할 때 전달하는 값을 인자라고 부릅니다. 잘 이해가 되지 않으실 것 같아 아래의 예시 코드를 참고하시면 좋을 것 같습니다.

fun main() {

    val result1 = sum(3, 2) // 3, 2 인자
    val result2 = sum(6, 7)

    println(result1)
    println(result2)
}

fun sum(a : Int, b : Int) = a + b // a, b 매개변수

💥 반환값이 없는 함수는 어떻게?

함수를 생성하다 보면 반환 값이 필요 없는 경우 또한 존재합니다. 그럴 때 Java에서는 void 키워드를 사용하여 반환 값이 없다는 것을 표시해주었습니다. 그면 Kotlin에서는 어떻게 해야 할까요? Kotlin에서는 void를 대시한 Unit이라는 키워드가 존재합니다. 하지만 void와 Unit 키워드 사이에는 차이점이 존재합니다. void는 정말로 아무것도 반환을 하지 않으나 Kotlin의 특수한 자료형 Unit은 특수한 객체를 반환한다는 차이점이 존재합니다. 또한 함수에 반환 값이 없다면 Unit 자료형을 생략할 수도 있습니다. 그러나 Unit 자료형을 적지 않더라도 Unit 자료형을 반환한다는 점을 절대 잊어서는 안 된다는 것을 기억해야 합니다.

fun printSum(a: Int, b : Int) : Unit = println("sum : ${a + b}")
fun printSum(a: Int, b : Int) = println("sum : ${a + b}") // Unit 생략

🎨 매개변수 제대로 활용해보자!

다양한 함수를 생성하면서 코드를 작성하다 보면 함수의 매개변수를 지정해야 하는 경우가 많을 것이다. 하지만 함수를 호출할 시 함수의 매개변수로 전달해야 하는 경우도 있으며 매개변수를 전달하지 않고 함수를 사용하는 경우도 있을 것이다. 이럴 때를 위해 Kotlin에서는 "default" 문자열을 함수에 전달하기로 하였다. 그럼 아래의 코드를 통해 확인해보도록 하겠습니다.

fun main(){

   add("김주엽", "default")
   add("이영은", "default")
   add("김경훈", "default")
}

fun add(name : String, email : String){
}

하지만 위의 코드와 같이 전달할 값이 없을 경우 매번 default 문자열을 전달해야하는 것 또한 귀찮은 부분이다. 그렇기 때문에 Kotlin 에서는 위의 방법에서 조금 더 업그레이드된 add 함수의 매개변수에 직접 값을 default로 지정을 하는 것이다.

fun main(){

   add("김주엽")
   add("이영은")
   add("김경훈")
}

fun add(name : String, email : String = "default"){
}

위와 같이 코드를 작성하게 된다면 main 함수에서 add 함수를 호출할 시 인자로 이름값만 전달해주면 되는 것이다. 그러나 많은 사람들이 이 과정에서 오해를 하는 부분이 있다. 그것은 main 함수에서 add 함수를 호출할 때 특정한 email 인자 값을 전달을 하여도 add 함수의 매개변수 값으로는 default 문자열 값이 지정되는 것이 아닌가?라는 생각을 많이 한다. 하지만 그것은 틀린 생각이다.

fun main() {

    val name = "김주엽"
    val email = "kjy031104@gmail.com"

    add(name)
    add(name, email)
    add("이영은", "eun3679@gmail.com")

    defaultArgs()
    defaultArgs(200)
}

fun add(name : String, email : String = "default"){
    val outPut = "${name}님의 이메일은 ${email}입니다."
    println(outPut)
}

fun defaultArgs(x : Int = 100, y : Int = 200){
    println(x + y)
}

위의 코드를 보면 위에서 걱정하였던 문제들이 해결되는 모습을 볼 수 있다. 인자로 전달한 값은 그 함수에서 매개변수로 활용이 되는 모습을 볼 수 있으며 만약 인자로 전달한 값이 없다면 그때 default로 지정된 값이 사용되는 모습을 볼 수 있다.

🎁 매개변수 이름과 함께 인자 전달?

다양한 함수를 이용하다 보면 그 함수의 매개변수로 인자를 여러 개 전달을 해야 하는 경우가 있을 수도 있습니다. 그렇게 인자를 많이 전달하다 보면 어떤 매개변수의 인자를 넘기는지 헷갈리는 경우가 있습니다. 그렇기 때문에 Kotlin에서는 매개변수의 이름을 붙여 인자 값을 전달할 수 있도록 도와주는 기능이 있습니다. 이러한 과정에서는 매개변수의 이름을 주의해서 작성을 해야 합니다.

fun main(){

    namedParam(x = 200, z = 100)
    namedParam(z = 150)
}

fun namedParam(x : Int = 100, y : Int = 200, z : Int){
}

🛒 매개변수의 개수를 가변적으로?

우리는 코드를 작성하다 보면 함수의 매개변수를 어떨 때는 3개, 어떨 때는 4개 등 똑같은 목적과 기능을 하는 함수일지라도 매개변수의 개수가 달라지는 경우가 있다. 그러한 경우를 대비하기 위해서 Kotlin에서는 가변 인자 키워드인 vararg라는 키워드를 제공하고 있다. 그럼 아래의 코드를 통해 vararg 키워드를 사용하는 예제를 통해 매개변수에 관련된 내용을 모두 끝내도록 하겠습니다.

fun main(){

    normalVarargs(1, 2, 3, 4)
    normalVarargs(5, 6, 7)
}

fun normalVarargs(vararg counts: Int){
    for(num in counts){
        print("$num ")
    }
    println()
}

위의 코드를 간략히 본다면 normalVarargs 함수에서 varagrg 키워드를 통해 counts 매개변수에 인자 값들을 가변적으로 전달받고 있습니다. 그 후 추후 배울 for 문을 활용하여 counts 변수 안에 있는 값들을 모두 출력하는 모습을 볼 수 있습니다. 이렇게 vararg 키워드를 사용한다면 배열과 같은 형태로 값을 가변적으로 입력받을 수 있는 모습을 볼 수 있다는 것을 알았습니다.

👑 Kotlin 코틀린은 어떤 프로그래밍 방식?

Kotlin은 현재 Java 언어에서 지향하는 객체지향 프로그래밍 방식과 함수형 프로그래밍 방식 두 가지를 모두 활용하고 있는 다중 패러다임 언어입니다. 이 두 가지의 프로그래밍 방식을 사용한다면 코드를 더욱 간략하게 할 수 있으며 대규모 프로젝트에도 적합하기 때문에 현대 프로그래밍 언어가 지향하는 특징입니다. 특히 함수형 프로그래밍은 테스트나 재사용성이 더 좋아지면서 개발 생산성이 늘어나는 장점 덕분에 정말 중요한 프로그래밍 방식이라고 할 수 있습니다. 객체 지향 프로그래밍 방식은 다음번에 알아보도록 하고 오늘은 먼저 함수형 프로그래밍에 대해서 더욱 자세히 알아보는 시간을 가지도록 하겠습니다.

⚽ 함수형 프로그래밍이란 무엇인가?

함수형 프로그래밍은 순수 함수를 작성하여 프로그램의 부작용을 줄이는 기법을 의미합니다. 대표적인 함수형 프로그래밍으로는 람다식과 고차 함수가 있습니다. 먼저 함수형 프로그래밍에 대해서 알기 위해서는 순수 함수에 대해서 먼저 이해를 해야 합니다.


📕 순수 함수란?

어떤 함수가 같은 인자에 대해서 항상 같은 결과를 반환하면 부작용이 없는 함수라고 말합니다. 그리고 부작용이 없는 함수가 함수 외부의 어떤 상태도 바꾸지 않는다면 순수 함수이며 즉 순수 함수는 부작용이 없어 값이 예측 가능해 결정적이라고도 부릅니다.

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

위의 코드를 통해 순수 함수의 예를 들어봤습니다. 그럼 위의 코드가 왜 순수 함수가 되는 것일까요? 먼저 위의 함수는 똑같은 함수의 인자가 전달되면 그 값을 덧셈하여 반환을 하는 과정이 항상 같습니다. 또한 이 함수를 호출함에 따라 외부의 변수 또는 함수 등의 상태 변화를 일으키지 않아 순수 함수의 조건을 만족시키므로 위의 코드의 함수는 순수 함수라고 부를 수 있습니다.

fun check(){

    val test = User.grade()
    if(test != null) process(test)
}

반면에 위의 코드는 check 함수 안에 test라는 지역 변수를 생성하였으며 지역 변수 안에 User 객체의 grade 함수를 호출하고 있습니다. 또한 if 문의 조건문에 성립을 할 시 process 함수를 호출하고 있습니다. 이러한 과정을 통해 순수 함수의 조건에 모두 성립하지 않아 위의 함수는 순수 함수가 되지 않습니다. 추후 여러분이 프로그램을 개발하다 보면 프로그램의 규모 또한 커지면 부작용을 모두 통제하는 순수 함수를 만들기는 어렵습니다. 그러나 가능한 순수 함수를 통해 함수를 구성하려고 하는 것이 좋습니다.

📙 람다식 이란?

람다식은 수학의 람다 대수에서 많이 유래를 하여 생성이 되었습니다. 자주 사용하는 기호는 중괄호 기호와 화살표 기호를 많이 사용합니다. 함수형 프로그래밍의 람다식의 의미는 다른 함수의 인자로 넘기는 함수, 함수의 결괏값으로 반환하는 함수, 변수에 저장하는 함수를 대체적으로 많이 의미하며 이 내용에 대해서는 추후 다시 고차 함수와 함께 더 자세히 설명을 할 예정입니다.

{ x, y -> x + y } // 일반적인 람다식의 모습 (중괄호, 화살표)

📒 일급 객체란?

함수형 프로그래밍에서는 함수를 일급 객체라고 생각을 하고 있으며 람다식 또한 일급 객체의 특징을 많이 지니고 있습니다. 그러면 일급 객체란 무엇일까요? 아래의 글을 통해서 일급 객체의 큰 특징에 대해서 알아보도록 하겠습니다.

 

일급 객체의 특징


1. 일급 객체는 변수에 담을 수 있다.

2. 일급 객체는 함수의 인자로 전달할 수 있다.

3. 일급 객체는 함수의 반환 값에 사용할 수 있다.

만약 함수가 일급 객체라면 일급 함수라고 부를 수 있으며 일급 함수에 이름이 없다면 람다식 함수, 람다식이라고 부를 수 있습니다. 즉 람다식은 일급 객체의 특징을 가진 이름 없는 함수라고 다시 한번 정의를 내릴 수 있다는 것을 알 수 있습니다.

📗 고차 함수란?

고차 함수는 다른 함수를 인자로 사용하거나 함수를 결괏값으로 반환하는 함수를 의미합니다. 즉 위에서 방금 말한 일급 객체 혹은 일급 함수를 서로 주고받을 수 있는 함수가 고차 함수이기도 하면서 일급 함수가 고차 함수이기도 합니다. 

fun main(){

   println(highFunc({ x, y -> x + y}, 10, 20))
}

fun highFunc(sum : (Int, Int) -> Int, a : Int, b : Int) = sum(a, b)

위의 highFunc 함수는 sum이라는 매개변수가 존재하며 sum 매개변수는 람다식 함수 형식으로 되어있습니다. 따라서 highFunc 함수는 sum 매개변수를 통해 람다식 함수를 인자로 받아들이는 고차 함수가 되는 것을 알 수 있습니다.

fun main(){
   println(highFunc({ x, y -> x + y}, 10, 20))
}

fun highFunc(sum : (Int, Int) -> Int, a : Int, b : Int) : Int{
   return sum(a, b)
}

위의 코드를 조금 더 간략하게 설명을 한다면 main 함수에서 highFunc 함수를 호출할 때 첫 번째 인자로 람다식 형태로 인자를 전달하고 있습니다. 그 후 highFunc 함수에서 첫 번째 sum 매개변수에서는 인자로 전달받은 값을 통해 람다식 함수의 완전체가 된 것을 볼 수 있습니다. 마지막으로 return 문을 통해 매개변수의 sum 함수를 호출하여 함수를 반환하고 있습니다. 즉 고차 함수의 조건인 highFunc 함수에서는 함수를 인자로 사용하고 있으며, 함수로 결괏값을 반환하는 모습을 볼 수 있습니다.


자 그럼 지금까지 함수형 프로그래밍에 대해서 알기 위해 함수형 프로그래밍의 다양한 특징들에 대해서 알아보았습니다. 그럼 마지막으로 함수형 프로그래밍의 정의와 특징에 대해서 알아본 후 함수형 프로그래밍에 대한 설명을 모두 마치도록 하겠습니다. 

 

함수형 프로그래밍의 정의와 특징


1. 순수 함수를 사용해야 한다.


2. 람다식을 사용할 수 있다.

3. 고차 함수를 사용할 수 있다.

👍 글을 마치며

오늘은 Kotlin 코틀린 언어의 함수와 함수형 프로그래밍에 대해서 알아보았습니다. 함수형 프로그래밍은 현재 프로그래밍의 시대에 맞는 장점들과 특징들을 많이 가지고 있는 프로그래밍 방식입니다. 즉 아직은 함수형 프로그래밍의 순수 함수, 일급 객체, 람다식, 고차 함수 등에 대해서 잘 이해가 되지 않고 어려우시겠지만 계속 연습을 하고 도전을 하면 좋을 것 같습니다. 그럼 다음번에는 람다식과 고차 함수에 대한 내용들을 더욱 자세히 공부해볼 예정이며 다음 파트에서는 더욱 어렵고 헷갈리기 때문에 저도 열심히 더 공부해서 글 포스팅을 해보도록 하겠습니다. 그럼 함수와 함수형 프로그래밍 파트에 대해서 모두 마치도록 하겠습니다. 감사합니다.

 

참고 : Do it 코틀린 프로그래밍

Comments