C a
는 317(원서), 472(번역서)에 있는 도메인 집합 타입입니다.
표시적 의미론은 기본 연산을 받아서 의미집합을 반환하는 함수입니다.
의미 집합은 언어에 따라 달라질 수 밖에 없죠.
FRP에서는 셀과 스트림 두 가지 요소밖에 없으니 그 두가지에 대해서만 의미 집합을 정의하면 됩니다.
즉, 셀과 스트림이 표현하는 것이 도대체 무엇이고 그걸 수학적으로 정의하려면 어떻게 정의해야 할까를 고민해서 집합을 정의한 것이 C a
와 S a
입니다. (여기서 C a
는 하스켈의 제네릭 타입 표기법이고, 자바식으로 하면 C<a>
라고 생각하시면 됩니다. S a
도 마찬가지고요)
Stream a
는 간헐적으로 a
타입의 이벤트를 발사합니다. 이를 수학적으로 표현한다면 시간과 발사한 이벤트의 값을 묶은 리스트가 되겠죠. 예를 들어, [(T0, 1), (T1, 2)]
같은 리스트는 T1
시점에 1
을, T2
시점에 2
를 발사한 Stream int
타입의 스트림이겠죠. 이런 리스트의 타입을 정의하려니 다음과 같은 정의가 나오는 겁니다.
type S a = [(T, a)]
여기서 [(T,a)]
는 (시간, a 타입의 값)
튜플로 이뤄진 리스트를 하스켈식으로 표기한 것입니다.
마찬가지로, 셀은 초기값이 있고, 발사는 안하지만 내부적으로 시간에 따라 값이 변하니까 변화되는 값을 시간하고 짝지어 리스트로 표현하면 되겠죠. Cell int
의 의미를 그런 리스트로 표기한다면 타입이 (int, [(T, int)])
가 될겁니다. 일반적인 타입 a
에 대한 제네릭 타입으로 타입을 지정한다면 결국 다음과 같이 되는거죠.
type C a = (a, [(T,a)])
셀을 만들어내 구문의 의미와 스트림을 만들어내는 구문의 의미를 밝혀주는 함수가 각각 있습니다. 굳이 구문을 강조하는 이유는 의미함수는 어떤 프로그램 조각을 받아서 그 조각의 뜻을 밝히는 함수이기 때문입니다.
occs :: Stream a -> S a
: 스트림을 만들어내는 구문을 S a
의미집합에 속한 의미 중 하나로 매핑해 줍니다.
steps :: Cell a -> C a
: 셀을 만들어내는 구문을 C a
의미집합에 속한 의미 중 하나로 매핑해 줍니다.
예를 들어, 가장 쉬운 Never
를 보면,
Never :: Stream a
Never
의 타입은 Stream a
occs Never = []
Naver
의 의미는 []
(시간이 지나도 아무것도 발사하지 않음)
그 다음으로 MapS
를 보면,
MapS :: (a → b) → Stream a → Stream b
MapS
는 a
타입을 b
로 매핑해주는 함수를 받고, Stream a
를 받아서 Stream b
를 반환해주는 함수
occs (MapS f s) = map (\(t, a) → (t, f a)) (occs s)
Maps f s
라는 FRP 식은 occs s
의 결과(이걸 말로 하면 s 스트림의 의미 = s 스트림이 fire 하는 이벤트들)에 대해 t
는 그대로 t
로 (즉 같은 시간에 발사됨), a
는 f a
로(즉 s 스트림이 fire하는 모든 이벤트의 값에 f
를 적용한 결과를 발사함) 한다는 뜻이 됩니다.
Constant :: a → Cell a
Constant
는 a
타입의 값을 받아서 Cell a
를 만들어내는 기본 연산입니다.
steps (Constant a) = (a, [])
Constant a
라는 식은 초기값이 a
이고 그 값의 변화가 없는(그래서 리스트가 []
입니다) 것으로 해석됩니다.
위 스트림 섹션의 Naver는 Never의 오타인 듯 합니다.