Haskell study 3

18
Haskell Study 3. type & typeclass

Transcript of Haskell study 3

Haskell Study

3. type & typeclass

Type

Haskell은 앞에서도 이야기했듯이 정적 타입 언어입니다. Haskell의 모든 표현식들은 컴파일 타임에

타입이 뭔지 알 수 있어야만하고, 이런 특성은 컴파일 타임에 굉장히 많은 버그를 잡을 수 있게 해

줍니다. GHCi에서 :t 커맨드를 통해 표현식의 타입을 확인할 수 있습니다.

Prelude> :t 'a''a' :: CharPrelude> :t TrueTrue :: BoolPrelude> :t "Hello!"[Char]Prelude> :t (True, 'a')(True, 'a') :: (Bool, Char)

Type

타입은 표현식 뒤에 ::(has type of 라고 읽습니다) 기호와 함께 표기됩니다. 앞에서 봤듯이 'a'는

Char 타입을 갖고, True는 Bool 타입, "Hello!"는 [Char] 타입(String과 동의어 - 문자열은 문자의

list죠), 튜플은 그 튜플을 이루는 원소들의 타입에 따라 타입이 결정됩니다. ('a','b','c')같은 건

(Char, Char, Char) 타입을 갖겠죠. 당연한 이야기지만 함수에도 타입이 있습니다.

first.hs-- 연습문제 및 예제에서 다뤘던 함수들입니다. Haskell에서 주석은 --로 표기합니다.

-- 함수의 타입 선언에서 인자와 리턴값의 타입을 구분하여 표기하지 않습니다.

-- 모두 -> 기호를 통해 연속적으로 표기합니다.

even' :: Int -> Bool -- even은 Int 하나를 받아 Bool 하나를 리턴합니다.

even' n = n `mod` 2 == 0double2 :: Int -> Int -> Int -- double2는 Int 2개를 인자로 받아 Int를 리턴합니다.

double2 x y = double x + double y

Type

Haskell에 있는 몇 가지 타입들을 살펴봅시다.

• Int

정수를 나타내는 대표적인 타입입니다. -2147483648 ~ 2147483647 까지 범위의 정수들을

표현할 수 있습니다.

• Integer

역시 정수를 나타내는 타입입니다. 단, 이 타입은 숫자 범위에 한계가 없습니다. 당연한 이야기지만 Int

보다 조금 느리긴 합니다.

• Float

보통 정밀도(single precision)의 부동 소수점 수입니다.

Type

• Double

Single 보다 더 정밀한(double precision) 부동 소수점 수입니다.

• Bool

논리 타입입니다. True 또는 False라는 두 개의 값을 가지고 있습니다.

• Char

