Haskell #10 (Typeclasses 101)

이번 챕터에서는 하스켈의 타입클래스에 대해서 알아봅니다. 타입클래스는 어떤 행위를 정의하는 일종의 인터페이스입니다. 타입클래스는 OOP의 클래스와는 다르고, 오히려 Java 인터페이스에 가까운 개념입니다. 자 그럼 OOP는 잊고 시작해 볼까요??
Ref: Types and Typeclasses-Typeclasses 101

== 함수의 타입을 보면 아래와 같습니다.

하스켈에서는 비교 연산자 ==도 함수입니다. 마찬가지로 +, *, -, / 등 연산자들도 함수 입니다. 만약 함수가 문자들로만 구성되어 있다면 기본적으로 infix로 사용됩니다. 타입을 체크할때나 다른 함수에 파라메터로 넘길때, 또는 prefix 함수를 사용할 때는 위와 같이 괄호로 묶어 줍니다.

이 예제에서는 타입에 “=>”라는 새로운 심볼이 나온 것을 확인하실 수 있습니다. 여기서 “=>” 심볼 이전에 나온 모든 것은 클래스 제약(class constraint)이라고 부릅니다. “(==) :: (Eq a) => a -> a -> Bool”를 해석하면, == 함수는 Eq 클래스의 멤버이고, 동일한 타입을 가진 두개의 값을 얻어서 Bool을 반환한다 입니다. 여기서 “=>” 이전에 나온 “(Eq a)”가 함수의 입력 a가 Eq 계열이어야 한다는 클래스 제약에 해당합니다.

Eq 타입클래스는 같은 값인지를 테스트하기 위한 인터페이스를 제공합니다. 어떤 타입의 두값이 같은지를 테스트하기 위해서는 두 값이 Eq 타입클래스 계열이어야 합니다. 하스켈에서는 IO를 제외한 모든 표준 타입과 함수들은 Eq 타입클래스 계열입니다.

elem 함수는 내부적으로 ==을 사용하기 때문에 “(Eq a) => a -> [a] -> Bool” 타입을 가지고 있습니다.

몇가지 기본적인 타입클래스를 살펴보면 아래와 같습니다.

Eq는 같은 값인지 테스트하는 것을 지원하는 타입들에 사용됩니다. 멤버 함수로는 ==과 /= 함수가 있습니다. 따라서 어떤 함수내 타입 변수(type variable)에 Eq 클래스 제약이 있다면, 내부적으로 어디선가 ==또는 /=을 사용합니다. 따라서 이런 Eq계열의 타입들은 같은 값인지 비교가 가능합니다.

Ord는 순서를 가지는 타입들을 위한 것입니다.

Ord는 >, <, >=, <=와 같은 모든 비교 함수를 커버합니다. compare 함수는 두개의 같은 Ord 계열의 타입을 받아서 순서를 반환합니다. “Ordering”은 GT(greater than), LT(lesser than), EQ(equal)가 될 수 있는 타입입니다.

여기서 Ord 계열이 되기 위해서는 먼저 Eq 계열이어야 합니다.

Show는 문자열로 표현 가능한 타입입니다. 대부분의 함수에서는 Show 타입클래스를 show로 취급하는데, show는 Show 계열의 어떤 값을 얻어서 문자열로 표시해줍니다.

Read는 Show와 반대인 타입클래스라고 할 수 있습니다. read 함수는 문자열을 받아서 Read 계열의 타입으로 반환해줍니다.

위와 같이 사용하면 예외가 발생하는 것을 볼 수 있는데, 이것은 무엇을 반환해야 되는지를 ghci가 추론할 수 없기 때문에 발생한 예외 입니다.

read의 타입을 보면 Read 계열의 a를 리턴하는 것을 볼 수 있습니다. 따라서 a의 타입을 컴파일러가 추론하지 못하면 예외가 발생하게 됩니다. 이럴때 우리는 명시적으로 타입을 기술(type annotations)할 수 있습니다.

타입 주석(type annotation)은 표현식의 타입 무엇인지를 명시적으로 알려주기위한 방법입니다. 위와 같이 표현식에 끝에 “::”를 사용하고 타입을 명시하면 됩니다.

Enum 계열은 순차적인 타입입니다. 따라서 차례대로 열거할 수 있습니다. Enum 계열인 경우는 리스트의 범위(Range)를 사용할 수 있습니다. 또한 succ, pred 함수의 파라메터로 넘길 수 있습니다. (), Bool, Char, Ordering, Int, Integer, Float, Double가 Enum 계열에 해당합니다.

Bounded는 최대값과 최소값을 가지는 타입입니다.

minBound와 maxBound의 타입을 보면 다형성 상수라는 것을 확인하실 수 있습니다.

만약 튜플내의 컴포넌트들이 Bounded 계열이라면 튜플도 Bounded 계열이 됩니다.

Num은 숫자 타입클래스 입니다. 여기서 Num 계열은 다형성 상수인 것을 확인하실 수 있습니다.

위 예제에서 나온 타입은 모두 Num 계열입니다.

* 의 타입을 보면 *가 모든 숫자들을 허용한다는 것을 확인하실 수 있습니다. *는 두개의 동일한 타입의 숫자를 받아서 동일한 타입의 숫자를 반환합니다. 따라서 아래 예제와 같이 사용하면 두개의 타입이 다르기 때문에 에러가 나는 것을 확인할 수 있습니다.

Num 계열의 타입이라면 이미 Show, Eq의 계열입니다.

Integral도 역시 숫자 타입클래스 입니다. Num 계열은 실수와 정수 모두를 포함하지만, Integral은 정수 전체만 포함합니다. Int와 Integer가 Integral 계열에 해당합니다.

Floating은 Float, Double와 같은 부동 소수점을 포함합니다.

fromIntegral 함수는 숫자를 다루기위해서 매우 유용한 함수 입니다. 이 함수는 Integral을 받아서 좀 더 일반적인 Num을 반환해줍니다. (여기서 여러개의 클래스 제약을 포함하는데, 이럴때는 괄호안에 ,로 구분하여 표시한다.)이 기능은 정수와 부동소수점 타입이 함께 동작하게 할때 유용하게 사용됩니다.

만약에 우리가 리스트의 길이를 얻어온 다음에 3.2를 리스트에 추가한다면 위와같이 타입이 다르기 때문에 에러가 발생할 것입니다.

이때 위와 같이 fromIntegral를 사용하여 해결할 수 있습니다.