오늘 오카믈에서 mutable 하게 데이터타입을 정의하는 부분을 보면서, 불변에 대한 이해도가 확 올라갔기에 그것을 공유해보려고 함.


1. 변수의 사용, 그 자체는 부작용이다.


불변을 이야기하려면 변수를 이야기해야한다.

후술하겠지만, 변수는 그 존재 자체가 부작용이다.

변수라는 것은 일종의 데이터를 위한 공간이다.


함수형은 부작용을 사용하지 않는 것이 아니다. 통제할 뿐이다.

즉, 함수형 또한 공간, 즉 변수를 사용한다.

단, 그것은 불변이 아니라 가변이다.

엥? 함수형은 불변이라면서요?? -> 함수형은 공간을 배정하지 않고도 연산을 하는 방법을 가지고 있다.

함수형의 공간, 즉 변수는 가변이다. 단, 변수를 선언하지 않고도 연산할 뿐이다.


공간을 배정한다는 것은 필연적으로 부작용을 포함한다.

대부분의 경우 공간의 데이터를 읽어오고, 공간의 데이터에 따라서 연산이 바뀌기 때문이다.

이는 참조투명성을 깨뜨리는 행위이다.


IO라는 행위를 예시로 들면, 컴퓨터 바깥의 입력을 받아오기 때문에 결과값이 뭐가 나올지 예상할수가 없다.

마찬가지로, 공간에 데이터가 무엇이 들어있을지를 읽어오는 함수가 있다면, 그 결과는 예상할 수가 없다.

변수의 존재 자체가 부작용을 의미한다는 것이다.

변수를 선언하고 바꾸지 않는다?? -> 그것또한 부작용이다. 변수의 선언 자체가 부작용이기 때문.

공간에 데이터를 쓰는것 또한 부작용으로 간주된다.


즉, "함수형의 성질로 변수의 불변을 제시하는 것"은, 철저히 객체지향적으로 사고하고, 피상적으로 상황을 이해하는 것이다.




상태는 그 자체가 부작용이다. C언어가 변수를 사용하는것 그 자체가 부작용인 것이다.


함수형 언어는 부작용을 철저히 통제하면서 코딩한다. 부작용이 없는게 아니다.

함수형 언어도 부작용을 사용한다. 다만 철저하게 통제하며, 부작용이 없는 쪽의 연산을 극단적으로 발달시켜놓았을 뿐이다.

부작용을 철저히 통제하는 만큼, 통제 없이 사용하는 pure 한 부분은 함수형언어의 "철학"이라고 할 수 있다.

여러분들이 그렇게 알고싶어하는 "그래서 함수형이란게 뭔데?" 말이다.


2. Ref = 함수형 언어도 공간을 사용한다.


자 앞서, 변수의 존재 자체가 부작용이라고 말했는데,

함수형 언어도 변수를 사용한다. 또, 변수가 반드시 필요한 경우가 있다.

아무리 함수형으로 쇼를 써도, 변수 없이는 한계가 있다.


그러면, 이제까지 얘기한걸 종합해보자.


1. 변수는 부작용이고

2. 함수형도 부작용을 사용한다.


뭐야 그러면 객체지향과 뭐가 다른데???


함수형 언어는 부작용을 모나드 안에서 철저하게 통제한다.


함수형 언어를 강력하게 만드는것은, 부작용이 없는 순수의 영역에서 그 진가가 발휘된다.

부작용을 안쓰고도 이만큼 해낸다고?? 가 선행되어야

부작용을 통제하는 의미가 있다. 부작용의 영역을 줄이지 않고, 단지 통제만 한다는 것은 그냥 바보짓 아니겠는가?

그럼 이제 함수형이 변수를 안쓰고도 연산을 해내는 법을 알아보자.



3. 함수형이 순수하게 연산하는 방법


변수를 배정하지 않고 연산하는 방법은, 모든 연산의 입력을 함수의 인수로 전달하고,

모든 연산의 결과를 함수의 리턴값으로 반환하는 것이다.


즉, 상태를 컴퓨터 공간에 저장하는것이 아니라, 흐르는 성질로 만드는 것이다.

