하스켈 프로그래밍 입문하스켈 학교 2016 년 5 월 14 일서광열
출처 : https://wiki.haskell.org/Haskell_logos/New_logo_ideas
출처 : http://i.imgur.com/GHxmrsR.jpg
교재
역사1930s
Alonzo Church
람다 대수1950s
John McCarthy 의 Lisp
1960s
Peter Landin’s ISWIM
첫 번째 순수 함수 언어
1970s
John Backus’s FP
고차함수와 프로그램의 이해를 강조한 함수 언어
1970s
Robin Milner 의 ML
첫 번째 현대적인 함수 언어타입 추론과 polymorphic 타입 제공
1970s - 1980s
David Turner 의 Miranda
lazy 함수 언어
역사1987 년하스켈PL 연구자들이 모여서 만든 표준 함수 언어
1990s:
Phil Wadler
타입 클래스와 모나드
2003
하스켈 언어 명세 Haskell Report 출간2010
개정 버전 출간
중요 특징• Purely functional
• Statically typed
• Lazy
출처 : https://xkcd.com/1312/
프로그래밍 스타일
int total = 0;
for (int i = 1; i <= 10; i++)
total = total + i;
sum [1..10]
Glasgow Haskell Compiler
• 하스켈 컴파일러 (ghc) 와 인터프리터 (ghci) 제공• www.haskell.org/platform
GHCi• REPL (Read-Eval-Print Loop)• 터미널에서 ghci 를 실행$ ghci
GHCi, version 7.10.3: http://www.haskell.org/ghc/ :? for help
Prelude>
Expression> 2+3*4
14
> (2+3)*4
20
> negate 3
-3
Standard Prelude
> head [1,2,3,4]
1
> tail [1,2,3,4]
[2,3,4]
> [1,2,3,4] !! 2
3
• 하스켈의 표준 라이브러리• GHCi 를 실행하면 자동으로 로딩
> take 3 [1,2,3,4,5]
[1,2,3]
> drop 3 [1,2,3,4,5]
[4,5]
> length [1,2,3,4,5]
5
> sum [1,2,3,4,5]
15
> product [1,2,3,4,5]
120
> [1,2,3] ++ [4,5]
[1,2,3,4,5]
> reverse [1,2,3,4,5]
[5,4,3,2,1]
유용한 GHCi 명령명령 의미
:load name 스크립트를 로드:reload 현재 스크립트를 다시 로드
:type expr expr 의 타입을 보여줌:? 모든 명령을 보여줌
:quit GHCi 를 종료
Function Application• 함수 f 와 인자 a
• f a
• 함수 f 와 인자 a, b
• f a b• 함수 f 와 인자 a, b, c
• f a b c• 함수 f, g 와 인자 a, b
• f a + g b
하스켈 스크립트• 함수를 정의할 수 있음• .hs 확장자를 가짐-- test.hs
main = putStrLn "Hello World!"
$ ghc test.hs -o test
$./test
"Hello World"
하스켈 함수 정의-- test.hs
double x = x + x
square x = x * x
$ ghci test.hs
Prelude> double 10
20
Prelude> square 10
100
하스켈 함수 정의fac n = product [1..n]
average ns = sum ns `div` length ns
Naming• 요구사항
• 함수와 인자 이름은 소문자• myFun, fun1, arg_2, x'
• 타입은 대문자• Bool, Char, String
• 컨벤션• 리스트 인자는 보통 s 를 끝에 붙임
• xs, ns, nss
레이아웃 규칙좋은 예
a = 10
b = 20
c = 30
나쁜 예
a = 10
b = 20
c = 30
나쁜 예
a = 10
b = 20
c = 30
레이아웃 규칙a = b + c
where
b = 1
c = 2
d = a * 2
타입• 연관된 값들의 집합• 예 ) Bool 타입은 True 값과 False 값을 가짐
타입 오류> 1 + False
<interactive>:16:3:
No instance for (Num Bool) arising from a use of '+'
In the expression: 1 + False
In an equation for 'it': it = 1 + False
하스켈의 타입• expression e 를 계산했을 때 타입 t 가 나오면 e :: t라고 표기함• 모든 well formed expression 을 타입을 가지며 , 컴파일 타임에 type inference 를 통해 자동으로 계산됨
타입 확인하기> not False
True
> :type not False
not False :: Bool
하스켈의 기본 타입타입 값Bool 논리값Char 문자
String 문자열Int [-2^29 .. 2^29-1] 사이의 정수
Integer 임의의 정수Float 부동소수점
리스트 타입• 리스트 : 같은 타입을 가지는 값의 나열
• [False, True, False] :: [Bool]
• ['a', 'b', 'c', 'd'] :: [Char]
• [['a'], ['b', 'c']] :: [[Char]]
• [t] 는 t 를 원소로 가지는 리스트 타입
튜플 타입• 튜플 : 서로 다른 타입을 가지는 값의 나열
• (False, True) :: (Bool, Bool)
• (False, 'a', True) :: (Bool, Char, Bool)
• ('a', (False, ('b')) :: (Char, (Bool, Char))
• (True, ['a', 'b']) :: (Bool, [Char])
• (t1, t2, …, tn) 은 j 번째 원소가 tj 타입인 n 튜플 타입
함수 타입• 함수 : 한 타입의 값을 다른 타입의 값으로 매핑
• not :: Bool -> Bool
• even :: Int -> Bool
• t1 -> t2 는 타입 t1 의 값을 타입 t2 의 값으로 매핑하는 함수의 타입
함수 타입add :: (Int, Int) -> Int
add (x,y) = x + y
zeroto :: Int -> [Int]
zeroto n = [0..n]
Curried 함수• curried 함수 : 함수를 결과로 리턴하여 인자 하나짜리 함수로 인자 여러 개의 함수를 표현add :: (Int, Int) -> Int
add (x, y) = x + y
add' :: Int -> (Int -> Int)
add' x y = x + y
Partial Application• Curried function 에 인자를 일부만 적용해서 새로운 함수를 만들 수 있음
• add' 1 :: Int -> Int
• take 5 :: [Int] -> [Int]
• drop 5 :: [Int] -> [Int]
Currying 의 컨벤션• 괄호가 너무 많아지는 것을 방지하기 위해 -> 는 right-
associative 함• Int -> Int -> Int -> Int
• Int -> (Int -> (Int -> Int))
Curried 함수의 application
• 반대로 function application 은 left associative함• 따라서 mult x y z 는 (((mult x) y) z) 를 의미함• 튜플을 명시적으로 사용하지 않으면 모든 하스켈 함수는 기본적으로 curried 함수가 됨
타입 변수
> :t head
head :: [a] -> a
> :t fst
fst :: (a, b) -> a
• 타입 변수는 소문자 a, b, c, … 로 표시• 어떤 타입이든 될 수 있음
Polymorphic 함수• polymorphic 함수 : 타입이 하나 이상의 타입 변수를 포함
• length :: [a] -> Int
• 모든 타입 a 에 대해서 [a] 를 주면 Int 를 리턴함
Polymorphic 함수> length [False,True]
2
> length [1,2,3,4]
4
> length ['a','b','c']
3
> length ["hello","world"]
2
Prelude 에 정의된 Polymorphic 함수
• fst :: (a, b) -> a
• head :: [a] -> a
• take :: Int -> [a] -> [a]
• zip :: [a] -> [b] -> [(a, b)]
• id :: a -> a
Overloaded 함수• overloaded 함수 : 타입이 하나 이상의 class
constraint 를 가지고 있는 polymorphic 한 함수• (+) :: Num a => a -> a -> a
Overloaded 함수 (+)> 1 + 2
3
> 1.0 + 2.0
3.0
> 'a' + 'b'
ERROR
Overloaded 함수 (==)
> 5 == 5
True
> 5 /= 5
False
> 'a' == 'a'
True
> "Ho Ho" == "Ho Ho"
True
> 3.432 == 3.432
True
Overloaded 함수 read
> read "5" :: Int
5
> read "5" :: Float
5.0
> (read "[1,2,3,4]" :: [Int])
[1,2,3,4]
> (read "(3, 'a')" :: (Int, Char))
(3, 'a')
하스켈의 타입클래스들• Num: 숫자 타입
• (+) :: Num a -> a -> a -> a
• Eq: 같은지 비교가 가능한 타입• (==) :: Eq a => a -> a -> Bool
• Ord: 순서가 비교 가능한 타입• (<) :: Ord a => a -> a -> Bool
하스켈의 타입클래스들• Show: 해당 타입을 문자열로 변환
• show :: Show a => a -> String
• Read: 문자열을 해당 타입으로 변환• read :: Read a => String -> a
Overloaded number literal
• 하스켈의 상수는 여러 타입을 가짐> : t 123
123 :: Num a => a
> :t (123 :: Int)
(123 :: Int) :: Int
> :t (123 :: Double)
(123 :: Double) :: Double
> :t (123 :: Int)
(123 :: Int) :: Int
조건 expression
abs :: Int -> Int
abs n = if n >= 0 then n else -n
signum :: Int -> Int
signum n = if n <0 then -1 else
if n == 0 then 0 else 1
• 하스켈에서 항상 값을 리턴해야 하기 때문에 else 구문을 생략할 수 없음
let 과 where
let y = a*b
f x = (x+y)/y
in f c + f d
f x y | y>z = ...
| y==z = ...
| y<z = ...
where z = x*x
Guardabs n | n >= 0 = n
| otherwise = -n
signum n | n < 0 = -1
| n == 0 = 0
| otherwise = 1
패턴 매칭not :: Bool -> Bool
not False = True
not True = False
(&&) :: Bool -> Bool -> Bool
True && True = True
_ && _ = False
잘못된 패턴 매칭(&&) :: Bool -> Bool -> Bool
_ && _ = False
True && True = True
리스트 패턴• 리스트 [1,2,3,4] 는 1:(2:(3:(4:[])))
• (:) 는 cons 연산자라고 불림head :: [a] -> a
head (x:_) = x
tail :: [a] -> [a]
tail (_:xs) = xs
Lambda expression
• lambda expression 을 이용하면 이름 없는 함수를 생성할 수 있음• \x -> x + x
• 인자 x 를 받아서 x + x 를 리턴하는 함수
Lambda expressionadd x y = x + y
add = \x -> (\y -> x + y)
const :: a -> b -> a
const x _ = x
const :: a -> (b -> a)
const x = \_ -> x
Lambda expressionodds n = map f [0..n-1]
where
f x = x * 2 + 1
odd n = map (\x -> x * 2 + 1) [0..n-1]
Section• 1 + 2 는 (+) 1 2 로 쓸 수 있음• 인자 중에 하나만 제공해서 새로운 함수를 만들 수도 있음> (1+) 2
3
> (+2) 1
Section 예제• (1+)
• (1/)
• (*2)
• (/2)
재귀 함수• 하스켈에서 함수는 자기 자신으로 정의 가능한데 이런 함수를 재귀 함수라고 부름
fac 0 = 1
fac n = n * fac (n-1)
fac 함수의 실행fac 3
= 3 * fac 2
= 3 * (2 * fac 1)
= 3 * (2 * (1 * fac 0))
= 3 * (2 * (1 * 1))
= 3* (2 * 1)
= 3 * 2
= 6
재귀 함수의 장점• 일부 함수는 재귀로 정의하는 것이 더 쉬움• 재귀로 정의한 함수는 mathematical induction 을 이용해 property 를 증명할 수 있음
리스트에 대한 재귀product :: Num a => [a] -> a
product [] = 1
product (n:ns) = n * product ns
length :: [a] -> int
length [] = 0
length (_:xs) = 1 + length xs
product 함수의 실행product [2,3,4]
= 2 * product [3,4]
= 2 * (3 * product [4])
= 2 * (3 * (4 * product []))
= 2 * (3 * (4 * 1))
= 24
length 함수의 실행length [1,2,3]
= 1 + length [2,3]
= 1 + (1 + length [3])
= 1 + (1 + (1 + length []))
= 1 + (1 + (1 + 0))
= 3
리스트에 대한 재귀reverse :: [a] -> [a]
reverse [] = []
reverse (x:xs) = reverse xs ++ [x]
reverse 함수의 실행reverse [1,2,3]
= reverse [2,3] ++ [1]
= (reverse [3] ++ [2]) ++ [1]
= ((reverse [] ++ [3]) ++ [2]) ++ [1]
= (([] ++ [3]) ++ [2]) ++ [1]
= [3,2,1]
인자가 여러 개인 재귀 함수zip :: [a] -> [b] -> [(a,b)]
zip [] _ = []
zip _ [] = []
zip (x:xs) (y:ys) = (x, y) : zip xs ys
리스트에 대한 재귀drop :: Int -> [a] -> [a]
drop 0 xs = xs
drop _ [] = []
drop n (_:xs) = drop (n-1) xs
(++) :: [a] -> [a] -> [a]
[] ++ ys = ys
(x:xs) ++ ys = x:(xs ++ ys)
append 시각화xs = [0, 1, 2]
ys = [3,4,5]
zs = xs ++ ys
퀵소트qsort :: Ord a => [a] -> [a]
qsort (x:xs) =
qsort smaller ++ [x] ++ qsort larger
where
smaller = [a | a <- xs, a <= x]
larger = [b | b <- xs, b > x]
퀵소트q [3,2,4,1,5]
++ [3] ++q [2,1] q [4,5]
++ [2] ++q [1] q [] q [] ++ [4] ++ q [5]
[1] [5]
고차함수• 고차함수 : 함수를 인자로 받거나 함수를 결과로 리턴하는 함수
twice :: (a -> a) -> a -> a
twice f x = f (f x)
map 함수• map :: (a -> b) -> [a] -> [b]
> map (+1) [1,3,5,7]
[2,4,6,8]
map 함수의 정의-- list comprehension
map f xs = [f x | x <- xs]
-- recursive definition
map f [] = []
map f (x:xs) = f x : map f xs
filter 함수• filter :: (a -> Bool) -> [a] -> [b]
> filter even [1..10]
[2,4,6,8,10]
filter 함수의 정의-- list comprehension
filter p xs = [x | x <- xs, p x]
-- recursive definition
filter p [] = []
filter p (x:xs)
| p x = x : filter p xs
| otherwise = filter p xs
foldr 함수• foldr 는 리스트에 대한 재귀의 패턴을 표현함
• f [] = v
• f (x:xs) = x ⊕ f xs
직접 재귀 예제-- v = 0, ⊕ = +
sum [] = 0
sum (x:xs) = x + sum xs
-- v = 0, ⊕ = *
product [] = 1
product (x:xs) = x * product xs
-- v = 0, ⊕ = &&
and [] = True
and (x:xs) = x && and xs
foldr 을 이용한 재귀 예제sum = foldr (+) 0
product = foldr (*) 1
or = foldr (||) false
and = foldr (&&) True
foldr 의 정의foldr :: (a -> b -> b) -> b -> [a] ->b
foldr f v [] = v
foldr f v (x:xs) = f x (foldr f v xs)
foldr 시각화
sum 의 계산sum [1,2,3]
= foldr (+) 0 [1,2,3]
= foldr (+) 0 (1:(2:(3:[])))
= 1+(2+(3+0))
= 6
product 의 계산product [1,2,3]
= foldr (*) 1 [1,2,3]
= foldr (*) 1 (1:(2:(3:[])))
= 1*(2*(3*1))
= 6
foldr 를 이용한 length 함수
length = foldr (\_ n -> 1 + n) 0
length [1,2,3]
= length (1:(2:(3:[])))
= foldr (\_ n -> 1 + n) 0
foldr 를 이용한 재귀 함수reverse = foldr (\x xs -> xs ++ [x]) []
(++ ys) = foldr (:) ys
foldr 이 유용한 이유• 직접적인 재귀보다 정의하기 쉬움• 함수의 property 를 foldr 의 algebraic property를 이용하여 증명할 수 있음• 프로그램 최적화가 쉬워짐
고차 함수의 예 takeWhile
takeWhile :: (a -> Bool) -> [a] -> [a]
takeWhile p [] = []
takeWhile p (x:xs)
| p x = x : takeWhile p xs
| otherwise = []
> takeWhile (/= ' ') "abc def"
"abc"
참고 자료1. Slides of Programming in Haskell
• http://www.cs.nott.ac.uk/~pszgmh/book.html
2. Learn You a Haskell for Great Good!• http://learnyouahaskell.com/chapters
3. A Gentle Introduction to Haskell 98• https://www.haskell.org/tutorial/haskell-98-tut
orial.pdf
연습 문제1. 리스트의 마지막 원소를 리턴하는 함수 myLast 를 작성하시오
• myLast :: [a] -> a
2. 리스트의 k 번째 원소를 리턴하는 함수 elementAt를 작성하시오• elementAt :: [a] -> Int -> a
연습 문제• 리스트가 palindrome 인지 아닌지를 리턴하는 함수를 작성하시오
• isPalindrome :: (Eq a) => [a] -> Bool> isPalindrome [1,2,3]
False
> isPalindrome "madamimadam"
True
> isPalindrome [1,2,4,8,16,8,4,2,1]
True
연습 문제• 연속된 리스트 원소를 제거하는 compress 함수를 작성하시오
• compress :: Eq a => [a] -> [a]
> compress "aaaabccaadeeee"
"abcade"
연습 문제• 리스트를 run-length 인코딩으로 리턴하는 함수
encode 를 작성하시오• encode :: Eq a => [a] -> [(Int, a)]
> encode "aaaabccaadeeee"
[(4,'a'),(1,'b'),(2,'c'),(2,'a'),(1,'d'),(4,'e')]
숙제• CIS 194 Homework 1,2,3,4,5 번을 풉니다
• http://www.seas.upenn.edu/~cis194/lectures.html