문자를 나타냅니다. 홑따옴표(')로 감싼 하나의 문자의 형태로 표기합니다.

• Tuple

튜플은 구성 원소에 따라 굉장히 많은 종류의 타입이 존재합니다. 한 가지 특징적인 것은 빈 튜플 ()

또한 타입이며 이 타입의 값은 () 하나 밖에 존재하지 않는다는 것입니다.

Type variable

다른 언어의 제네릭이나 템플릿과 비슷한 개념으로 Haskell에는 타입 변수라는 것이 있습니다.

리스트의 첫 번째 원소를 가져오는 head 함수의 타입은 뭘까요?

Prelude> :t headhead :: [a] -> a

head의 타입은 [a] -> a입니다. head 함수는 임의 타입(a)의 리스트([a])를 인자로 받아 해당 임의

타입(a)의 값 하나를 반환하는 함수라는 의미죠. 이 때 head 타입 선언에 나타난 ‘a’와 같이 임의

타입에 대응될 수 있는 것을 ‘타입 변수’라고 합니다. Haskell의 모든 타입은 대문자로 시작하는 반면

타입 변수는 타입이 아니라 타입 ‘변수’기 때문에 소문자로 시작하죠. 꼭 한 글자일 필요는 없지만 보통

한 글자로 많이 씁니다.

Type variable

Prelude> :t fstfst :: (a,b) -> a

fst 함수는 두 개의 임의 타입을 원소로 가진 튜플을 인자로 받아 그 튜플의 첫 번째 원소와 같은 타입을

가진 값을 반환하는 함수입니다. 이 때문에 fst를 2개보다 많은 원소를 가진 튜플에 쓸 수 없죠.

타입 변수를 사용하는 다른 함수들의 타입도 예제 삼아 한 번 확인해봅시다.

Prelude> :t zipzip :: [a] -> [b] -> [(a,b)]Prelude> :t lengthlength :: [a] -> Int

Type Class

Haskell에는 타입 클래스라는 개념이 있습니다. 이름에 ‘클래스’라는 말이 들어가 있어서 조금 헷갈릴

수 있지만, Haskell의 타입 클래스는 타 언어의 클래스와는 아무런 관련이 없습니다. 오히려 따지자면

인터페이스의 개념에 더 가깝습니다. 예시를 위해 == 함수의 타입을 확인해 봅시다.

Prelude> :t (==)(==) :: Eq a => a -> a -> Bool

기호 => 앞의 것은 클래스 제약(class constraint)이라고 부릅니다. 이는 타입 변수 a가 타입

클래스 Eq에 속하는 경우에만 == 함수를 쓸 수 있다는 뜻이죠. 서로 같은지 다른지 비교할 수 있는

타입이라면 타입 클래스 Eq에 속해야합니다.

Type Class

Prelude> :t elemelem :: Eq a => a -> [a] -> Bool

elem함수는 내부적으로 리스트에 속하는 원소인지 아닌지를 판별하기 위해 == 함수를 이용합니다.

그래서 elem 함수는 Eq 타입 클래스에 속한 타입에게만 사용할 수 있습니다.

Haskell에는 기본적으로 정의된 여러 가지 타입 클래스들이 있습니다. 각 타입 클래스들의 특징을

하나씩 살펴봅시다.

Type Class

Eq 서로 같은지 다른지 판별할 수 있는 타입들을 위해 존재합니다. 이 타입 클래스의 멤버라면 == 함수와

/= 함수를 사용할 수 있습니다.

OrdOrd는 순서를 가진 타입들을 위한 타입 클래스입니다. 이 타입 클래스는 >, <, >=, <= 등의 함수를

제공합니다. 또 compare 함수(앞이 뒤보다 크면 GT, 같으면 EQ, 작으면 LT 반환)를 제공합니다.

Ord 타입 클래스의 멤버가 되려면 반드시 Eq 타입 클래스에 속해야만 합니다.

Prelude> 5 `compare` 3GTPrelude> "abcd" < "efgh"True

Type Class

Show 이 타입 클래스의 멤버는 문자열로 변환할 수 있습니다. show 함수는 Show 타입 클래스에 속하는

타입 값 하나를 받아서 그걸 문자열로 변환해줍니다.

Prelude> show 3"3"Prelude> show 5.334"5.334"Prelude> show True"True"

Type Class

Read Read는 Show와 반대되는 타입 클래스라고 볼 수 있습니다. 이 타입 클래스에 속한 값들은

문자열로부터 해당 타입의 값을 만들어낼 수 있습니다. 이 때 read 함수를 씁니다.

Prelude> read "True" || FalseTruePrelude> read "[1,2,3,4]" ++ [3][1,2,3,4,3]Prelude> read "4" + 59

Type Class

Read 하지만 read 함수는 그냥 그 함수만 가지고는 사용할 수 없습니다. read "4"와 같은 식에서, Haskell

은 이걸 어떤 타입의 값으로 바꿔야할 지 알아낼 수 없기 때문이죠. 반면에 read "4" + 5와 같이

타입을 추론할 수 있는 식의 일부로 사용할 경우 read "4"를 4로 바꿔야 4 + 5 계산이 가능하다는

점으로부터 Haskell이 타입을 추론해 낼 수 있기 때문에 이럴 때는 사용이 가능합니다. 만약 read

함수를 단독으로 쓰고 싶다면 그 때는 변형하고자 하는 값의 타입을 ::(type annotation)을 이용해

명시해주어야 합니다.

Prelude> read "4" :: Int4Prelude> read "[1,2,3,4]" :: [Int][1,2,3,4]

Type Class

Enum Enum은 열거될 수 있는 타입들을 위한 타입 클래스 입니다. Enum 타입 클래스에 속한 타입들의

경우 각각 자신의 이전 값과 다음 값을 가져오는 pred, succ 함수를 쓸 수 있으며 리스트 범위(list

range)를 이용할 수 있다는 장점이 있습니다.

Prelude> ['a'..'e']"abcde"Prelude> [LT..GT][LT, EQ, GT]Prelude> succ 'B''C'

Type Class

Bounded 이 타입 클래스의 멤버들은 상한선과 하한선을 갖고 있습니다.

Prelude> minBound :: Int-2147483648Prelude> maxBound :: BoolTruePrelude> minBound :: Char'\NUL'

Type Class

Num 이 타입 클래스의 멤버들은 숫자처럼 동작할 수 있습니다.

Prelude> :t 2020 :: Num a => a

숫자 리터럴은 그 자체로 다형적인 타입처럼 동작합니다. 숫자 리터럴은 Num 타입 클래스에 속하는

모든 타입에 대해 그 타입처럼 동작할 수 있습니다.

Prelude> 20 :: Int20Prelude> 20 :: Float20.0

Type Class

Integral 이 타입 클래스 역시 숫자와 관련된 타입 클래스입니다. 단 Num이 모든 숫자를 포함하는 반면

Integral 타입 클래스에는 정수와 관련된 타입들만 속합니다. 이 타입클래스에 속한 타입으로는 Int,

Integer가 있습니다.

Floating Floating 타입 클래스 역시 숫자와 관련된 타입 클래스고, 이름에서도 알 수 있듯이 부동 소수점 관련

타입들만 속합니다. 이 타입 클래스에 속한 타입으로는 Float, Double이 있습니다.

Type Class

fromIntegral 숫자 연산과 관련하여 유용한 함수로 fromIntegral이라는 함수가 있습니다. 이 함수의 타입 서명은

(Num b, Integral a) => a -> b로, Integral에 해당하는 타입을 Num에 해당하는 타입으로

바꿔주는 역할을 합니다. 숫자인데 서로 다른 타입인 경우 같이 계산할 때 유용합니다. 예를 들어 list

의 리턴 타입은 Int인데, 이 값에 3.2라는 부동 소수점을 더하고 싶다면 fromIntegral 함수를 써야

합니다.

Prelude> fromIntegral (length [1,2,3,4]) + 3.27.2