오늘도 더 나은 코드를 작성하였습니까?

함수형 프로그래밍이란??? (feat. kotlin) 본문

기타

함수형 프로그래밍이란??? (feat. kotlin)

hik14 2024. 8. 20. 05:12

정의

함수형 프로그래밍(functional programming) 자료 처리를 수학적 함수의 계산으로 취급하고 상태와 가변 데이터를 멀리하는 프로그래밍 패러다임의 하나이다. 명령형 프로그래밍에서는 상태를 바꾸는 것을 강조하는 것과는 달리, 함수형 프로그래밍은 함수의 응용을 강조한다.

 

일단, 함수형 프로그래밍을 살펴보기전에, 명령형 프로그래밍과, 선언형 프로그래밍에 대해서 알아보자!

 

명령형 프로그래밍

- 프로그램이 상태와 상태를 어떻게(HOW TO) 변경시키는 지 명령들을 순서대로 써 놓은 것이다

 

선언형 프로그래밍

- 프로그램이 어떻게 해야 하는지를 나타내기보다 무엇(WHAT)과 같은지를 설명하는 경우에 "선언형"이라고 한다.

 

예를 들어서 1-10의 정수에서 짝수를 구하는 프로그램을 만든다고 가정해보자!

 

명령형 프로그래밍 code

fun main() {
    val evenNumbers = mutableListOf<Int>()
    
    for (i in 1..10){
        if (i % 2 == 0){
            evenNumbers.add(i)
        }
    }
    println(evenNumbers)
}

 

짝수들을 담을 list를 생성한다.

변수 i 에 1에서 10까지 반복하여 숫자를 대입하면서 i가 2로 나누어 떨어지면,

i의 값을 짝수 list에 담는다.

* evenNumbers, i의 변수의 값과 상태가 계속 변한다

 

선언형  프로그래밍 code

fun main() {

    val evenNumbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).filter { it % 2 == 0 }
    println(evenNumbers)
}

 

짝수 = 1-10의 정수에서 2로 나누어 떨어지는 것

 

이제 두 패러다임에서 중점적으로 생각하는 것을 조금 더 이해했을 것이라 생각한다.

함수형 프로그래밍은 선언형 프로그래밍 패러다임을 따르고, 함수의 응용을 강조한다.

 

함수형 프로그래밍을 위한 도구들

불변데이터(Immutable Date)

- 선언후 초기화를 제외하고 값을 대입하는 경우는 없다.

- 한번 정해진 값은 바뀌지 않는다!

참조의 투명성(referencial transparency)

- 프로그램의 다른 변경 없이 특정한 표현식(expression)을 값(value)로 대체할 수 있다.

- 참조 상 투명한 함수는 표현식(expression) 평가(evaluation)하게 되면 동일한 인자에 대해 동일한 을 반환해야 한다.

그러한 함수를 순수 함수라고 부른다.

순수함수(pure function)

- 같은 입력 값에 대해 항상 동일한 출력 값을 반환하는 함수

- 함수 외부영역에 side effect(부수효과)가 없는 함수

- 외부 상태를 변경하지 않고, 외부 상태에 의존하지 않기 때문에 Thread-safe하다.

 

* side-effect

함수가 만들어진 목적과는 다른 효과 즉, 예상치 못한 일이 발생할 가능성이 있는 것

- 함수 외부의 변수의 값을 변경하는 행위 

- 입력된 파라미터를 변경하는 행위

- 네트워크 요청을 보내는 행위 (예외나 오류가 발생할 수 있음)

- 데이터 입출력을 하는 항위 ()

함수를 최고 클래스로...(First-Class function)

특정 프로그래밍 언어에서 function을  최고 구성요소(first-class citizens)로 다루는 경우 first-class function를 가지고 있다고 한다.

즉, 함수를 다른 함수에 인수로 전달하고, 다른 함수에서 값으로 반환하고, 변수에 할당하거나 데이터 구조에 저장하는 것을 지원 한다.

익명 함수(람다함수)도 지원하고, 함수를 Type이 있는 일반 변수처럼 취급이 가능하다.

 

*고차함수 - (Higher-Order function)

다른 함수를 매개 변수로 받고, 값으로 함수를 반환하는 함수

커링(curring)

- 여러개의 인자를 받아 호출하는 함수를 한 개의 인자를 받아 처리하는 함수로 만들어서 연속적 호출을 하는것.

- 외부함수와 내부함수로 나누어 처리합니다. 

- 마지막 인자가 들어올때까지 함수 식의 평가를 지연합니다.

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

fun curringSumLambda(a: Int): (Int) -> Int = { b -> a + b }

함수형프로그래밍의 장점

불변 데이터와 순수함수는 부수효과가 없고, Thread-safe하여 멀티스레드 환경에서 오류가 적고 안전한 코드를 작성할 수 있다.

함수의 연속적인 호출과 고차함수, 람다함수를 이용한 구현을 통해 코드가 간결해지고 이해하기 쉬어진다.

입력값에 대한 동일한 결과를 도출한다면, 프로그램을 예측하기 쉬워지며, 테스트에 있어도 용이합니다.

  

kotlin에서는 함수형 프로그래밍을 위해 무엇을 제공하는가?

 

1.  val keyword를 통한 불변 변수 생성 

=> 코틀린에서 변수 및 객체의 속성에 val 의 키워드가 붙어 있다면, 초기화시 대입된 값은 절대 변하지 않는다.

 

2. 람다 함수 

- 다른 함수에 인자 또는 반환 값으로 넘길 수 있는 작은 코드 조각

- 공통된 코드 구조를 쉽게 함수로 뽑아낼 수 있다.

val sum: (a:Int, b: Int) -> Int  = {a, b -> a + b}

val div: (a:Int, b: Int) -> Int  = {a, b -> a / b}

fun calc(num1: Int, num2: Int, operation:(a:Int, b: Int) -> Int) = operation(num1, num2)


fun main() {
    val ret1 = calc(10,2, sum)
    val ret2 = calc(10,2, div)
}

 

3. 익명함수

- 이름 없는 함수

- 다른 함수에 인자 또는 반환 값으로 넘길 수 있는 작은 코드 조각

val anonymousFunction = fun (x: Int, y: Int): Int = x + y

 

4. 기본적으로 제공되는 순수함수

 

예를 들어 사람들로 구성된 리스트에서 남성이면서 가장 연장자를 찾고싶다면,

person List를 filter 와 maxByOrNull 같은 기본적으로 제공되는 순수함수를 체이닝하여 쉽게 찾을수 있다.

data class Person(
    val sex: String,
    val age: Int
)

fun main() {

    val personList = listOf(
        Person(sex = "남성", age = 14),
        Person(sex = "남성", age = 21),
        Person(sex = "여성", age = 16),
        Person(sex = "남성", age = 34),
        Person(sex = "여성", age = 10),
        Person(sex = "여성", age = 18),
        Person(sex = "남성", age = 28),
        Person(sex = "여성", age = 60),
    )
    val target = personList.filter { it.sex == "남성" }.maxByOrNull { it.age }
    println(target)
}