Haskell #11 (Pattern matching)
이제부터는 하스켈의 함수를 사용하는 다양한 문법에 대해서 학습합니다. 이번 챕터에서는 그중에서도 패턴 매칭 기능에 대해서 살펴봅니다. 함수를 선언할때 여러가지 패턴으로 함수의 바디를 나누어서 심플하고 읽기 좋은 코드를 작성할 수 있습니다. 숫자, 문자, 리스트, 튜플 등과 같은 데이터 타입으로 패턴 매칭을 할 수 있습니다.
패턴 매칭을 사용한 첫번째 예 입니다. lucky라는 함수가 호출됐을때 위에서부터 순서대로 매칭되는 함수의 바디를 실행합니다. 따라서 위 예제의 경우, 입력 파라메터가 7일 경우에만 첫번째 바디에 매칭되어 “LUCKY NUMBER SEVEN!”이 출력될 것입니다. 아래는 실행한 예입니다.
이 예제에서는 입력값이 1 ~5이 아니면 “Not between 1 and 5”를 출력할 것입니다. 만약 마지막 패턴을 맨위로 옮기면 우선순위에 의해서 항상 “Not between 1 and 5”가 출력될 것 입니다.
재귀를 이용해서 구현한 factorial 함수 입니다. 하스켈에서 재귀는 중요하고, 이 부분은 다른 챕터에서 다루도록 하겠습니다. factorial 함수에 3을 넘기면 3 * factorial 2, 3 * (2 * factorial 1), 3 * (2 * (1 * factorial 0)) 순서대로 재귀적인 계산이 수행될 것 입니다. 최종적으로 0에 매칭됐을때 1이되면서, 3 * (2 * (1 * 1))이되어 6이 됩니다.
만약 여기서 두번째 패턴을 맨위로 올린다면 0을 포함한 모든 숫자에 매칭되어 종료되지 않을 것입니다. 따라서 패턴 매칭에서 패턴을 적는 순서는 매우 중요합니다. 이 순서는 항상 좀 더 세부적인 패턴을 먼저 적고 일반적인 것을 나중에 적어야 합니다.
만약 패턴매칭에 실패하면 아래 예제와 같은 예외가 발생하게 됩니다.
따라서 이런 예외발생을 막기위해서는 패턴을 만들때 반드시 모든 패턴에 걸릴 수 있는 패턴을 포함해야 합니다.
패턴매칭은 튜플에서도 사용할 수 있습니다.
위 예제는 패턴매칭을 사용하지 않고 두개의 백터를 더하는 함수를 만든 것입니다. 벡터의 x,y를 각각 더하기 위해서 먼저 튜플의 x 컴포넌트와 y를 분리하여 더하였습니다. 잘 동작하는 함수이지만 패턴매칭을 사용하여 아래와 같이 개선할 수 있습니다.
가독성도 개선되고, 내부에서 다른 함수를 호출하지도 않은 것을 확인할 수 있습니다. 또한 이미 모든 패턴에 대해서 처리하고 있습니다.
fst와 snd 함수는 두개의 컴포넌트를 추출하는 함수 입니다. 만약 튜플에서 세개의 컴포넌트를 추출해야한다면 어떻게 해야할 까요? 기본적으로 세개의 컴포넌트에서 추출하는 함수는 제공하지 않지만, 아래와 같이 직접 만들어서 사용할 수 있습니다.
이 예제에서 _는 리스트 정의(list comprehensions)내에 있는 것과 동일합니다. 쉽게말해서 _는 어떤 입력이 들어오든지 상관하지 않겠다는 의미입니다.
이 예제를 통해서 리스트 정의내에서도 패턴매칭을 사용할 수 있음을 확인할 수 있습니다. 만약 패턴 매칭이 실패하면, 그냥 무시하고 다음으로 넘어갑니다.
리스트에서도 패턴 매칭이 사용될 수 있습니다. [] 또는 :를 사용하면 쉽게 리스트 패턴 매칭을 할 수 있습니다. [1, 2, 3]은 1:2:3:[]의 문법적 표현이고, 사실은 동일한 리스트를 의미합니다. x:xs는 리스트의 head를 x에 나머지 tail을 xs에 바인딩하게 됩니다. 만약 리스트 원소가 한개라면 xs는 빈 리스트로 바인딩 됩니다.
Note: x:xs는 재귀함수에서 특히 많이 사용되는 패턴입니다. :를 포함하는 패턴은 길이가 1이상인 리스트에만 매칭될 수 있습니다.
만약 리스트의 첫번째부터 세개의 원소를 변수에 바인딩하고 나머지 전부를 변수에 바인딩하고 싶다면 x:y:z:zs와 같은 패턴을 사용하면 됩니다. 이 경우, 리스트는 3개 이상의 원소를 포함하고 있어야 합니다.
지금까지 배운 패턴매칭을 사용하여 head 함수를 구현하면 아래와 같습니다.
위 예제에서 사용된 _는 실제로 어떤 것에도 바인딩하지 않습니다. 또한 여러개의 변수로 매칭하려고 할때 괄호로 묶어서 표현하는 것을 확인할 수 있습니다.
예제에서 잘못된 입력이 들어왔을때 error 함수를 사용했는데, error 함수는 문자열을 입력받아서 런타임 에러를 만듭니다. 여기서 입력받은 문자열로 발생한 에러에 대한 상세 정보를 줄 수 있습니다. error 함수는 프로그램의 종료를 야기시키기 때문에 남발하는 것은 좋지 않습니다.
위 예제의 tell 함수는 빈 리스트, 원소가 1개인 리스트, 원소가 2개인 리스트, 원소가 2개 이상인 리스트를 처리했기때문에 안전합니다. 여기서 (x:[])와 (x:y:[])는 각각 [x], [x,y]로 쓸수도 있습니다. 이렇게 쓸때는 괄호는 필요 없습니다. (x:y:_)는 2개 이상의 어떤 리스트든 매칭되어야 하기때문에 중괄호로 작성될 수는 없습니다.
length 함수를 패턴매칭과 재귀를 사용해서 구현하면 위와 같습니다. 이 예제에서도 첫번째 패턴에서 빈 리스트를 처리하였고, 두번째 패턴에서 모든 리스트에 대해서 처리하였습니다.
패턴 매칭과 재귀를 사용하여 sum 함수를 구현한 예제입니다.
이 예제에서는 패턴의 앞에 “@”를 붙이고 all이라는 이름을 넣은 것을 볼 수 있습니다. 여기서 all@을 통해서 x:xs를 전부 적는것보다 쉽게 리스트 전체를 받아올 수 있습니다.
패턴 매칭안에서는 ++는 사용될 수 없습니다. 만약 (xs ++ ys) 패턴을 쓴다면, 어떤게 첫번째고 어떤게 두번째 리스트인지 알 수 없습니다.