23
Haskell Study 4. syntax in functions

Haskell study 4

Embed Size (px)

Citation preview

Page 1: Haskell study 4

Haskell Study

4. syntax in functions

Page 2: Haskell study 4

Pattern matching

패턴 매칭은 함수를 만들 때 사용할 수 있는 굉장히 강력한 구문입니다. 패턴 매칭은 어떤 데이터가

가져야 할 패턴을 명시하고, 데이터가 있을 때 그 데이터가 해당 패턴에 맞춰 분해될 수 있는 지

확인하는 과정을 거칩니다. Haskell에서 함수는 데이터의 패턴에 따라 서로 다른 여러 개의 본체를

가질 수 있습니다.

f :: (Integral a, Num b) => a -> bf 0 = 0f 1 = 1f n = f (n-1) + f (n-2)

Prelude> f 1055

위와 같이 패턴 매칭을 이용해 피보나치 함수를 굉장히 쉽고 직관적으로 구현할 수 있습니다.

Page 3: Haskell study 4

Pattern matching

패턴 매칭은 위에서부터 차례대로 내려오며 검사합니다. 즉, 패턴에 따른 함수 구현의 순서가 서로

다르면 결과 역시 달라집니다. 아래 두 함수를 봅시다.

f :: (Integral a) => a -> Stringf n = "must run this function!"f 0 = "it’s 0"

f2 :: (Integral a) => a -> Stringf2 0 = "it’s 0"f2 n = "must run this function!"

이 두 함수는 패턴에 따른 함수 선언의 순서만 바꿨을 뿐이지만 결과가 전혀 달라집니다.

Page 4: Haskell study 4

Pattern matching

*Main> f 0“must run this function!”*Main> f 1“must run this function!”*Main> f2 0“it’s 0”*Main> f2 1“must run this function!”

위와 같이 함수 f의 경우 f n에 대한 정의가 먼저 이루어지면서, 모든 인자에 대해 ‘n’이라는 패턴이

성립하므로 아래의 f 0 패턴까지 가지 못하고 함수가 실행됩니다. 그래서 패턴 매칭을 할 때는 명확한(

특수한) 패턴을 앞에 두고, 보다 일반적인 패턴을 뒤에 두게끔 작성하시는 것이 좋습니다.

Page 5: Haskell study 4

Pattern matching

패턴 매칭은 실패할 수도 있습니다. 이 경우 예외가 발생합니다.

foo :: Int -> Intfoo 0 = 0foo 1 = 1foo 2 = 2

*Main> f 00*Main> f 4*** Exception: test.hs:(2,1)-(4,7): Non-exhaustive patterns in function f

패턴 매칭을 쓸 때는 모든 종류의 패턴이 매칭될 수 있게끔 신경써서 코딩해야 합니다.

Page 6: Haskell study 4

Pattern matching

튜플에 대한 패턴 매칭도 가능합니다. 두 개의 벡터 값을 더하는 함수를 생각해보죠.

addVector :: (Num a) => (a,a) -> (a,a) -> (a,a)addVector a b = (fst a + fst b, snd a + snd b)

물론 위 함수도 잘 동작하지만, 아래 코드가 좀 더 깔끔합니다.

addVector :: (Num a) => (a,a) -> (a,a) -> (a,a)addVector (x1, y1) (x2, y2) = (x1 + x2, y1 + y2)

이런 패턴 매칭을 이용해서 원소가 3개인 페어(트리플)에서 특정 한 원소를 꺼내오는 함수를 작성할

수 있습니다.

Page 7: Haskell study 4

Pattern matching

first :: (a,b,c) -> afirst (x, _, _) = x

second :: (a,b,c) -> bsecond (_, y, _) = y

third :: (a,b,c) -> cthird (_, _, z) = z

패턴 매칭에서 해당 위치에 오는 값에 아무런 관심이 없는 경우(뭐가 와도 상관없는 경우)에는 _

기호를 이용합니다.

Page 8: Haskell study 4

Pattern matching

패턴 매칭은 list comprehension에서도 이용할 수 있습니다.

pairToNum :: (Num a) => [(a,a)] -> [a]pairToNum xs = [a+b | (a,b) <- xs]

*Main> pairToNum [(3,4), (1,2), (5,7), (6,3)][7,3,12,9]

Page 9: Haskell study 4

Pattern matching

튜플과 비슷하게 리스트에서도 패턴 매칭을 이용할 수 있습니다. 텅빈 리스트([]), cons 연산자(:)

등을 이용해서 패턴에서 해당 리스트가 가져야 할 형태를 나타낼 수 있습니다. 패턴 매칭을 이용해서

한 번 head 함수를 구현해봅시다.

head' :: [a] -> ahead' [] = error "Can't call head on empty list."head' (x:_) = x

