함수형 프로그래밍 선택 이유
함수형 프로그래밍은 대부분을 순수 함수로 나누어 문제를 해결하는 기법으로 작은 문제를 해결하기 위한 함수를 작성하여 가독성을 높이고 유지보수를 용이하게 한다.
함수형 프로그래밍 언어를 사용하면 코드를 간결하게 작성할 수 있어 개발 시간을 단축할 수 있고, 함수형 프로그래밍 언어가 부작용(Side Effect)를 허용하지 않는 순수 함수(Pure Function)를 지향하여 동시에 여러 스레드에서 문제 없이 동작하는 프로그램을 쉽게 작성할 수 있다.
함수형 프로그래밍 언어의 이해
유명한 책인 클린 코드(Clean Code)의 저자 Robert C.Martin은 함수형 프로그래밍을 대입문이 없는 프로그래밍이라고 정의했다.
* 대입문 : int a = 10;
함수형 프로그래밍은 대입문을 사용하지 않으며 작은 문제를 해결하기 위한 함수를 작성한다.
함수형 프로그래밍에서 함수는 1급 객체(First class citizen)으로 관리된다.
1급 개체(First class citizen)?
3가지 조건을 충족한다.
1) 변수나 데이터에 할당할 수 있어야 한다.
2) 객체의 인자로 넘길 수 있어야 한다.
3) 객체의 리턴값으로 리턴할 수 있어야 한다.
함수형 프로그래밍 언어의 특징
1. 불변성(Immutability)
함수형 프로그래밍 언어는 불변성을 지향하는 프로그래밍 언어 패러다임으로 변경 가능한 상태를 최대한 제거하려고 노력한 프로그래밍 언어이다.
순수 함수를 지향하는 프로그래밍 언어라고 설명하기도 한다.
* 순수 함수 : 내부 상태를 갖지 않아 같은 입력에 대해서는 항상 같은 출력이 보장되는 함수로 부작용(side effect)이 없는 함수이다.
함수형 프로그래밍 언어가 불변성을 추구함에 따라 제공되는 여러가지 장점은 아래와 같다.
1.1 프로그램의 검증이 쉽다.
프로그램을 구성하는 모듈들이 오로지 입력 값에만 영향을 받기 때문에 테스트 코드를 작성하기 쉽고, 프로그래머가 예측하지 못하는 시점에 변경될 수 있는 내부 상태가 없기 때문에 프로그램이 예측 가능해진다.
1.2 최적화가 가능하다.
불변성은 다양한 최적화를 가능하게 한다. 이전에 계산한 함수의 값을 캐싱(caching)해 두었다가 필요할때 다시 사용하는 메모이제이션(memoization)은 함수의 불변성이 보장되지 않으면 불가능하다.
예제) make-auth-config- 라는 함수는 username, password, server-address라는 세 인수를 입력받아 AuthConfig 클래스의 인스턴스를 반환한다.
아래는 순수 함수로 내부 상태를 갖지 않으며 함수의 결과가 오로지 함수의 입력 값에만 영향을 받는다. 따라서 이 함수는 메모이제이션이 가능하다(마지막 줄)
이에 따라 함수가 1회라도 호출된 이후 동일한 값을 가진 인수 조합이 입력되면 함수를 실행하지 않고 이전에 저장했던 값을 반환한다.
(defn make-auth-config-
[username password server-address]
(-> (AuthConfig/builder)
(.username username)
(.password password)
(.serverAddress server-address)
(.build)))
(def make-auth-config
(memoize make-auth-config-))
1.3 동시성 프로그램을 작성하기 쉽다.
멀티프로세서 환경에서 동작하는 동시성 프로그램을 작성할 때 함수형 프로그래밍 언어가 유용하다.
동시성 프로그램을 작성하기 힘든 이유는 여러 쓰레드들이 프로그램 상태를 공유하기 때문인데, 함수형 프로그래밍 언어에서는 변경 가능한 상태를 배제하기 때문에 잠금(Lock)이나 동기화(Synchronize)같은 쓰레드 관련 문제를 신경쓰지 않아도 된다.
2. First-class, higher-order functions
함수형 프로그래밍에서 함수는 일등 시민(First-class citizen)이다.
변수에 할당할 수 있고, 다른 함수의 인자로 전달할 수 있으며, 다른 함수의 결과 값으로 반환될 수 있는 함수라는 의미이다.
함수를 하나의 값처럼 다룰 수 있기 때문에 객채지향 패러다임에서 클래스를 재사용하는 것처럼 함수를 재사용할 수 있고, 핵심 코드를 boilerplate 없이 간단하게 표현할 수 있다.
일등 시민으로서의 함수는 고차 함수(Higher-order function)의 표현을 가능하게 한다. 고차 함수란 인수로 전달된 함수를 이용하여 만든 새로운 함수를 의미한다. 고차 함수는 부분 적용(partial application)이나 커링(currying)을 가능하게 만들어 프로그래머가 프로그램을 간결하게 작성할 수 있도록 도와준다.
참고
* boilerplate : 별 수정 없이 반복적으로 사용되는 코드
* Currying : 여러 개의 매개변수를 가진 함수를 한개의 매개변수를 가진 여러 함수의 연결로 나타내는 방법이다.
* Partial Application : 함수를 반환하는 함수이지만 함수가 받는 인자는 하나일 필요는 없다. 여러 개를 받을 수도 있다.
// Arrow function
const add = x => y => z => x + y + z;
// Normal function
function add(x) {
return function(y) {
return function(z) {
return x + y + z;
}
}
}
const addFive = add(5);
const addFourAgain = addFive(4);
const nineTeen = addFourAgain(10);
nineTeen === 19 // true
참고 : 함수형 프로그래밍 curry와 partial application
예제)
(defn listing-all-containers [docker]
(let [lists (.listContainers docker (into-array [(com.spotify.docker.client.DockerClient$ListContainersParam/allContainers)]))]
(map (fn [list]
[:id (.id list)
:names (.names list)
:image (.image list)
:imageId (.imageId list)
:command (.command list)
:created (.created list)
:status (.status list)
:ports (.ports list)
:labels (.labels list)
:sizeRw (.sizeRw list)
:sizeRootFs (.sizeRootFs list)]) lists)))
listing-all-containers 함수 : 모든 도커 컨테이너들의 목록을 나열할 때 사용되는 함수
함수의 흐름은 인자로 받은 docker라는 인스턴스의 메소드인 listContainers를 호출하여 컨테이너 리스트를 얻고, 이 리스트의 각 원소에 람다 함수를 적용하여 새로운 컬렉션을 만드는 것이다.
위의 함수를 단순하게 표현하면 아래와 같다.
(map
(fn ...)
lists)
map 함수는 두 개의 인자를 입력받는다.
첫 번째 인자는 람다 함수로 lists 원소 각각에 적용할 코드 블록이고, 두 번째 인자는 람다 함수를 적용할 컬렉션(collection)이다.
이처럼 함수형 프로그래밍 언어에서는 함수를 다른 함수의 인자로 전달할 수 있다. -> 이는 함수가 first-class citizen이기 때문이다.
또한, 위에서 map 함수는 고차 함수의 한 예이다. map 함수와 람다 함수를 조합하여 새로운 함수를 만든 것이다.
3. Lazy evaluation
함수형 프로그래밍 언어는 지연 연산(lazy evaluation)을 지원한다. 지연 연산이란 어떤 값이 실제로 쓰이기 전까지 그 값의 게산을 최대한 미루는 것이다.
값을 미리 계산하여 저장하지 않기 때문에 공간을 절약할 수 있고, 값이 꼭 필요할 때만 계산하기 때문에 프로그램의 성능에도 긍정적이다.
지연 연산은 주로 메모이제이션과 함께 사용된다. 값이 필요할 때 계산을 하고, 다음에 해당 값이 필요하면 계산하지 않고 캐싱한 값을 재사용한다. 예로는 피보나치 수열이 있다.
Scala
스칼라는 명령형 프로그래밍 세계에서 적극 활용되는 객체 지향 프로그래밍(OOP) 방식과 함수형 프로그래밍(Functional Programming, FP) 언어의 기능을 잘 통합한 언어이다.
- 파이썬과 같이 아주 간결한 문법
- 객체지향과 함수형 프로그래밍 모두 가능
- 자바와 호환되며 JVM 위에서 실행되기 때문에 좋은 성능
- 정적 타입을 지향(타입 추론해서 정적 타입 체크)
- REPL Shell을 활용하여 Scripting
References
'프로그래밍 언어' 카테고리의 다른 글
ArrayList와 LinkedList의 차이 / Stringbuilder (0) | 2022.08.14 |
---|---|
자바 HashMap의 구조 / HashTable / LinkedHashMap (0) | 2022.08.14 |
자바 동기화 | Synchronized(Monitor), Atomic Type (1) | 2022.08.14 |
[Java] Comparable과 Comparator의 차이 (0) | 2022.08.10 |
[Java] JVM의 구조와 메모리 영역 (0) | 2022.07.28 |
댓글