말 그대로 무엇하고도 묶여binding 있지 않은 자유로운 변수입니다. (개인 생각으론, 결국엔 무엇과는 묶여야지만 의미있는 일을 할 수 있으니, “아직은 누구와 묶이는지 모르는 변수”에 가깝습니다.)
※ Free 변수가 무엇인지는 하스켈로 가기 전 필수 코스 - 람다 대수 기본 용어를 참고해 주세요.
필요한 값이 있는데, 어디에 있는지, 무엇과 연결 되는지 지금 당장은 상관하지 않고 프로그래밍할 때 Free변수를 씁니다.
type Local = Int
func1 :: Local -> Int
= local + global func1 local
global
은 어디서 왔을까요? 지금 당장은 모릅니다. 일단 필요하니 써 두고 넘어갑니다. (위 코드는 아직 컴파일 되지 않습니다.)
func2 :: Local -> Int
= local - global func2 local
역시 지금은 정확히 뭐와 연결되는지 모르고, 그냥 전역 값 중에 하나겠거니 하고 프로그래밍합니다.
순수 함수에선 매개 변수말고 외부로부터 정보를 받을 수 있는 방법은 없습니다. 어찌 보면 매개 변수고, 어찌 보면 외부 변수로 보일 것 같은데, 스코프가 잘 보이게 람다 형태로 바꿔 보겠습니다.
= \local -> local + global
func1 = \local -> local - global func2
이제 global이 들어올 길을 만들어 보겠습니다. (바인딩!)
(저는 이게 람다와 Free변수의 파워라고 생각합니다. 보통 이름없는 함수니 어쩌니란 설명만 하고 넘어가는데, Free변수와 람다 변수를 실제 설계에서 어떻게 쓰는지 꼭 설명해야 하지 않을까하는 개인 생각입니다.)
= \global -> \local -> local + global
wrap1 = \global -> \local -> local - global wrap2
잘 보니 (wrap1
과 wrap2
가 같은 global
을 참조 한다는 가정) \global ->
중복입니다. 이 것만 따로 떼어내면,
type Global = Int
withGlobal :: (Global -> Local -> Int) -> (Global -> Local -> Int)
= \global -> f global
withGlobal f
= withGlobal wrap1
f = withGlobal wrap2 g
이제, 둘이 컴포지션이 되도록 바꾸는 작업만 남았습니다. Int -> (Int -> Int)
와 Int -> (Int -> Int)
를 컴포지션 하려면 어떻게 해야 할까요? 첫 번째 함수의 출력 (Int -> Int)
와 다음 번 함수의 입력 Int
이 같지 않아 그냥은 컴포지션할 수 없습니다. 각 함수의 첫 번째 인자로 global
를 주면 (Int -> Int)
와 (Int -> Int)
가 되니 바로 컴포지션이 됩니다. 그럼 먼저 global
를 주게 바꾸면 됩니다.
combinator :: (Global -> Local -> Int) -> (Global -> Local -> Int) -> (Global -> Local -> Int)
= \global -> \local -> (withGlobal g $ global) ((withGlobal f) global $ local)
combinator f g
-- Reader 모나드와 비교하기 쉽게 이름들을 바꾸면
= \r -> runGlobal (g (runGlobal f r)) r combinator f g
Reader
모나드를 알고 계신다면, 다음처럼 Reader
모나드의 바인드와 비교해 보시면, 모나드를 보는 눈을 넓힐 수 있습니다.
>>= k = R $ \r -> runReader (k (runReader m r)) r
m -- 중위 연산자로 표현하면
`combinator` g = \r -> runGlobal (g (runGlobal f r)) r f
Free 변수와 람다 변수의 바인딩이란 개념은, 마치 절차형에서 로컬 변수나 상위 스코프 변수를 정의해서 메모리로 쓰는 것과 비슷합니다. 함수와 함수를 합성compose하며 필요한 정보지만 아직 알 수 없는 것들은, 그냥 free_v1
, free_v2
…등으로 표시하고 넘어가며 설계합니다. 그리고, 나중에 \free_v1->
, \free_v2 ->
…와 바인딩하면 됩니다. 람다 함수를 품고 품게 설계된 함수 합성 뭉치 어디에서든, Free변수를 필요한만큼 쓰고 나중에 람다 헤드와 바인딩해버리면 됩니다.