하스켈 타입(Type)

타입(type)이란 데이터의 유형을 의미합니다.

 

변수를 지정하기 위해서는

어떤 데이터 타입을 사용할 건지

정해야 합니다.

 

그렇지 않고 데이터는 그냥

0과1의 반복이 될 뿐입니다.

 

타입은 카테고리를 규정하는 것과

비슷합니다.

 

우리가 어떤 수를 사용할 때

범위를 정하는 것에서 시작합니다.

 

중학교 수학정도에서 배우는게

자연수, 정수, 실수 뭐 그런것을

배웁니다. 숫자는 숫자인데

뭘 그런 것들을 알아야 하는가

골치아픈 일이라 생각할 수 있습니다.

 

중학교 수학에서는 집합과

수의 종류를 가르치는

이유를 알려주지 않으므로

많은 사람들이 수학을

매우 지루한 학문으로

생각하게 됩니다.

 

그렇다면 대학에 들어가면

끝나는가? 그렇지 않습니다.

수학은 응용학문과 공학의 기본

바탕이 되므로 원리를 모르면

결국 대학의 공학 과목에도

고전을 하게 되어 있습니다.

 

컴퓨터 공학에서 데이터 타입을

지정하는 이유는 수학에서

수의 집합을 정의하는 것과

비슷합니다.

 

컴퓨터에게 타입을 주지 않으면

그것은 0과1의 연산일 뿐이고

기계가 연산을 하는데는 지장이 없지만

어떤 타입인지 알수가 없어서

사람이 사용하는 것은 불가능합니다.

 

0과1의 연산에서 정수, 실수, 문자형 등

모든 것을 만들어 내기 때문에

0과1만 나열해 놓으면 그게

무슨 의미인지 알 수가 없습니다.

 

예를 들어 이진수 0000 0000 과

0000 0001를 더하면 0000 0001 입니다.

8bit의 메모리상에 저장됩니다.

 

이것이 의미하는 것은 정수 1인가요?

아니면 불린 형인 True 인가요?

아스키 코드의 NULL 문자 인가요?

 

정답은 Type 에 따라 다르다입니다.

 

Type에 따라 정수 1이 될수도

True일수도 NULL 일수도 있습니다.

 

마치 Type 에 따라 매칭되는

상대(테이블)가 달라지는 것과 같습니다.

 

아스키 코드 테이블은 7비트를

0~127까지 128개의 문자와

매치 시키는 것이고 

Int를 비롯한 대부분 정수형들은

숫자와 매치시키는 것 입니다.

숫자는 그 자체가 숫자긴 하지만

컴퓨터에는 이진수로 저장되기

때문이 우리가 입력과 출력을

할 때는 편의적으로 10진수로

변환해서 사용합니다.

 

키보드에 숫자 1을 입력하는 것은

0000 0001 이 아닙니다.

키보드에서 아스키 코드 0011 0001에

해당하는 신호를 운영체제에 넘겨주면

그것을 바이너리 파일(이진파일)에

저장할 때 비로소 0000 0001 로

바뀌는 것 입니다.

 

이러한 원리를 알게되면

자신만의 타입을 만들 수도 있습니다만,

프로그래머가 사용할 법한 대부분의

타입은 이미 언어의 컴파일러에

내장되어 있는 경우가 많습니다.

 

크게 봤을 때 대부분 언어는

다음의 타입을 가집니다.

 

- 숫자형 (2의 보수, 정수)

- 실수형 (부동소수점)

- 문자형 (문자형을 요소로 문자열)

- 불린형 (True or False)

 

C계열에서는 int variable 이런 식으로

앞에다 타입을 써주는데

하스켈은 함수형 프로그래밍이라

타입 표기 방식이 조금 다릅니다.

 

 

정적인 언어 (static)

 

하스켈은 static 정적인 언어라고 했습니다.

 

컴파일러가 프로그램이 실제

실행되기 전에 변수마다 어떤

타입을 사용할 것인지 확정하는

언어를 정적인 언어라고 합니다.

(static type language)

이는 코드를 작성할 때는 매우

까탈스럽게 검사를 하지만

결과적으로 실행시 불확실한

오류가 줄어드는 장점이 있습니다.

 

반면 동적인 언어(dynamic)의 경우

실행시간에 인터프리터에서 바로

코드를 번역하는 방식인데요.

파이썬이나 자바스크립트 처럼

보통 컴파일러가 알아서 타입을

추정합니다.

 

숫자 변수를 사용하고 싶으면

파이썬에서는 대략 number = 10

이렇게 정의하면 됩니다.

