Kotlin in Action의 1장은 코틀린에 대해 간단히 소개하는 내용이다. 따라서 1장을 따로 정리하다기 보다는 코틀린이라는 언어에 대해 궁금하거나 알고 싶은 점을 Deep Dive하는 시간을 스터디원들과 함께 가지기로 했다. 나는 객체 지향 프로그래밍에 대해서는 많이 들어봤고 잘 알고있지만, 함수형 프로그래밍이라는 말은 처음 들어본 것 같아 흥미가 생겨 해당 주제를 Deep Dive하기로 결정했다.
#함수형 프로그래밍이란?
함수형 프로그래밍에 대해 다루기 전에, 잠시 다양한 프로그래밍 방식을 배워보자. 근래의 프로그래밍 방식은 다음과 같이 분류할 수 있다.
명령형 프로그래밍: 프로그램의 상태와 상태를 변경하는 구문의 관점에서 연산을 설명하는 방식. 즉, What이 아니라 How에 집중한다.
- 절차지향 프로그래밍: 문제를 순차적으로 처리하며 프로그램을 만드는 방식 (C, C++)
- 객체지향 프로그래밍: 객체들 간의 상호작용을 통해 프로그램을 만드는 방식 (C++, Java, C#)
선언형 프로그래밍: 프로그램이 무엇과 같은 지를 설명하는 방식. 즉, How가 아니라 What에 집중한다.
- 함수형 프로그래밍: 수학적 함수의 계산을 통해 자료를 처리하고 함수를 조합하여 프로그램을 만드는 방식 (코틀린)
함수형 프로그래밍이란 이렇게 모든 것을 순수 함수로 나누어 문제를 해결하는 기법이다. 그렇다면 함수로 나누어 문제를 해결하는 것이 왜 중요한 프로그래밍 패러다임이 되었을까? 기존의 프로그래밍 패러다임을 기반으로 개발했던 개발자들은 소프트웨어의 크기가 커짐에 따라 복잡하게 엉킨 스파게티 코드를 유지 보수하는 것이 힘들다는 것을 깨닫게 되었다. 함수형 프로그래밍은 이를 해결하기 위해 등장한 패러다임으로, 함수 단위의 코드 재사용이 굉장히 용이하며, 불변성을 지향하기 때문에 프로그램의 동작을 예측하기 쉬워진다는 장점으로 인해 유명해졌다. 전반적인 소개를 마쳤으니, 이제 함수형 프로그래밍의 자세한 특징에 대해 알아보자. 위에서 언급한 ‘순수 함수’, ‘불변성’ 등 뿐만 아니라 다양한 특징들에 대해 살펴보도록 하겠다.
#순수 함수(Pure function)
순수 함수란 무엇일까? 우선 함수는 동일한 입력에는 항상 같은 값을 반환하는 것을 뜻한다. 이중 함수의 실행이 프로그램의 실행에 영향을 미치지 않으며, 함수 내부에서 인자의 값을 변경하거나 프로그램 상태를 변경하는 부수 효과(Side Effect)가 없는 것을 순수 함수라고 말한다. 다음과 같은 예시를 보도록 하자.
fun add(a: Int, b; Int): = a + b
이때 위의 add는 순수 함수가 된다. 언제 어느 상황에서 실행해도 add(1+2)는 항상 3을 리턴하고, 외부 상태의 변경이 이루어지지 않기 때문이다. 즉, 메모리 어딘가에 저장되어 있던 1과 2라는 값은 변경되지 않은 상태로 그대로 남아있다. add 함수를 실행해도 메모리에 저장되어 있던 1과 2라는 값은 변경되지 않는다(Call By Value). 이처럼, C언어와 달리 Kotlin에서는 포인터 주소를 활용하지 않기 때문에 주소 참조(Call By Reference)가 아닌 값을 복사하여 전달하는 값에 의한 호출(Call By Value)이 일반적으로 일어난다.
#참고: 부수효과(Side Effect)
여기서 부수효과(Side Effect)란 외부의 상태를 변경하는 것 또는 함수로 들어온 인자의 상태를 직접 변경하는 것을 의미한다. 구체적으로는 다음과 같은 변화를 의미한다.
- 변수의 값 변경
- 자료 구조 수정
- 객체의 필드 값 새로 할당
#비상태, 불변성 (Stateless, Immutability)
함수형 프로그래밍에서의 데이터는 변하지 않는 불변성을 유지해야 한다. 순수 함수라는 개념과 불변성이라는 특징은 항상 같이 갈 수 밖에 없다. 수학의 세계에서 생각해보면, 수학에서는 어떤 상태라는 것이 존재하지 않는다(어떤 한 상태에 놓여있다는 말은 다른 상태에 놓일 수 있다는 개념을 내포하므로). 예를 들어 (2, 3) 좌표에 있는 점은 x=2, y=3이라는 불변성을 가지고 있다. 이 점은 x=10, y=10 이라는 또 다른 ‘상태’를 가질 수 있는 것이 아니다. 단지 x=2, y=3이라는 불변성에 고정되어 있는 것이다. 따라서 수학의 함수 모델을 프로그래밍 세계에 구현한 함수형 프로그래밍에서도 어떠한 상태를 가지거나 상태를 변경해서는 안 되며, 이러한 특징을 곧 불변성이라고 말한다.
그렇다면 상태의 변경이란 무엇일까? 이는 정확히 말하면 메모리에 저장된 값을 변경하는 모든 행위를 뜻한다. 자세한 예시를 들어보자면 변수가 재할당되는 것, 함수의 인자가 변경되는 것 등을 뜻한다고 말할 수 있다.
따라서 함수형 프로그래밍에서 데이터의 변경이 필요한 경우에는 원본 데이터 구조를 변경하지 않고 그 데이터의 복사본을 만들어서 그 일부를 변경하고, 변경한 복사본을 사용해 작업을 진행해야 한다.
#일급 객체(First-class citizens)
함수형 언어에서는 모든 것이 객체가 된다. 자바에서는 클래스만이 객체가 되고 함수는 클래스 안의 매소드로 구현되지만, 함수형 언어인 코틀린에서는 함수 또한 객체로 관리할 수 있다. 구체적으로 말하자면, 코틀린에서는 함수가 일급 객체(First-class citizens)로 사용된다. 이때 ‘일급 객체’란 무슨 뜻일까? 다음의 3가지 조건을 충족할 때 일급 객체라고 할 수 있다.
- 변수에 할당 가능해야 한다.
- 객체의 인자로 넘겨질 수 있어야 한다.
- 객체의 리턴값으로 리턴될 수 있어야 한다.
코틀린에서 함수는 변수에 할당할 수 있고, 다른 함수의 인자로 전달될 수 있으며, 다른 함수의 결과 값으로 반환될 수 있다. 다음은 각각의 특징을 모두 보여주는 예시이다.
object Main {
@JvmStatic
fun main(args: Array<String>) {
val a = test //변수에 함수 할당 가능
factor_function(input_fun)
return_function()
}
//함수 타입을 인자로 받는 함수(람다(Lambda)식을 통해 선언)
fun factor_function(f: () -> Unit) {
f.invoke()
}
//함수 타입을 리턴하는 함수(람다(Lambda)식을 통해 선언)
fun return_function(): () -> Unit {
return { println("함수 리턴 가능") }
}
val test: () -> Unit = { println("함수 할당 가능") } //변수에 함수 할당 가능
val input_fun: () -> Unit = { println("함수 인자 가능") }
}
반면 자바의 경우에는 위의 세 가지가 전부 다 불가능하므로, 자바의 함수는 일급 객체가 아니다.
#고차 함수(Higher-order function)
코틀린에서 함수가 일급 객체이기 때문에 고차 함수(Higher-order function) 표현 또한 가능해지는데, 여기서 고차 함수란 인자로 전달된 함수를 이용하여 만든 새로운 함수를 의미한다. 따라서, 고차 함수는 람다/함수 참조를 인자로 넘길 수 있거나 람다/함수 참조를 반환하는 함수라고 할 수 있다.
이러한 고차 함수를 사용할 경우 함수의 재사용성을 높여주는 역할을 한다.
그런데 고차 함수를 사용하면 왜 재사용성이 높아지는 것일까? 절차지향적 언어인 C++ 코드를 보면서 그 이유를 알아보도록 하자.
#include <iostream>
#include <vector>
using namespace std;
int idx[26];
int check(int n, int m, char alpha, vector<char> &vec){
/*...*/
}
int main() {
/*...*/
answer = check(n, m, alpha, vec);
}
위의 코드를 보면 check 함수의 매개 변수가 상당히 많은 것을 알 수 있다. 이처럼 절차지향 언어에서는 한 함수에서 다른 함수를 호출 할 때 어떤 값을 매개 변수로 넘겨줄 지, 각 매개 변수를 어떤 타입으로 할 지를 정확하게 설계해야 한다.
그런데 이렇게 만든 check 함수를 다른 알고리즘에서 재활용할 수 있을까? 정답은 ‘쉽지 않다’이다. 만약 다른 알고리즘에서 재활용하고자 한다면 두 개의 정수, 하나의 문자, 하나의 백터를 넘겨주는 매개변수 형식을 정확하게 맞춰 주거나, 아니면 함수의 코드를 수정해야 한다.
코틀린의 고차 함수 컨셉은 바로 이러한 문제를 해결하기 위해 등장했다고 생각하면 된다. 함수 자체를 인자로 넘겨준다면 각 매개 변수를 철저히 검사할 필요도 없으며, 다른 알고리즘에 재활용하기도 굉장히 간편하다. 따라서 최소한의 변경으로 여러 곳에서 재사용될 수 있는 코드(보일러플레이트 코드라고 부른다)를 만들 수 있기 때문에 SW 개발을 빠르게 하는데 많은 도움이 된다.
#마무리하며
내가 궁금했고 알고 싶었던 한 주제를 깊이 있게 파고드는 과정이 재미있었으며, 특히 람다(Lambda)라는 개념이 굉장히 흥미로웠던 것 같다. 최근에는 AWS를 공부하고 있는데, AWS의 서버리스 코드 실행 서비스인 Lambda와 어떤 관련이 있는지도 궁금해진다. 해당 서비스를 사용해보면서 함수형 프로그래밍 개념과 관련 있는지를 추가적으로 더 공부해 보고 싶다.
'📱Kotlin > [GDSC] Kotlin in Action 스터디' 카테고리의 다른 글
[코틀린 인 액션] Kotlin in Action 3장 보충읽기 (0) | 2023.05.26 |
---|---|
[코틀린 인 액션] Kotlin in Action 2장 보충읽기 (0) | 2023.05.26 |
[코틀린 인 액션] Kotlin in Action 11장 정리 (0) | 2023.01.30 |
[코틀린 인 액션] Kotlin in Action 10장 정리 (0) | 2023.01.29 |
[코틀린 인 액션] Kotlin in Action 9장 정리 (0) | 2023.01.26 |