Haskell에서 프로그래머가 정의한 자료형은 생성자에 값을 줘서 만든다. 원래 값을 꺼내려면 패턴 맞추기를 이용하면 된다.
data PersonData = Person Int deriving (Eq) older (Person a) (Person b) = compare a b
PersonData 생성자는 정수를 하나 받는다. 이 정수를 다시 꺼내고 싶으면 older 함수에서 한 것처럼 패턴 맞추기로 다시 꺼낼 수 있다.
data Stack a = Stk [a] deriving Show push x (Stk xs) = Stk (x:xs) pop (Stk (_:xs)) = Stk xs
위의 코드는 스택 (stack)이라는 자료구조를 구현한 것이다. 스택은 넣은 순서와 반대로 값을 꺼내는 자료구조이다. push를 하면 값을 하나 넣고, pop을 하면 값을 빼낸다. 리스트와 달리 스택에서는 항상 맨 위에 있는 값만 건드릴 수 있고, 중간에 있는 값에는 접근할 수 없다. 하지만 위에서 구현한 스택은 얼마든지 그렇게 할 수 있다.
middle n (Stk list) = list !! n
이렇게 중간에 있는 값에 마음대로 접근할 수 있다면 스택을 만든 의도가 허무해져버린다. 따라서, 패턴 맞추기를 못하도록 생성자의 사용을 막으면 된다. 생성자를 사용할 수 없는 자료형을 추상 자료형(abstract datatype: 줄여서 ADT)이라고 한다. 대표적으로 IO 자료형의 경우 안에 들어 있는 값을 꺼낼 수 없다. 방법은 간단하다. 모듈에서 생성자를 내보내지 않으면 된다.
module DataStr ( Stack (), push, pop ) where -- Stack, push, pop의 정의
이 모듈을 import해도 Stk 생성자를 불러들이지 않기 때문에 패턴 맞추기를 할 수가 없고 내용에 함부로 접근할 수가 없게 된다. 문제는 생성자를 쓸 수 없으니 새 스택을 만들지도 못한다는 것. 따라서 추상자료형의 경우에는 기본값 하나는 만들어줘야 한다. 여기서는 빈 스택을 하나 만들어주자.
empty = Stk []
이제 스택을 한 번 사용해보자.
*Main> empty Stk [] *Main> push 1 it Stk [1] *Main> push 2 it Stk [2,1] *Main> push 3 it Stk [3,2,1] *Main> pop it Stk [2,1] *Main> pop it Stk [1]
다른 예제를 살펴보자.
module Graphics ( Point ( color ), startPoint, move ) where
data Point = Pt { color :: Int, position :: (Int,Int) } deriving Show
startPoint = Pt { color = 0, position = (0,0) }
move p (x,y) =
if newX >= 0 && newX <= 640 && newY >= 0 && newY <= 480
then p { position = (newX,newY) }
else p
where
(a,b) = position p
newX = a + x
newY = b + y
위의 모듈은 Point 자료형과 그것을 다루는 함수로 구성되어 있다. Point 자료형은 색과 위치의 레코드인데 색은 아무렇게나 바뀌어도 상관없지만 위치는 일정 범위를 넘어서면 안된다. 이럴 경우에 position을 내보내지 않고 position을 일정한 방법으로만 바꿀 수 있게 하는 move 함수를 따로 만들어서 내보낸다. 모듈을 이렇게 만듦으로써 잘못된 작동을 예방할 수 있다.