error 함수는 런타임 에러를 발생시키는 함수입니다. 잘못된 케이스의 함수 호출에 대해 간략한

정보를 남긴 후 프로그램을 죽일 때 사용합니다.

Page 10: Haskell study 4

Pattern matching

다른 몇 가지 예시도 한 번 살펴봅시다.

length' :: (Num b) => [a] -> blength' [] = 0length' (_:xs) = 1 + length' xs

sum' :: (Num a) => [a] -> asum' [] = 0sum' (x:xs) = x + sum' xs

Page 11: Haskell study 4

Pattern matching

패턴 및 해당 데이터 전체가 모두 필요한 경우 @를 이용할 수 있습니다.

headFirst :: (Show a) => [a] -> StringheadFirst [] = "empty string"headFirst all@(x:_) = show all ++ "'s first element is " ++ show x

*Main> headFirst [1,2,3,4]"[1,2,3,4]'s first element is 1"*Main> headFirst "abcd""\"abcd\"'s first element is 'a'"

Page 12: Haskell study 4

연습 문제

•yesNoBool 값 하나를 인자로 받아 그 값이 True면 "Yes", False면 "No"를 리턴하는 함수를 만들어 봅시다.

예를 들어 yesNo (True || False) 는 "Yes"를 리턴해야 합니다.

•truemanBool 값 리스트를 인자로 받아 True인 원소의 개수를 리턴하는 함수를 만들어 봅시다. 예를 들어

trueman [True,True,False,False,True]는 3을 리턴해야 합니다.

• listToPairslist를 인자로 받아 각 원소를 두 개씩 순서대로 짝 지은 페어의 리스트를 리턴하는 함수를 만들어

봅시다. 원소 개수가 홀수 개인 경우 마지막 원소는 버립니다. 예를 들어 listToPairs [1,2,3,4,5] 는

[(1,2), (3,4)]를 리턴해야합니다.

Page 13: Haskell study 4

Guard

패턴 매칭이 데이터가 특정 패턴을 만족하는 지를 판단하는 방법인 반면 가드는 데이터가 특정 조건을

만족하는 지를 판단하는 방법입니다. 여러 개의 if ~ else if가 나열되어 있는 구조와 비슷한 형태라고

생각하면 됩니다. 우선 예제를 봅시다.

bmiTell :: (Floating a) => a -> StringbmiTell bmi | bmi <= 18.5 = "You are underweight." | bmi <= 25.0 = "you are normal." | bmi <= 30.0 = "you are fat." | otherwise = "you are very fat."

가드는 인자 다음에 파이프(|)와 논리 표현식을 이용해 표현합니다. 위에서부터 순서대로 인자가

조건을 만족하는지 확인한 다음에 만족한다면 거기에 해당하는 코드를 실행합니다.

Page 14: Haskell study 4

Guard

가드는 보통 앞의 예제와 같이 여러 줄에 가드의 각 조건과 거기에 해당하는 코드를 나열합니다.

조건을 여러 줄에 나눠 쓸 때는 인덴트(indent)에 조심하셔야 합니다. 각 파이프의 위치가 서로

인덴트가 다르다면 컴파일 에러가 발생합니다.

보통 가드의 맨 마지막은 otherwise입니다. otherwise는 단순히 True로 정의되어 있으며, 다른

가드가 처리하지 못한 조건을 처리하기 위해 존재합니다.

패턴과 가드를 같이 쓸 경우, 가드에서 만족하는 조건이 없을 경우 단순히 다음 패턴으로 넘어갑니다.

다음 패턴이 존재하지 않는다면 마찬가지로 예외가 발생합니다. 그리고 가드를 쓸 때 한 줄에 모든

코드를 다 써도 상관없지만, 가독성이 떨어지기 때문에 별로 추천할만한 방식은 아닌 것 같네요.

max' :: (Ord a) => a -> a -> amax' a b | a > b = a | otherwise = b

Page 15: Haskell study 4

where

where 절은 해당 함수 내부에서 반복적으로 쓰이는 여러 표현식 등을 위해 사용됩니다. 앞의 bmiTell

함수를 where를 이용해 수정해보겠습니다.

bmiTell :: (Floating a) => a -> a -> String bmiTell weight height | bmi <= 18.5 = "You are underweight." | bmi <= 25.0 = "You are normal." | bmi <= 30.0 = "You are fat." | otherwise = "You are very fat." where bmi = weight / height ^ 2

키와 몸무게를 인자로 받아 bmi지수를 계산해서 판단하게끔 했고, 이 때 bmi 계산 공식은

반복적으로 쓰이기 때문에 where 절을 이용했습니다.

Page 16: Haskell study 4

where

where는 코드의 가독성을 높여줄 뿐만 아니라 같은 계산식은 한 번만 계산해도 되게 만들어줌으로써

