역시 이런건.. 얄팍한 코딩사전
(추가로, 예제로 보려면 여기)
흔히 볼 수 있는 예로, 고차함수와 재귀함수가 함수형 프로그래밍 컨셉이다
객체지향 프로그래밍이 절차지향 프로그래밍은 서로 상반되는 것이 아니라
공존하기도 하고 이들을 포함하기도 하는 개념이다
이처럼 함수형 프로그래밍도 객체지향 프로그래밍과 아주 대립하거나 하는 관계는 아니다
(예로, 스칼라는 함수형인 동시에 객체지향 언어)
단, 함수형 프로그래밍은 프로그래머들이 일하는 새로운 패러다임을 제시한다
초콜릿을 만드는 공장이 있다고 생각해보자
객체지향으로 만든다면
각 구성원들이 맡은 역할에 따라 서로 협력한다
함수형으로 만든다면
협업같은 건 모르겠고 단지 어떤 인풋에 대해 그에 대응되는 아웃풋을 만들어낸다. 이런 친구들의 집합
의외로 이런 공장들이 안정적이기도 하다
1. 인풋과 아웃풋이 있다
이 친구들에게 일을 시키는 방법은, 그들의 역할대로 중간재료를 넘겨주는 것
그러면 각자 맡은 작업을 해서 결과물을 반환한다
2. 외부 환경으로부터 철저히 독립적이다
파라미터로 전달된 것 외에는 변경하지도, 참조하지 않는다
오로지 자신들에게 주어진 것들로만 정해진 작업을 한다
3. 같은 인풋에 있어서 언제나 같은 아웃풋을 만들어낸다
외부 영향을 받지 않기 때문
이런걸 '순수 함수'라고 한다
비 함수형 공장에서도 실수만 없다면 괜찮겠지만
여러 함수들이 손을 대는 외부 변수에 오류가 생기면
공장 프로세스에 차질이 생긴다
함수형이 주목받게 된 이유는 이런 '부작용' 문제로부터 상대적으로 자유롭기 때문
정의
어떤 함수의 동작에 의해 프로그램 내 특정 상태가 변경되는 상황
(변경된 상태는 관련된 다른 동작들에게 영향을 미치게 된다)
타이밍 이슈
멀티 쓰레드 환경에서 shared 메모리에서 '타이밍' 문제가 발생할 수 있다
이를 위한 몇가지 방법이 제안되고 있는데
이런 수동적인 방법은 구현이 어렵고 주의가 많이 필요하다
(특히, 시간차에 의한 오류 가능성을 전부 감안해야 하기 때문)
함수형 프로그래밍은 부작용을 원천 배제한다
함수형 프로그래밍은 함수의 동작에 의한 변수의 부수적인 값 변경을
원천 배제함으로써 이런 종류의 오류를 방지한다
즉, 문제의 소지가 있는 일을 하지 않는 코딩 방식
외부 변수를 사용하더라도 그 본체에 접근해서 변경하는게 아니라 인자로 넣어서 사본으로 복사해가서 작업을 하기 때문에 어떤 작업을 하든 부작용이 일어나지 않는다
물론 모든걸 함수형 프로그래밍으로 만드는 건 어렵다
하지만 적어도 일정 단위의 작업에 있어서는
부작용없이 안정적이고 예측가능한 프로그램을 짜는게 함수형 프로그래밍
요즘 멀티프로세싱/멀티쓰레딩 환경이 기본이라 함수형 프로그래밍이 더더욱 주목받고 있다
선언형이란?
아마 기존에 하던 코딩 방식은 '명령형'일 것이다
(= 너는 이걸 하고 너는 저걸 누구랑 어떻게 해서 이러이러한 결과를 산출해내라)
반면 선언형은 '이거는 저거다'
아까 말했듯 순수함수들은 인풋만 같으면 절대 다른 요인에 의한 변수가 없다
그러므로 이렇게 말할 수 있다
'A를 넣으면 B가 나온다'
이게 선언형이다
그래서 함수를 마치 변수처럼 생각할 수 있는 것
함수를 '값'으로 보고 프로그래밍 하는 것
순수함수이므로 인풋만 잘 넣어주면 절대 예측가능한 숫자 값을 내게 될 것이므로
이 함수를 '값'으로 바라볼 수 있는 것이다
근데 함수를 값처럼 바라봐서 뭘 하려는걸까?
함수를 값으로 본다면, 함수를 다른 함수의 인풋으로 넣는 것도 가능할 것이다
이처럼 인풋으로 다른 함수를 받아 아웃풋을 내보내는 함수를 고계함수라 한다
혹은 아웃풋을 다른 함수로 내보내는 함수도 고계함수라 한다
이런 고계함수를 활용하여 다채로운 프로그래밍이 가능하다
이건 생소한 개념인데.. 함수형 언어인 Scala에 있는 예시로 알아보자
아래에서 보통의 함수는 add
처럼 두 개의 인풋을 받아 하나의 아웃풋을 내는 형태로 만들어진다
하지만, add_curry
처럼 선언하면 var add2
에 할당한 것처럼
'숫자 하나를 2에 더하는 함수'를 만들 수도 있다
이처럼 여러 인풋을 받는 함수에 일부 인풋만 넣어서
나머지 인풋을 받는 다른 함수를 만들어낼 수 있는 함수형 프로그래밍 기법을 '커링'
이라고 한다
모든 인풋이 준비되지 않았을 때 부분적용된 상태의 함수를 만들어내서 마련해두거나
다른 함수의 인풋으로 넘겨주는 등 로직을 짜는 방식이 더 풍성해지고 코드도 줄일 수 있게 된다
함수형의 핵심은 아니지만 함수형에서 많이 사용되는 함수 컴비네이터
배열이나 리스트같은 컬렉션에서 자주 사용된다
마찬가지로 Scala를 활용해보자
특정 변수의 상태 변화, 즉 부작용을 필요로 하는 for문을 돌릴 때보다 훨씬 간결하고 가독성이 좋다
(아래 그림에서 for문이 filtered
와 result
를 건드리는 것이 부작용이다)
이전에는 '이것들을 어떻게 구현해낼까'하고 하나하나 설계했지만
이제는 이런 함수형 프로그래밍 기능들을 활용해 '어떤 것들을 가져다 이어붙이면 이게 구현될까'
가 되는 것
비슷하게, 명령형과 선언형을 비교할 때 자주 쓰이는 표현이
명령형은 how를 정의하고 선언형은 what을 정의한다