number 의 type 이 10인지 추정하는 것은

파이썬이 해결할 문제고 프로그래머는

아 이렇게 하면 대충 알아듣겠구나

이 정도로 충분합니다.

 

이런 기능을 추정 기능이라고 합니다.

 

정적인 타입 언어라면

일일히 다 써줘야 하는데

하스켈은 특이하게도 타입을

추정하는 기능도 있습니다.

 

하지만 역시 정확도를 높이기 위해

타입을 써주는 것이 좋습니다.

 

공통 타입

 

하스켈의 공통 타입에는 Int Char 등이

있습니다.

 

타입을 설정하려면 아래와 같이 합니다.

 

myNumber :: Integer
myNumber = 10

 

myNumber 의 타입을 Integer로

설정하고 10을 할당했습니다.

 

GHCI 에는 타입을 알아내는

:t 명령어가 있습니다.

 

어떤 변수라도 :t 를 사용하면

타입을 확인할 수 있습니다.

ghci> :t myNumber
myNumber :: Integer

 

Char 와 String 타입도

사용해 보겠습니다.

 

myChar :: Char
myChar = 'A'

myString :: String
myString = "Hello!"

main :: IO ()
main = do

    print myChar
    putStrLn myString
ghci> main
'A'
Hello!

 

함수의 타입

다음은 함수의 타입에 대해서

알아보겠습니다.

 

함수의 타입에는...

매개변수의 타입과 리턴값의

타입이 필요합니다.

 

아래는 세개의 Int 형 정수를 더해서

Int 형 정수로 돌려주는 함수입니다.

 

마찬가지로 함수의 타입도 :t 명령어로

확인이 가능합니다.

 

addThreeNumber :: Int -> Int -> Int -> Int
addThreeNumber x y z = x + y + z

main :: IO ()
main = do

    print (addThreeNumber 1 3 5)
ghci> main
9

 

함수의 타입은 좀 tricky 한데

타입 변수라는 것을 알아야 합니다.

 

타입변수

 

아래 리스트의 타입을 보면

헷갈리게 나옵니다.

ghci> :t head
head :: [a] -> a
ghci> :t tail
tail :: [a] -> [a]

 

a? [a]? 이는 타입변수로

어떤 타입이라고 될 수 있는

경우 사용할 수 있습니다.

 

C++나 자바의 지네릭스(generics)

타입과 비슷한 것이라 이것은

객체지향 프로그래밍을 학습하지

않았다면 이해가 어려울 수도 있습니다.

 

a라는 말은 a 가 Char Int Float 어떤 것도

될 수 있다는 말입니다.

최종적으로 함수가 호출되서

사용하는 시점에는 알게 됩니다.

 

예를 들어 Int 라 하면

 

head :: [Int] -> Int 가 되는데

 

head는 리스트의 첫번째 요소만

가져오는 함수입니다.

 

아래 결과를 보면 매개변수는

Int 형 리스트이고 리턴값은

Int 형입니다.

 

ghci> head [3, 7, 10]
3

이게 아래처럼 타입이 Char 로

바뀌면 head :: [Char] -> Char

가 되겠죠?

ghci> head ['a', 'n', 'b']
'a'

타입 변수를 사용한 이유는

간단합니다.

 

한개의 함수로 여러개의 타입을

사용할 수 있습니다.

이런 함수를 다형성 함수

(Polymorphic Function)

라고 합니다.

 

타입의 범위

 

Int 타입의 범위는 어디서부터 어디까지냐?

를 알고 싶다면 다음의 함수로

확인할 수 있습니다.

 

Int 형은 시스템마다 차이가 있습니다.

아래는 64비트 정수형 시스템에서

실행한 결과입니다.

 

ghci> minBound::Int
-9223372036854775808
ghci> maxBound::Int
9223372036854775807

 

문자의 경우 표기가 다른데

유니코드의 테이블의 범위를

의미합니다.

ghci> minBound::Char
'\NUL'
ghci> maxBound::Char
'\1114111'

 

타입클래스

타입클래스는 다소 고급의 주제로

기존 OOP (객체지향 프로그래밍)

사용자들에겐 혼란이 될 수 있습니다.

 

아래의 코드는 반복적인 표현이지만

설명하기 위한 예시입니다.

 

isEqual :: Eq a => a -> a -> Bool
isEqual a b = do
    if a == b 
        then True
        else False 


main :: IO ()
main = do
    print (isEqual 2 2)
    print (isEqual 2 5)
    print (isEqual 'a' 'a')
    print (isEqual 'a' 'b')
ghci> main
True
False
True
False

 

