최신 언어들은 대부분 다 반영하는 프로그래밍 패러다임이다. 사실 이렇다고 말하지만 swift만 제대로 써봤기 때문에... swift가 그렇다. 새로운 시각과 관점이 필요하기에 명령형에 익숙한 사람들에게 전환이 쉽지 않다고 한다. 그래도 앞으로 대세이기 때문에 꼭 알아두고 사용해보자.
RxSwift를 쓰지만 실제로 함수형 프로그램을 설계해보지 않았기 때문에 좋은 점은 알지만 그 안에 어떤 철학과 시각이 담겨있는지 깊이는 모른다. 점점 공부를 해가기로 하고 이번 포스팅에서는 함수형 프로그래밍의 간단히 소개하고 이해하는 시간을 가져보자.
함수형을 이해하기 위해 함수형을 포함하는 선언형 프로그래밍에 대해서 간단히 알아보자.
명령형 프로그래밍은 어떻게 할지 표현하고, 선언형 프로그래밍은 무엇을 할 건지 표현한다.
아래는 선언형을 이해에 도움이 되는 여러 정의다.
func double(arr: [Int]) -> [Int] {
var results = [Int]()
for i in 0...<arr.count {
results.appent(arr[i] * 2)
}
return results
}
double
이라는 함수가 원하는 기능을 구현하는 방법과 그 단계를 명시적으로 표현하고 있다.double
은 상태의 일부를 변경시키고 있다. results라는 변수를 만들어 계속해서 수정하며 단계를 진행한다.var results = arr.map { $0*2 }
무엇을 하고 싶은지 기술
했다. 위 명령형 코드에서는 각 요소를 더블링하기 위해서 상태를 변경하는 구문을 이용해 목적을 달성했다. 하지만 이 코드는 map
이라는 고차함수를 사용해서 '각각 요소를 더블링 해주세요'라고 선언, 최종 목적을 기술하고 있다. map이 어떻게 구현되는지 모른다. 신경쓸 이유도 없다. 그 사용 목적만을 알고 있을 뿐이다.상태도 변경하지 않는다.
모든 변경 사항은 map 안에 추상화되어 있다.직관적
이고 읽기 쉽다.context-independent
선언형 코드는 '어떤 목적을 성취하기 위해서 사용되는 단계 중에 하나'가 아니라 '궁극적인 목적 자체'와 관련이 있기 때문에 그 목적이 동일하다면 다른 프로그램에 적합하게 사용할 수 있다. 명령한 코드는 현재 컨텍스트에 의존적이기 때문에 이 작업을 수행하기 어렵다.함수형 프로그래밍은 프로그램이 상태의 변화 없이 데이터 처리를 수학적 함수 계산으로 취급하고자 하는 패러다임이다.
도움이 되는 다른 정의
기존 명령형 프로그래밍(절차적이나 객체 지향이나)에서는 프로그램에서 값이나 상태의 변화를 중요하게 여기지만 함수형 프로그래밍 패러다임은 함수 자체의 응용을 중요하게 여긴다. 기존 객체지향 프로그래밍에서는 객체들이 가지고 있는 속성의 변화와 그런 변화를 주고 받는 객체들의 행동으로 인해 프로그래밍이 진행되었다면, 함수형 프로그래밍에서는 그런 상태의 변화없이 '순수함수들의 조합'으로 프로그래밍 한다.
그렇기 때문에 함수형 프로그래밍을 지원하는 swift에서는 다양한 종류의 함수를 호출, 전달, 반환하는 등의 동작만으로도 프로그램을 구현할 수 있다.
이런 의미에서 지난 절차적 프로그래밍에서도 언급했던 것처럼, 함수형 프로그래밍의 함수
와 명령형 프로그래밍에서의 함수
는 의미가 조금 다르다. 명령형 함수를 이용하는 함수는 실행 시 함수가 전달받은 인자 외에도 포인터, 레퍼런스 값 등의 프로퍼티 값 또는 메모리 참조 값 등이 변경될 수 있고 함수 내부 처리에도 영향을 줄 수 있다. 하지만 함수형 프로그래밍의 함수는 '순수함수', 즉 순수하게 함수에 전달된 인자 값만 결과에 영향을 주기 때문에 상태 값을 가지지 않는다. 함수는 상호 간섭없이 배타적으로 실행된다. 그래야 함수마다 다른 값을 리턴하거나 side effect가 일어나거나 다른 스레드에 영향을 받거나 하는 일들이 일어나지 않기 때문이다. 그래서 함수형 프로그래밍은 병렬처리에 부작용이 없고 대규모 병렬처리에 큰 장점이 있다.
shallow copy vs deep copy
Input이 변경되지 않도록 순수함수에서는 input을 copy해서 사용하는데,
call by value가 적용되는 원시 타입(primative type)이면 문제가 없지만,
call by reference가 적용되는 참조 타입(reference type)이면 얘기가 좀 달라진다.
객체처럼 참조타입 데이터는 해당 값이 아닌 '값이 저장된 메모리의 주소'가 저장된다.
Shallow copy(얕은 복사)는 참조형 Type 데이터가 저장한 '메모리 주소 값'만 복사한 것을 의미한다.
반대로 Deep copy는 새로운 메모리 공간을 확보해 완전히 복사하는 것을 의미한다.
함수의 순수성, 전달인자의 불변성을 유지하면 다음과 같은 참조의 투명성을 기대할 수 있다.
자기 충족적이다(self-contained). 함수 외부에 의존하는 코드가 없고, 함수 사용자 입장에서는 유효한 매개변수만 전달하면 된다.
결정론적이다 (deterministic). 동일한 매개변수에 대해서는 항상 동일한 결과가 나온다.
예외 (Exception) 를 던지지 않는다. out of memory error 혹은 stack overflow error 는 발생할 수 있지만, 이러한 에러들은 버그로 취급되며, 함수의 사용자가 다룰 수 있는 것은 아니다.
다른 코드가 예기치 않게 실패하는 조건을 만들지 않는다. 예를 들어, 참조 투명성을 가진 함수는 매개 변수의 값을 변경하거나 함수 외부의 데이터를 변경하지 않는다.
데이터베이스, 파일 시스템, 네트워크 등의 외부 기기로 인해 동작이 멈추지 (hang) 않는다.
함수형 프로그래밍에서 가장 피해야 하는 지점이다.
고차원 함수는 함수를 인자로 받고 또 결과로 반환하는 함수를 이야기한다.
일급객체
여야 한다.일급객체?
- 전달인자(argument)로 전달할 수 있다.
- 동적 프로퍼티 할당이 가능하다. 컴파일 단계가 아니라 런타임시에도 할당이 가능하다는 뜻이다.
- 변수나 데이터 구조(자료구조) 안에 담을 수 있다
- 반환 값으로 사용할 수 있다.
- 할당할 때 사용된 이름과 관계없이 고유한 객체로 구별할 수 있다.
데코레이터 사용법
https://injun379.tistory.com/78
순수 함수를 조합하다보면, 인자의 수가 서로 맞지 않는 순수함수를 엮어야 하는 상황이 있을 수 있다.
이 상황을 Arity(the number of arguments taken by a function) Mismatch라 한다.
Arity Mismatch는 왜 일어날까?
순수함수 중에는 2개 이상의 인자를 필요로 하는 함수가 있을 수 있음
이 순수함수는 2개 이상의 순수함수에서 실행된 출력값을 받아와야 함
하지만 함수는 무조건 1개의 return 값만 나옴
이렇게 인자의 수가 서로 맞지 않는 상황을 Arity Mismatch라 한다.
Partial Application & Curring으로 Arity Mismatch을 해결
Partial Application : 인자를 부분적으로 먼저 엮어준다.
Curring : 인자를 하나씩만 받는 함수의 체인으로 만드는 방법이다.
// Partial Application
func add(_ a: Int, _ b: Int) -> (Int) -> Int {
return { c in
return a + b + c
}
}
var addTenFive = add(10, 5)//10과 5를 미리 합쳐준다!
print(addTenFive(5)) // 미리 10과 5가 합쳐줬기 때문에 20이 나온다.
//Curring
func before(_ a: String) -> ((String)->String) {
return { b in
return a + b;
}
}
var word = before("이렇게");
var finalWord = word("붙어요!");
print(finalWord); //이렇게붙어요!