Free 변수

Posted on November 11, 2022

Free 변수란?

말 그대로 무엇하고도 묶여binding 있지 않은 자유로운 변수입니다. (개인 생각으론, 결국엔 무엇과는 묶여야지만 의미있는 일을 할 수 있으니, “아직은 누구와 묶이는지 모르는 변수”에 가깝습니다.)

※ Free 변수가 무엇인지는 하스켈로 가기 전 필수 코스 - 람다 대수 기본 용어를 참고해 주세요.

지금 당장은 무엇인지 모른다.

필요한 값이 있는데, 어디에 있는지, 무엇과 연결 되는지 지금 당장은 상관하지 않고 프로그래밍할 때 Free변수를 씁니다.

type Local = Int

func1 :: Local -> Int
func1 local = local + global

global은 어디서 왔을까요? 지금 당장은 모릅니다. 일단 필요하니 써 두고 넘어갑니다. (위 코드는 아직 컴파일 되지 않습니다.)

func2 :: Local -> Int
func2 local = local - global

역시 지금은 정확히 뭐와 연결되는지 모르고, 그냥 전역 값 중에 하나겠거니 하고 프로그래밍합니다.

순수 함수에선 매개 변수말고 외부로부터 정보를 받을 수 있는 방법은 없습니다. 어찌 보면 매개 변수고, 어찌 보면 외부 변수로 보일 것 같은데, 스코프가 잘 보이게 람다 형태로 바꿔 보겠습니다.

func1 = \local -> local + global
func2 = \local -> local - global

더 이상 Free하지 않다. Binding.

이제 global이 들어올 길을 만들어 보겠습니다. (바인딩!)
(저는 이게 람다와 Free변수의 파워라고 생각합니다. 보통 이름없는 함수니 어쩌니란 설명만 하고 넘어가는데, Free변수와 람다 변수를 실제 설계에서 어떻게 쓰는지 꼭 설명해야 하지 않을까하는 개인 생각입니다.)

wrap1 = \global -> \local -> local + global
wrap2 = \global -> \local -> local - global

잘 보니 (wrap1wrap2가 같은 global을 참조 한다는 가정) \global -> 중복입니다. 이 것만 따로 떼어내면,

type Global = Int

withGlobal :: (Global -> Local -> Int) -> (Global -> Local -> Int)
withGlobal f = \global -> f global

f = withGlobal wrap1 
g = withGlobal wrap2

합성

이제, 둘이 컴포지션이 되도록 바꾸는 작업만 남았습니다. 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)
combinator f g = \global -> \local -> (withGlobal g $ global) ((withGlobal f) global $ local) 

-- Reader 모나드와 비교하기 쉽게 이름들을 바꾸면
combinator f g = \r -> runGlobal (g (runGlobal f r)) r 

Reader 모나드를 알고 계신다면, 다음처럼 Reader 모나드의 바인드와 비교해 보시면, 모나드를 보는 눈을 넓힐 수 있습니다.

m >>= k          = R $ \r -> runReader (k (runReader m r)) r
-- 중위 연산자로 표현하면
f `combinator` g =     \r -> runGlobal (g (runGlobal f r)) r 

람다 변수는 일종의 메모리

Free 변수와 람다 변수의 바인딩이란 개념은, 마치 절차형에서 로컬 변수나 상위 스코프 변수를 정의해서 메모리로 쓰는 것과 비슷합니다. 함수와 함수를 합성compose하며 필요한 정보지만 아직 알 수 없는 것들은, 그냥 free_v1, free_v2…등으로 표시하고 넘어가며 설계합니다. 그리고, 나중에 \free_v1->, \free_v2 -> …와 바인딩하면 됩니다. 람다 함수를 품고 품게 설계된 함수 합성 뭉치 어디에서든, Free변수를 필요한만큼 쓰고 나중에 람다 헤드와 바인딩해버리면 됩니다.

Github 계정이 없는 분은 메일로 보내주세요. lionhairdino at gmail.com