데이터의 흐름을 정의하고, 최초 입력값을 명확한 argument로 대입하는것,

이게 함수형이 연산하는 방식이다.


이에 대한 가장 간편한 예시는 바로 꼬리재귀이다.


꼬리재귀는 공간의 배정 없이 순수하게 상태를 묘사한 연산이다.


sum list 0 = sum tail (0 + head)


두번째 인수가 마치 변수처럼 보이지 않는가?


대충 이렇게 이해하면된다.


물론, 더 깊이 들어가자면, 상태라는 것을 이어가기 위해선 상태를 조작하는 함수들을 엮어서 새로운 함수를 만들어야하며,

이것이 모나드연산으로 이루어짐을 알 수 있고,

그중에서도 특히 Reader 모나드와 State 모나드에 집중해야하는데,

이부분의 내용은 어렵다. 사실상 이 부분을 떼고 나면 함수형을 진짜로 입문했다고 말할 수 있는 단계이지 않을까 싶다.


모나드에 대한 이야기는, 함수형이 변수에 대한 배정 없이 연산을 하는 방법이 함수형스타일이라서 할 얘기가 많다는것만 표현하려고 했고,


지금은 대충 꼬리재귀로 이해하자. 함수형은 꼬리재귀를 통해서 순수하게 연산을 한다.


물론, 순수한 방법만이 정답은 아니고, 앞서 얘기했듯, 하스켈도 상태, 즉 부작용을 쓴다. 모나드 안에서


4. 컨트롤되지 않은 부작용 => 불변이라는 개념 형성.


여기까지가 하스켈을 중심으로 논리전개가 이루어졋다면, 이제부터는 오카멜을 중심으로 얘기가 흘러갈 것이다.

오카멜에 이르러서야 왜 객체지향 언어가 그토록 불변을 부르짖었는지 깨닫게 되었다.


오카멜은 하스켈과 큰 차이가 있다.

부작용이 컨트롤되지 않는다. 그럼 오카멜은 이제까지 얘기한 함수형 특징에 벗어나는가?

벗어난다고 볼 수 있다. 하지만, 하스켈이 구현 가능한 부분들. 하스켈의 강력함을 오카멜은 적용이 가능하다.

하스켈이 안전한부분은 보장받지 못한다.


앞서, Ref 라는 공간배정은 모나드 안에서 이루어져야한다고 했지만,

오카멜은 공간이 모나드컨텍스트 바깥으로 튀어다닌다.


그 결과로,


상태를 포함한 데이터타입 이라는 괴물이 튀어나온다.


하스켈에서는 상태를 포함한 데이터타입을 만들어봤자, 모나드 컨텍스트 안에서만 값을 꺼내올 수 있는데,

오카멜은 바깥에서도 값을 꺼내버린다.

자, 여기서부터는 객체지향이다.


오카멜의 O는 Objective를 의미한다고 들었는데, 이런 부분도 객체지향적인 요소라고 간주되는지는 모르겠다. 어쨌든 pure하지 않다는것은 확실하다.


dataType MyType 이 있고,

data mut_prop 라는 가변 프로퍼티가 있고

data prop 라는 프로퍼티가 있다고 가정하자.


이때, mut_prop 는 상태고, prop은 일반적인 원시타입이다.


절대로, 두개 다 상태인데 단지 prop은 변경 불가능 으로 받아들이면 안된다.

prop 은 int 고, mut_prop 은 int ref 라고 타입이 다른거라고 받아들여야한다.

int 는 공간이 배정되어있지 않다. 즉, 변경 불가능하다.

int ref 는 ref 뒤에 공간이 배정되어있고, 그 공간에 접근하는 부작용 메소드를 통해 연결된 공간을 조작할 수 있다.

int ref는 모나드 안에서만 공간조작이 이루어져야하지만, 오카멜은 모나드 바깥에서도 공간을 조작할 수 있다.



5. 잠시 짚고넘어가야할 사안 -> 함수형에서 let 이란?


논지를 진행하기 위해선 이단락이 꼭 필요하다.

