하스켈 가볍게 맛보기

redneval의 이미지
1724
points
11
points

이번에 Yet Another Haskell Tutorial 을 대충 번역해보면서

(http://wiki.kldp.org/wiki.php/YetAnotherHaskellTutorial)

하스켈의 장점이라 할 수 있는 람다함수, 타입추론, 함수합성, 함수의 부분적용에 대해서 알게 되었습니다.

자세한 설명은 Yet Another Haskell Tutorial 을 읽어보시고,

여기에서는 간단하게 특징을 살펴보겠습니다.

그냥 설명하면 다소 지루할 것 같아서, "간단한 계산기 만들기"를 해보겠습니다.

복잡한 계산말고 사칙연산정도만 가능하게 만들겁니다. 예를들면, 다음과 같은 계산을 수행합니다.

Input  : -2*3+4/2-7+5*4/2   
Output : -1.0

1. 함수의 정의

하스켈에서 함수의 정의는 매우 간단하게 이뤄집니다. 마치 C 언어에서 변수에 값을 대입하는 것처럼 간단합니다.

그럼, 어떤 문자를 입력받았을 때, 곱하기 또는 나누기 문자인지 판단하는 함수를 만들어 봅시다.

isMultDiv x = (x == '*') || (x == '/')

2. 함수의 합성

수학에서 (f ◦ g)(x) = f (g(x)) 의 식을 알고 있다면, 이미 함수합성에 대해서 알고 있는 것입니다.

하스켈에서는 ◦ 대신에 . 연산자를 사용합니다.

그러므로 다음과 같은 코드가 있다면,

isNotOperator x = not ( isOperator x )

다음과 같이 바꿀 수 있습니다.
isNotOperator x = (not . isOperator) x

3. 함수의 부분적용

함수를 정의할 때, 함수의 인자를 뺄 수도 있습니다. (자세한 설명은 튜토리얼 참고) 여기서 괄호는 없어도 됩니다.

isNotOperator = (not . isOperator)

4. 모든 연산자는 함수이다.

먼저 더하기 기능을 생각해봅시다. 다른 언어 같으면 add() 같은 함수가 필요하겠지만,

하스켈에서는 중위연산자에 괄호를 둘러싸면 곧 함수가 됩니다.

그래서, 1 + 1 대신에 (+) 1 1 같은 식으로 바꿔서 쓸 수 가 있습니다.

그래서 다음과 같이 opToFunc 함수정의가 있다면

opToFunc '+' a b = a + b

다음과 같이 바꿀 수 있고,
opToFunc '+' a b = (+) a b

함수의 부분적용을 이용하면 다음과 같습니다.
opToFunc '+' = (+)

5. C에 배열이 있다면, 하스켈에는 리스트가 있다.

하스켈의 기본 데이터구조에는 리스트가 있습니다.

이는 C 의 배열과 비슷하다고 생각할지 모르지만, 사실은 링크드 리스트에 가깝습니다.

그렇기 때문에, 리스트는 head 로 불리는 x 와 tail 로 불리는 xs 를 이용하여 (x:xs) 로 표현됩니다. (빈 리스트는 예외)

6. 재귀적 함수정의

리스트의 합을 구하는 함수를 정의해봅시다. (참고로, 표준함수에 sum 이라는 함수가 있습니다.)

_my_sum sum []     = sum
_my_sum sum (x:xs) = _my_sum (sum+x) xs

사용법은 _my_sum 0 [1,2,3,4] 와 같은 식으로 합니다.

함수형 언어의 특징 중 하나는 재귀적으로 함수를 정의하는 것입니다.

재귀적으로 함수정의가 이뤄지다보니, 함수형언어에 익숙하지 않은 분은 이런 문법이 당황스러울 겁니다.

그리고, 어떠한 부수효과(side effect)도 없이 계산이 이뤄져야 하므로 변수를 사용할 수 없습니다.

그래서 현재 상태를 나타내는 sum 값은 함수의 인자로 계속해서 전달하는 수 밖에 없습니다.

한 가지 더 말하면, 함수의 부분적용을 이용하여 다음과 같이 정의할 수 있습니다.

my_sum = _my_sum 0

이제 my_sum [1,2,3,4] 과 같이 사용할 수 있습니다.

7. 그 밖의 것들...

_calc_number 라는 함수는, 문자열을 입력받아서 연산자가 나타낼때까지 숫자만 뽑아내고 나머지 문자열도 반환하는 함수입니다.

예를 들어 _calc_number 0 "10+5" 는 (Just 10, "+5") 를 반환합니다.

_calc_number sum [] = (Just sum, [])
_calc_number sum (x:xs) =
    if isDigit x  -- x가 숫자인지 봐서...
        then _calc_number (sum*10+(read [x])) xs  -- 숫자이면 sum 에 더합니다.
        else if isNotOperator x  -- 연산자가 아닌지 봐서
            then _calc_number sum xs  -- 연산자가 아니면 그 문자는 통과합니다.
            else (Just sum, (x:xs))  -- 연산자가 맞으면 반환합니다.

(여기서 -- 뒷부분은 주석으로 인식됩니다. C 언어에서 // 으로 주석을 나타내는 것과 비슷합니다.)

우선, if문은 쉽게 이해가 될 것입니다. 튜플은 그냥 (x,y) 같은 순서쌍이라고 생각합시다.

Just 는 약간 설명이 필요하지만, 그냥 자료구조의 하나라고 생각합시다.

이번에도 핵심적인 것은 재귀적인 함수정의입니다. sum 이라는 함수의 인자를 통해 상태값을 넘기는 것을 이해했다면,

이 함수도 근본적으로 위의 _my_sum 함수와 크게 다르지 않음을 알게 될 것입니다.

이번에도 my_sum 과 마찬가지로, 사용하기 편리하게 calc_number 이라는 함수를 만들어 봅시다.

calc_number = _calc_number 0

이제 calc_number "1+2+3" 과 같이 사용할 수 있습니다.

8. let/in 구문

위의 코드를 보면 알겠지만, (C 언어와는 다르게) if문은 흐름제어를 하는 것이 아니고 연산자에 가까운 모습을 보이고 있습니다.

(그런 연유로 하스켈에서는, C 언어의 return 같이 흐름제어를 할 수 있는 구문이 없습니다.)

게다가, then이나 else 뒤에는 하나의 표현식이 와야합니다.

그러면 표현식이 두 개 이상이라면? let/in 구문을 사용하면 됩니다.

let A in B 라는 구문이 있으면 B 의 표현식에서는 A 에서 정의된 것을 사용할 수 있게 됩니다.

calc_multiplier 는 곱하기와 나누기만을 수행하는 함수입니다. 더하기/빼기 연산자를 만나면 멈추고 계산값과 나머지 문자열을 반환합니다.

_calc_multiplier (Just a) [] = (Just a, [])
_calc_multiplier (Just a) (x:xs) =
    if isMultDiv x  -- x 가 '*' 나 '/' 인 경우
        then let (Just b, str) = calc_number xs  -- 그러면 calc_number 함수를 이용하여 x 뒤의 숫자를 뽑아냅니다.
             in _calc_multiplier (Just (opToFunc x a b)) str -- x 가 어떤 연산자인지를 보고 숫자를 계산합니다.
        else (Just a, (x:xs))  -- 계산을 종료합니다.
_calc_multiplier Nothing str =  -- Nothing 은 초기상태를 나타냅니다.
    let (Just a, str') = calc_number str  -- calc_number 함수를 이용하여 숫자를 뽑아냅니다.
    in _calc_multiplier (Just a) str'
calc_multiplier = _calc_multiplier Nothing

이와 비슷하게 더하기/빼기를 수행하는 함수 calc_adder 를 만들어봅시다. 이 함수는 calc_multiplier 를 호출하므로, 이제 사칙연산이 모두 가능한 함수가 만들어졌습니다.
_calc_adder (Just a) [] = (Just a, [])
_calc_adder (Just a) (x:xs) =
    if isAddSub x
        then let (Just b, str) = calc_multiplier xs
             in _calc_adder (Just (opToFunc x a b)) str
        else (Just a, (x:xs))
_calc_adder Nothing str =
    let (Just a, str') = calc_multiplier str
    in _calc_adder (Just a) str'
calc_adder = _calc_adder Nothing

calc_adder 의 반환형은 (Just Double, String) 과 비슷하게 나타나는데,

우리는 숫자값만 필요하므로 다음과 같이 calculator 함수를 정의하여 숫자만 뽑아냅시다.

calculator str =
    let (Just a, str') = calc_adder str
    in a

9. 타입추론

하스켈은 정적타입 시스템이지만 지금까지 타입을 선언한 적은 한번도 없었습니다. 왜일까요?

하스켈은 컴파일시점에 코드를 분석하여 각 함수의 타입을 자동적으로 추론하게 됩니다.

그래서 타입선언이 필요없기는 하지만, 다음과 같이 타입선언하면 계산을 Double 형으로 하도록 강제할 수 있습니다.

calculator :: String -> Double

이로인해, 자동적으로 calculator 가 호출하는 calc_adder 의 타입도 바뀌고 (타입추론에 의해서),

차례로 _calc_adder, calc_multiplier, _calc_multiplier, calc_number, _calc_number 들도 타입이 자동적으로 바뀌게 됩니다.

10. $ 연산자

$ 연산자는 다음과 같이 정의됩니다.

f $ x = f x

별 필요없을 것 같은 연산자이지만, 우선순위가 매우 낮다는 특징으로 인해 다음과 같이 쓸 수 있습니다.
putStrLn $ show $ calculator line

이는 다음과 같습니다.
putStrLn ( show ( calculator line ) )

11. 입출력

하스켈에서 입출력은 가장 어려운 부분이기는 하지만, 다행히도 여기서는 아주 간단한 내용만 나옵니다.

main = do
    hSetBuffering stdin LineBuffering  -- 표준입력 초기화
    putStr "Input  : "  -- 문자열 출력.
    hFlush stdout       -- 버퍼를 비웁니다. C 언어의 fflush() 를 생각합시다.
    line <- getLine     -- 문자열 입력.
    putStr "Output : "
    hFlush stdout
    putStrLn $ show $ calculator line  -- 계산 결과 출력

12. 컴파일 및 실행방법

첨부된 Calc.hs 파일을 다음과 같이 컴파일합니다.

ghc --make Calc.hs -o Calc

실행법은 다음과 같습니다.
./Calc

첨부 파일파일 크기
Calc.hs_.gz569 bytes
aero의 이미지
5069
points

혹~

1
point

혹시 Pugs에 관심을 가지시려는건가요? :)
Audrey Tang이 Haskell로 Perl 6를 구현한 Pugs를 만든 이유가
http://cooking-with-lisp.blogspot.com/2005/04/haskell-and-perl-community... 라는 글에도 있다시피 Haskell과 Perl 6가 유사한 점이 많아서라고 그러더군요.

geneven의 이미지
4330
points

함수형 언어.. 역시

1
point

함수형 언어.. 역시 C랑 엄청난 차이를 보이네요. 공부한게 C나 파이썬 밖에 없어서 이번에 Erlang을 해볼까 생각중인데, 처음공부해보는 함수형언어라서 엄청 고생할것 같습니다

이한길의 이미지
7700
points

저는 ML하면서 고생좀

1
point

저는 ML하면서 고생좀 했는데
그러고 나니까 다른 함수형 언어는 쉽게 접근할 수 있는것 같습니다.
덕분에 지금 프로그램의 원리와 구조 읽으려고 하는데 별로 부담은 안되는군요
이제 막 프로그래밍을 공부하거나 함수형 언어에 익숙해져보시려면
위 책이 괜찮을듯 싶습니다.

----
먼저 알게 된 것을 알려주는 것은 즐거운 일이다!
http://hangulee.springnote.com
http://hangulee.egloos.com

geneven의 이미지
4330
points

"프로그램의 원리와

1
point

"프로그램의 원리와 구조"를 검색해도 안나오는데 어떤 책인지 알려주실수 있나요?

kall의 이미지
5962
points

아마도 이책

2
points

이 책이 아닐까 싶은데요..

컴퓨터 프로그램의 구조와 해석
http://www.aladdin.co.kr/shop/wproduct.aspx?ISBN=8991268323

----
자신을 이길 수 있는자는
무슨짓이든 할수있다..
즉..무서운 넘이란 말이지 ^-_-^
나? 아직 멀었지 ㅠㅠ

이한길의 이미지
7700
points

아 .. 저 책

2
points

아 .. 저 책 맞습니다.. 정말 미안합니다.. 잠시 정신을 놓고 글을 썼나봅니다.

----
먼저 알게 된 것을 알려주는 것은 즐거운 일이다!
http://hangulee.springnote.com
http://hangulee.egloos.com

imyejin의 이미지
14778
points

Programming in Haskell 추천합니다

2
points

작년에 나온 책인데, 프로그램 처음 배우는 사람이 알기 쉽게 되어 있더군요.
교보문고에도 현재 재고가 있긴 한데 가격이 좀 세요 -_-;;

임예진 팬클럽 ♡예진아씨♡ http://cafe.daum.net/imyejin

댓글 보기 옵션

원하시는 댓글 전시 방법을 선택한 다음 "설정 저장"을 누르셔서 적용하십시오.