실행 속도의 향상까지 가져옵니다. where 절 내부의 내용은 해당 함수 내에서만 사용 가능합니다.

또 where 절에서도 패턴 매칭을 이용할 수 있습니다.

bmiTell :: (Floating a) => a -> a -> String bmiTell weight height | bmi <= skinny = “You are underweight.” | bmi <= normal = “You are normal.” | bmi <= fat = “You are fat.” | otherwise = “You are very fat.” where bmi = weight / height ^ 2 (skinny, normal, pat) = (18.5, 25.0, 30.0)

Page 17: Haskell study 4

where

where 절 내부에서도 함수를 선언할 수 있으며, where 절 내부에 where절을 중첩해서 쓸 수도

있고, 함수를 만드는 데 필요한 문법들(패턴 매칭, 가드 등)도 모두 사용 가능합니다.

calcFibos :: (Integral a) => [a] -> acalcFibos xs = [fibo x | x <- xs] where fibo 0 = 0 fibo 1 = 1 fibo n = fibo (n-1) + fibo (n-2)

Page 18: Haskell study 4

let in

let in 표현식은 where 절과 비슷한 역할을 합니다. let (bindings) in (expression)의 형태를 하고

있으며, where이 뒤에 binding이 오는 반면 let은 binding이 앞에 온다는 차이점이 있습니다.

cylinder :: (Num a) => a -> a -> a cylinder r h = let sideArea = 2 * pi * r * h topArea = pi * r^2 in sideArea + 2 * topArea

let in 표현식은 let 절에서 바인딩한 값을 in 뒤에서 사용할 수 있습니다. 또 where절이 구문적인

구조인 반면 let in 표현식은 if문처럼 코드 상의 어디서든지 등장할 수 있습니다. 즉, let in 표현식은

그 자체로 평가되며 결과 값이 존재해야만 합니다.

Page 19: Haskell study 4

let in

한 줄에 여러 개의 바인딩을 하고 싶다면 세미콜론(;)을 이용합니다.

Prelude> let a = 5 in a * a * a125Prelude> let squre x = x * x in (squre 5, squre 6)(25, 36)Prelude> let a = 100; b = 200; c = 300; in a + b + c600

list comprehension에서도 let을 사용할 수 있습니다. 이 때 let은 바인딩의 역할만 합니다.

Prelude> [f x|x<-[0..10],let f 0=0;f 1=1;f n=f (n-1)+f (n-2)][0,1,1,2,3,5,8,13,21,34,55]

Page 20: Haskell study 4

case expression

case 표현식은 명령형 언어의 switch - case 구문과 비슷한 개념이나, 훨씬 강력한 기능을 갖고

있습니다. case 표현식 역시 표현식이기 때문에 평가되는 값이고, 코드의 중간에 사용할 수 있습니다.

구문은 아래와 같은 구조를 갖고 있습니다.

case expression of pattern -> result pattern -> result pattern -> result ...

함수 인자의 패턴 매칭과 굉장히 유사합니다. 특정 표현식의 패턴에 따른 결과 값을 case 표현식을

통해 나타낼 수 있습니다. 다만 함수에서의 패턴 매칭과는 다르게 case 표현식은 표현식이기 때문에

어디에서든지 사용가능합니다.

Page 21: Haskell study 4

case expression

head’ :: [a] -> ahead’ xs = case xs of [] -> error "Can’t call head on empty list." (x:_) -> x

descList :: [a] -> StringdescList xs = "this List is " ++ case xs of [] -> "Empty List" [x]-> "Singleton List" xs -> "Long List"

Page 22: Haskell study 4

연습 문제

•maximum어떤 리스트가 주어져 있을 때, 리스트의 원소중 가장 큰 원소를 리턴하는 함수를 구현해 봅시다. 빈

리스트의 경우 error 함수를 사용해서 런타임 에러를 일으키게끔 만들어 보세요.

•take숫자와 리스트가 주어졌을 때, 리스트에서 처음부터 해당 숫자만큼의 원소를 잘라서 리턴하는 함수를

만들어 봅시다. 단, 숫자가 0이하인 경우 빈 리스트([])를 리턴해야 합니다.

•zip두 개의 리스트가 주어졌을 때 두 리스트의 원소 각각을 튜플로 짝지은 리스트를 리턴하는 함수를

만들어 봅시다.

위 함수는 모두 표준에 존재하는 함수입니다. 이름을 조금 다르게 작성하셔야 합니다.

Page 23: Haskell study 4

연습 문제

•quickSort(*hard) 어떤 리스트가 주어져 있을 때, 그 리스트를 quickSort를 이용해 정렬하는 함수를 구현해봅시다.

처음에는 풀기 상당히 어려운 문제니 천천히 시간을 들여서 고민해봅시다.