(Haskell Tutorial) 2. Pattern matching

 

오늘은 하스켈의 function이다. 처음이라 이해하기 어려운 syntax와 새로운 style이 많았다. http://learnyouahaskell.com에서 읽고 재정리 한 글임을 밝힙니다.

Simple Function Definition

아래의 문법이 아주아주 간단한 버전의 함수이다. func_name {args seperated with space} = {expression}. 입문자의 입장에서 아주아주 난감했다. 함수에 괄호(parenthesis)도 등장하지 않다니, 하스켈을 할줄아는 친구에게 물어봤다. “함수에 괄호가 없으면 인자가 몇개인지 뭔지 헷갈리지 않아?”라고 했으나, 돌아온 답변은 “해보면 문제없다”였다. 아주아주 난감했지만, 일단 해보기로

> inc x = x + 1
> inc 2 
3 
> add_ x y = x + y 
> add_ 1 2 
3
> 1 `add_` 2
3 

backtip으로 함수를 wrap해 func x yx func y처럼 쓸수있다.

Function declaration with :t

Tutorial 1에서 사용했던 :t 명령어를 선언했던 function에 사용해보자.

> :t inc 
inc :: Num a => a -> a
> :t add_
add_ :: Num a => a -> a -> a

이전 tutorial에서 언급했듯이 :t는 expression :: type이다. 그러므로 {expression}=inc :: {type}=(Num a => a -> a) 또한 함수의 정의가 아닌 type 이다.

사용 실행하면 새로운 문법이 등장한다. => 기호 전 문자들은 class constraintvar a의 type을 미리 하고 기호의 이후는 a를 인자로 받아 Num type 값을 반환한다는 의미다. add_는 인자 값을 두개로 받는데 (a, a) -> a가 아닌 a -> a -> a 이유는 currying을 배우게 되면 알게될 것 이다.

Pattern Matching

pattern matching은 data를 함수의 pattern에 따라 분석된다. 아래 구문을 보면 바로 이해가 될것임, :set +mghci의 multiline을 enable하는 명령이다. 그리고 let 키워드로 함수를 정의시에 라인의 끝을 space없이 개행하면 multiline definition이 가능하다.

> :set +m 
> let inc 1 = 2
|     inc x = inc (x-1) + 1
> inc 3 
4 
> let first :: (a, b, c) -> a 
|     first (x, _, _) = x 
> first 1, 2, 3 
1 

_은 해당 값은 무시한다는 의미이다.

List

list는 특히 recursive한 function에서 자주 쓰이는 것 같다. ghci 인터프리터 쉘에서는 함수 정의시의 indent를 다음 pattern에서 indent를 맞추지 않으면 에러가 발생한다.

> let head :: [a] -> a
|     head [] = error "Empty list"
|			head (x:_) = x
> head [1, 2, 3]
1 
> let print :: (Show a) => [a] -> String
|			print [] = "Empty"
| 		print (x:[]) = "size one: " ++ show x 
| 		print (x:y:_) = "long with: " ++ show x ++ " and " ++ show y
> print [1]
"size one: 1"
> print [1, 2, 3]
"long with: 1 and 2"
> print [1, 2]
"*** Exception: <interactive>:(): Non-exhaustive patterns in function print

func []는 빈 list를 argument로 받았을 때, (x:y:_)로 list의 arg-1:x, arg-2: y, _: tail로 표현가능하다. 굳이 x, y 말고도 a, b, c, d, e 이런식으로 여러개의 n-th argument를 지정한 다음 ` _` : (리스트의 나머지: tail)로 사용가능하다.

As Pattern

list의 pattern matching에서 func (x:xs) = "do something" 처럼 head:tail를 의미하는 x:xs가 있다. 앞에 var@ 를 붙여 head:tail이 아닌 all, head,tail로 사용할 수 있다.

> let print _list@(x:xs) = "First char is '" ++ [x] ++ "' else '" ++ xs ++ "' original string is '" ++ _list ++ "'"
> print "Hello World"
"First char is 'H' else 'ello World'' original string is 'Hello World'"

Condition

Condition pattern matching은 if-elif-elif-else 처럼 값의 condition으로 pattern matching을 한다. condition pattern matcing이 정식명칙인지는 모르겠으나 tutorial page에서 따로 keyword를 알려주지 않았기 때문에 일단 이렇게 넘어간다.

> let max` a b 
|        | a > b = a
|        | otherwise = b
> max` 3 1 
3 
> max` 1 3 
3

특이한 점은 위 pattern matching에서는 함수 이름을 indentation에 맞춰쓰면 되지만, condition pattern은 함수 이름이 아닌 condition expressoin을 써야되기 때문에 | 로 대체 가능하다.

Where

아래는 여기에서 그냥 가져왔다. 딱봐도 뭔지 알겠지.

bmiTell :: (RealFloat a) => a -> a -> String  
bmiTell weight height  
    | bmi <= skinny = "You're underweight, you emo, you!"  
    | bmi <= normal = "You're supposedly normal. Pffft, I bet you're ugly!"  
    | bmi <= fat    = "You're fat! Lose some weight, fatty!"  
    | otherwise     = "You're a whale, congratulations!"  
    where bmi = weight / height ^ 2  
          skinny = 18.5  
          normal = 25.0  
          fat = 30.0  

이 하스켈은 놀랍게도 where 문 안에서 함수를 정의하는 것 또한 가능하다!

calcBmis :: (RealFloat a) => [(a, a)] -> [a]  
calcBmis xs = [bmi w h | (w, h) <- xs]  
    where bmi weight height = weight / height ^ 2