하스켈 가볍게 맛보기
이번에 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_.gz | 569바이트 |
혹~
혹시 Pugs에 관심을 가지시려는건가요? :)
Audrey Tang이 Haskell로 Perl 6를 구현한 Pugs를 만든 이유가
http://cooking-with-lisp.blogspot.com/2005/04/haskell-and-perl-community.html 라는 글에도 있다시피 Haskell과 Perl 6가 유사한 점이 많아서라고 그러더군요.
함수형 언어.. 역시
함수형 언어.. 역시 C랑 엄청난 차이를 보이네요. 공부한게 C나 파이썬 밖에 없어서 이번에 Erlang을 해볼까 생각중인데, 처음공부해보는 함수형언어라서 엄청 고생할것 같습니다
저는 ML하면서 고생좀
저는 ML하면서 고생좀 했는데
그러고 나니까 다른 함수형 언어는 쉽게 접근할 수 있는것 같습니다.
덕분에 지금 프로그램의 원리와 구조 읽으려고 하는데 별로 부담은 안되는군요
이제 막 프로그래밍을 공부하거나 함수형 언어에 익숙해져보시려면
위 책이 괜찮을듯 싶습니다.
----
먼저 알게 된 것을 알려주는 것은 즐거운 일이다!
http://hangulee.springnote.com
http://hangulee.egloos.com
----
먼저 알게 된 것을 알려주는 것은 즐거운 일이다!
http://hangulee.springnote.com
http://hangulee.egloos.com
"프로그램의 원리와
"프로그램의 원리와 구조"를 검색해도 안나오는데 어떤 책인지 알려주실수 있나요?
아마도 이책
이 책이 아닐까 싶은데요..
컴퓨터 프로그램의 구조와 해석
http://www.aladdin.co.kr/shop/wproduct.aspx?ISBN=8991268323
----
자신을 이길 수 있는자는
무슨짓이든 할수있다..
즉..무서운 넘이란 말이지 ^-_-^
나? 아직 멀었지 ㅠㅠ
----
자신을 이길 수 있는자는
무슨짓이든 할수있다..
즉..무서운 넘이란 말이지 ^-_-^
나? 아직 멀었지 ㅠㅠ
아 .. 저 책
아 .. 저 책 맞습니다.. 정말 미안합니다.. 잠시 정신을 놓고 글을 썼나봅니다.
----
먼저 알게 된 것을 알려주는 것은 즐거운 일이다!
http://hangulee.springnote.com
http://hangulee.egloos.com
----
먼저 알게 된 것을 알려주는 것은 즐거운 일이다!
http://hangulee.springnote.com
http://hangulee.egloos.com
Programming in Haskell 추천합니다
작년에 나온 책인데, 프로그램 처음 배우는 사람이 알기 쉽게 되어 있더군요.
교보문고에도 현재 재고가 있긴 한데 가격이 좀 세요 -_-;;
임예진 팬클럽 ♡예진아씨♡ http://cafe.daum.net/imyejin
[예진아씨 피카사 웹앨범] 임예진 팬클럽 ♡예진아씨♡ http://cafe.daum.net/imyejin
음...
하스켈에서 흐름제어를 사용하지 않는 이유가 자세히 어떤거 때문인가여? 이해가 잘...
흐름제어라는게 for나 while을 얘기하는
흐름제어라는게 for나 while을 얘기하는 것이라면
함수형언어에서 그것을 피하는 이유는
for나 while은 값이 변하는 변수들의 상태를 검사해서 흐름을 제어하기 때문입니다.
함수형 언어의 가장 중요하고 큰 특징은 사이드이펙트(변수의 값을 바꾸는 것)를 피하는 것입니다.
때늦은 답변 ...
윗 분이 잘 설명해주셨네요.
하스켈이라는 언어에 한정해서 추가적으로 답을 해보면
저 글에서 언급했던 흐름제어라는 건 if문이나 for문 같은 걸 말했던 건데
절차형언어에서는 명령문이 어떤 순서(규칙)에 의해서 순차적으로 실행되는 개념인데
하스켈에서는 선언적인 언어로서 패러다임이 완전히 다릅니다.
기본적으로 명령문(?)의 순서를 지정할 수가 없고
모나드 같은 걸로 순서를 지정하는 것 비슷한 효과를 낼 수 있는데
그렇다고 또 그 순서대로 실행되는 것도 아니죠.
그래서 for문이나 if문을 흉내는 낼 수 있는데
하스켈에서는 그게 좀 번거롭게 돼있거니와
겉모양은 흐름제어같이 보이지만 내부적인 원리는 전혀 다르게 돼있습니다.
아무튼 처음 배우는 단계라면 복잡한 내용은 나중에 알아보고 다음 사항 정도만 알아두세요.
1. 하스켈에서는 흐름제어(if문과 for문 등)는 없다.
2. 하지만 모나드를 이용해서 흐름제어 비슷하게 흉내는 낼 수 있다.
더 자세한 내용은 monad와 lazy evaluation이 무엇인지를 심도있게 이해하다보면 알게 될 겁니다.
저도 때늦은 답변
하스켈은 함수형언어 중에서도 순수함수형언어입니다.
윗분께서 monad를 언급하셔서 잠깐 말씀 드리면,
이때문에 side effect처리를 위한 다양한 방법들이 구현되어 왔고,
ghc를 비롯하여 현재는 monad를 통해서 side effect를 처리할 수 있게되었습니다. (I/O는 필연적으로 side effect가 수반되는 부분이라, haskell은 순수영역과 부작용에 오염된 영역을 구분합니다. 이를 위해서 monad가 사용됩니다.)
(monad가 무엇인지, category theory에 대해서 강의도 듣고 해 보았지만, 저의 머리로는 이해불능이었습니다.)
C와 같은 절차식 언어는 '변수'를 조작하는 것으로 프로그램이 목적을 달성합니다.
그렇기 때문에 어떻한 '순서'로 변수를 조작하느냐가 중요합니다.
반면 함수형에서는 변수가 중심이 아니라 '방법(함수)'가 중심이 됩니다.
이때문에 haskell에서는 point free style의 코딩방식을 선호합니다. pint free에서는 코드에 변수를 기재하지 않거나 최소한으로만 기재합니다.
(알아보기 어렵기 때문에 변수를 기재해야 한다는 국내분도 계시지만, 익숙해지시면 변수가 주렁주렁 달린 코드가 이해하기 더 힘들다는 것을 실감하실 것입니다.)
저 역시 지금 공부중이라...
언젠가 자작한 업무용 프로그램들을 haskell로 다시 쓸 것을 꿈꾸고 있습니다.
ps. 함수형의 뛰어난 점은 코딩을 줄일수 있습니다. 예를 들면 xmonad라는 x 윈도 시스템은 2000여줄의 코드로 만들어졌다고 합니다.
댓글 달기