앞서, 함수형은 변수선언이 없다고 말했다.

그러면, 함수형에서 let 이 보이는데 얘들은 뭔가요??


함수형에서 let 은 연산식 이름붙이기이다.


let a = expression 라고 쓴다면, 이는 절차지향과 해석방법이 다르다.

절차지향의 경우, expression을 실행하고 그 결과물을 a에 집어넣어라

함수형의 경우, "내가 앞으로 a라고 적으면 그것은 다음 expression 에 대한 축약이야" 라는 뜻이다.


즉, a를 쓸때마다 expression이 재평가된다.

a가 변수가 아니라 함수다 라고 생각하면 이해가 편할것이다.

a는 함수다.


다만, 무엇이 하스켈을 이해하기 어렵게 만드냐면, 하스켈의 지연평가는 전부 thunk라는 형식으로 내부에서 저장되어서 평가를 기다리는데'

같은 이름을 사용하면 a의 평가결과를 한번 기억해 놨다가, 재사용한다. 즉 여러번 연산을 반복하지 않는다는 의미.


심지어 이 연산 캐싱 로직은 매우 복잡해서, 숙련된 하스켈러들조차 연산의 평가시점을 완벽하게 계산하긴 어렵다고 한다.


근데, 그 복잡성은 하스켈의 단점이 되지 않는다.


왜냐?


참조투명성

표현식이 재평가 되던지 말던지간에 언제나 값은 똑같다. 즉, 코드의 재평가 여부가 코드의 전체 행동에 영향을 주지 못한다.


이제 이 단락의 결론인데,


let a = ref 0 로 공간을 배정하고

a := 3 로 쓰고

!a 로 읽으면


모두 같은 공간을 접근할까??


답은 yes 다.

하스켈에 대해서는 모나드를 풀어헤쳤을때 ref가 인수로 들어가고 인수가 named되어있으며 named parameter가 없기 때문(이라고 쓰고 그냥 무시하면 된다)

오카믈에 대해서는 오카멜은 앞서 설명한 지연평가가 적용되지 않는 Strict 한 언어다.

따라서 모든 수식들이 즉시 연산이되며, 그 연산 결과가 같은 이름에 대해 공유된다.



6. 다시 돌아가서... 결론


다시 앞서 설계한 MyType 과 prop 그리고 mut_prop 을 살펴보자.

원래 함수형이 MyType 을 다루는 방법은


꼬리재귀와 같이,


인자에 넣고, 흘려보내는 것이다.

그러면 MyType 은 m0 에서 m1으로, m1에서 m2로 바뀌면서 데이터를 변형해 나갈 것이고, 그 결과물로 마지막 함수를 투과하고 나온 m_n 이 결과물로 반환될 것이다.


하지만,

mut_prop이 있으면 이 방법은 먹히지가 않는다.


m이 내부의 공간을 바꾸기 위해서 mutable 하게 선언되었다고 생각하나??

공간을 바꾸기 위해서인가?

아니다. 공간 안의 변수를 바꾸기 위해서이다.

즉, 공간은 그대로 두어야한다. 공간이 변형되면 안된다.


공간 안의 변수가 그대로가 되면서 엄청난 일이 벌어진다.

Mytype은 m0 에서 m0으로, m0에서 m0으로... 이런식으로 체이닝이 이루어 질 것이고,

그 결과 prop 은 불변상태가 되어버린다.

mut_prop은 부작용을 통해서 바뀐다고 해도, prop은 전혀 건들수가 없는게 되는것이다.




prop이 불변인게 함수형 철학인가???


난 아니라고 생각한다.


m0 -> m1 -> m2 로 바꿔나가는 행동을 해야 함수형 철학이라고 생각한다.


함수형은 데이터를 고정시켜놓고 생각하는 철학이 아니라,

데이터를 통째로 바꾸는 철학이다. 내부가변성이 아닌,


근데 난 내부가변성도 좋다.

쓰고보니까 순혈 함수형을 주장하고있는것처럼 글이 써져버렸는데

난 오카멜의 융통성이 아주 마음에 든다.