Eq a 에서 a가 타입변수인 것은

알았는데 => 라는 표시가 나옵니다.

 

여기서 Eq가 타입클래스입니다.

타입변수가 있는데 또 타입클래스가

있는 것 입니다.

 

이는 객체지향언어의 클래스보다는

자바의 인터페이스에 가까운

개녕으로 받아들이면 됩니다.

 

자바에서 인터페이스는

여러개 구현(implement)할 수

있습니다.

 

즉 하나의 클래스는 여러개의

인터페이스를 구현함으로써

다양한 속성을 가지게 되는거죠.

 

Eq 는 두 변수가 같다는 조건을

만들 수 있는 클래스입니다.

 

숫자를 넣어서 비교하면

다음과 같이 변할 겁니다.

 

isEqual :: Eq a => a -> a -> Bool

 

isEqual :: Int -> Int -> Bool

 

이렇게쓰면 Int 형만 비교가 되고

Char 형은 사용하지 못하겠죠.

 

자바의 컬렉션 프레임워크에서

지네릭(Generics)을 많이 사용하는데

그것도 마찬가지입니다.

 

ArrayList 클래스가 있는데

여기에는 int 형도 담을 수 있고

char 형도 담을 수 있고 float 형도

담을 수 있습니다.

 

각각의 자료타입을 위해서 새로운

클래스를 만들 필요가 없다는 것 입니다.

 

약간 어려울 수도 있는 설명이긴 한데...

어쨋든 하스켈은 이런 문법으로

처리를 한다~ 는 정도로 이해하면

충분합니다.

 

자바의 지네릭스에 비하면 훨씬

직관적이고 알아보기 쉽습니다.

 

하스켈에서 연산자는 함수입니다.

:t 명령어로 연산자의 타입클래스를

확인하면 아래와 같이 나옵니다.

 

ghci> :t (==)
(==) :: Eq a => a -> a -> Bool
ghci> :t (>) 
(>) :: Ord a => a -> a -> Bool
ghci> :t (<)
(<) :: Ord a => a -> a -> Bool
ghci> :t (<=)
(<=) :: Ord a => a -> a -> Bool
ghci> :t (>=)
(>=) :: Ord a => a -> a -> Bool



ghci> :t (+)
(+) :: Num a => a -> a -> a
ghci> :t (-)
(-) :: Num a => a -> a -> a
ghci> :t (*)
(*) :: Num a => a -> a -> a
ghci> :t (/)
(/) :: Fractional a => a -> a -> a

 

Ord 는 Order 의 약자로 순서가 있는

타입이고 Num 은 숫자형인데 Int 가

될 수도 있고 Float 가 될 수도 있습니다.

 

사칙연산에서 나누기는 Fractional

타입클래스인데 0으로 나눌 수 없는

나누기연산의 특성과 부동소수점

방식으로 나눗셈을 처리하기 때문입니다.

 

타입클래스를 보면 함수의 특성을

이해하는데 도움이 됩니다.

다형성 (Polymorpism)을 표현하는데

굉장히 직관적이죠.

show 와 read 함수

show는 putStrLn 함수에서 유용하게

사용됩니다. 문자열이 아닌 타입을

문자열로 변환해줍니다.

 

숫자형과 문자열을 함께 사용하려면

어떤 시점에는 형변환이 일어나야

하는데 show 가 그 일을 해줍니다.

 

rea는 반대로 문자열을 숫자형이나

리스트 등으로 변환합니다.

이때 뒤에 :: 자료형 을 명시해줘야

하는 경우가 있습니다.

 

역시 tricky 하지만 몇번 정도

연습을 해보면 적응할 수 있습니다.

 

ghci> :t show
show :: Show a => a -> String
ghci> :t read
read :: Read a => String -> a


ghci> show 5  
"5"
ghci> show 10.7
"10.7"


ghci> read "5"::Int
5
ghci> read "[1,3,5]"::[Int]
[1,3,5]
ghci> read "[1,3,5]"++[7]  
[1,3,5,7]

 

요약

하스켈 타입과 타입클래스 등

데이터를 사용하는 방법에 대해서

대략적으로 알아봤습니다.

 

역시 하스켈의 특별한 문법이

학습초기에 걸림돌이 될 수 있겠으나

시간이 지날수록 아 왜 이렇게

좋은 것을 그전엔 몰랐을까?

라는 생각이 들 수 있습니다.

 

다음 포스팅에도 하스켈 입문을

이어가도록 하겠습니다.

 

 

공유하기

facebook twitter kakaoTalk kakaostory naver band