하스켈 함수

함수 언어

하스켈은 함수 언어입니다. 함수 언어의 대표적인 특징은 함수를 다른 함수에 인자로 넘기거나 함수가 다른 함수를 리턴하는 일이 가능하다는 것입니다. 이런 함수를 고차 함수(higher order function)라고 부릅니다. 이 글에서는 이런 특징을 가진 하스켈의 함수에 대해 살펴봅니다.

함수 호출

하스켈에서 함수 호출(function application)은 f a입니다. 여기서 f는 함수, a는 인자를 의미합니다. 함수 호출이 빈도가 높기 때문에 가장 간결한 문법을 사용하고 있습니다. 명령형 언어가 대개 f(a)와 같이 괄호를 사용하는 것과는 대비됩니다.

간단한 함수 호출의 예는 아래와 같습니다. isLower 함수는 인자 'a'를 입력으로 받아True를 값으로 돌려줍니다. 인자 'A'을 입력하면 False를 값으로 돌려줍니다. isLower함수는 Data.Char 모듈에 선언되어 있기 때문에 함수를 사용하기 위해서는 먼저 import구문을 사용해 임포트를 해주어야 합니다.

> import Data.Char
> isLower 'a' 
True
> isLower 'A'
False

또 다른 예로 fstsnd 함수가 있습니다. 둘 다 튜플을 인자로 받아 fst는 튜플의 첫 번째 요소를 리턴하고, snd는 튜플의 두 번째 요소를 리턴합니다.

> fst ('a', 1)
'a'
> snd ('a', 1)
1

함수의 인자 개수

하스켈 함수의 인자 개수는 항상 하나입니다. fstsnd 함수는 인자 'a'1 두 개의 인자를 받는 함수가 아니라 ('a', 1)이라는 튜플 값 하나를 인자로 받는 함수입니다.

하스켈에는 인자를 2개 이상 받는 함수가 존재하지 않습니다. 2개 이상의 인자가 필요하면 튜플을 넘기거나, 조금 있다 살펴볼 커리 함수(curried function)을 사용합니다.

함수의 타입

ghci에서 :t 명령을 이용하면 주어진 값의 타입을 확인할 수 있습니다. 아래 예를 보면,'a'의 타입은 Char임을 확인할 수 있습니다.

> :t 'a'
'a' :: Char

마찬가지 방법으로 함수의 타입도 확인할 수 있습니다. 앞서 살펴본 isLower 함수의 타입을 확인해보면 다음과 같은 결과가 출력됩니다.

> :t isLower
isLower :: Char -> Bool

isLower 함수의 타입이 Char -> Bool임을 확인할 수 있는데, 여기서 ->는 함수 타입을 나타냅니다. ->의 왼쪽에 있는 타입은 함수의 인자의 타입이고, ->의 오른쪽에 있는 타입은 함수의 리턴 타입입니다. 즉, isLower의 인자 타입은 Char, 리턴 타입은 Bool입니다.

같은 방법으로 fst의 타입도 확인할 수 있습니다.

> :t fst
fst :: (a, b) -> a

fst의 인자 타입은 (a, b), 리턴 타입은 a입니다. 튜플 타입은 (,)로 표현하는데, a타입을 첫 번째 요소로, b 타입을 두 번째 요소로 가지는 튜플 타입을 뜻합니다.

앞서 isLower 함수와 달리 구체적인 타입이 아닌 ab 같은 소문자로 표시된 타입은 다른 타입으로 대체될 수 있기 때문에 Char와 같은 타입과 구분하여 타입 변수(type variable)라고 합니다.

예를 들어, 튜플 ('x', 1::Int)(Char, Int) 타입을 가지는데 이 튜플로 fst 함수를 호출하면 fst 함수의 인자 타입 aChar로, bInt로 대체되어 fst의 타입은(Char, Int) -> Char가 됩니다. 같은 방식으로 (False, 'y') 튜플을 호출하면 aFalse의 타입인 Bool, b'y'의 타입인 Char가 되어 fst의 타입은(Bool, Char) -> Bool이 됩니다. 이처럼 하나의 함수가 인자에 따라 여러 타입을 가질 수 있는데, 이런 함수를 다형 함수(polymorphic function)라고 합니다.

연산자

하스켈은 +, -, &&, ||와 같이 특수 문자로 된 함수도 제공합니다. 이런 함수들을 연산자(operator)라고 부르는데, 연산자와 일반 함수의 차이점은 연산자는 infix로 일반 함수는 prefix로 사용한다는 점입니다. 즉, + 1 2가 아니라 1 + 2와 같은 형태로 함수를 호출하게 됩니다.

> 1 + 2
3

하지만 + 연산자를 prefix로 호출할 수도 있습니다. (+)와 같이 연산자를 괄호로 싸주면 일반 함수와 마찬가지로 prefix로 호출이 가능합니다.

> (+) 1 2
3

커리 함수(curried function)

앞서 하스켈 함수는 항상 인자를 하나만 받는다고 이야기하였습니다. 2개 이상의 인자가 필요할 경우, 튜플을 넘길 수 있다는 사실도 배웠습니다. 하지만 논리 연산자 (&&)의 예를 보면 인자를 2개 받는 것처럼 보입니다.

> False && True
False

분명 하스켈 함수는 인자를 1개만 받을 수 있다고 이야기했는데, (&&)는 어떻게 2개의 인자를 받을 수 있는 걸까요? :t 명령을 이용해서 (&&) 함수의 타입을 확인해 보겠습니다.

> :t (&&)
(&&) :: Bool -> Bool -> Bool

(&&)의 타입은 Bool -> Bool -> Bool입니다. 이 타입을 어떻게 해석해야 할까요? 앞서->는 함수 타입이고 ->의 왼쪽은 함수의 인자, ->의 오른쪽은 함수의 리턴 타입이라고 이야기했었습니다. 하지만 ->가 2개 있기 때문에 어디가 인자 타입이고, 어디가 리턴 타입인지 구분하기가 어렵습니다.

->은 우결합(right-associative)하기 때문에 Bool -> (Bool -> Bool)와 같이 괄호로 묶을 수 있습니다. 이 타입을 놓고 다시 해석을 해보면, (&&) 함수는 인자가 Bool 타입이고, 리턴 값은 함수 타입인 Bool -> Bool임을 알 수 있습니다.

바꿔 말해, (&&) 함수는 인자 2개를 받는 함수가 아니라 Bool 타입의 인자를 받아서Bool -> Bool 타입의 함수를 리턴하는 함수입니다. 하스켈은 고차 함수를 지원하는 언어이기 때문에 함수가 함수를 인자로 받거나 함수를 리턴하는 것이 가능합니다.

실제로 인자를 하나만 넘겨보겠습니다.

> (&&) False
Prelude> (&&) False

<interactive>:5:1: No instance for (Show (Bool -> Bool)) (maybe you haven't
applied enough arguments to a function?) arising from a use of ‘print’ In a stmt
of an interactive GHCi command: print it

에러 메시지는 ghci가 함수는 화면에 제대로 출력할 수 없기 때문에 발생합니다. 정말 함수를 리턴한 것이 맞는지는 :t 명령으로 확인할 수 있습니다.

> :t (&&) False
(&&) False :: Bool -> Bool

이처럼 리턴된 값이 Bool -> Bool 타입을 가진 함수라는 사실을 확인할 수 있습니다. 이 함수를 f라는 이름으로 바인딩하여 다시 True를 호출해 보겠습니다.

> let f = (&&) False
> f True
False

False && True(&&) 함수에 2개의 인자 FalseTrue를 넘긴 것이 아니라, 먼저False를 호출하고 그 결과로 함수가 리턴되면 다시 True를 인자로 리턴된 함수를 호출한 것입니다. 이 과정을 좀 더 명시적으로 보기 위해 괄호를 추가하면 다음과 같습니다.

> ((&&) False) True
False

이렇게 함수가 함수를 리턴하는 방식으로 여러 개의 인자를 받을 수 있게 만든 함수를 커리 함수(curried function)라고 부릅니다. 이와 달리 여러 값을 하나의 튜플로 묶어 넘기는 것을 언커리(uncurried function) 함수라고 부릅니다. (&&)는 커리 함수의 예이고, 앞서 살펴본 fstsnd 함수는 언커리 함수의 예입니다.

섹션

(+), (-)와 같은 연산자들은 모두 커리 함수이기 때문에 인자를 하나만 줘서 호출하면 함수를 리턴합니다. 예를 들어, (+) 1은 주어진 인자에 1을 더해서 리턴하는 함수가 됩니다.

> let f = (+) 1
> f 2
3

이렇게 바이너리 연산자에 인자 하나만 줘서 새로운 함수를 만드는 경우가 흔하기 때문에 하스켈은 (+1)과 같은 특별한 문법을 제공합니다. 이렇게 연산자에 인자 하나만 줘서 새로운 함수를 만드는 방법을 섹션(section)이라고 부릅니다.

> (+1) 2
3

피연산자의 위치에 따라 섹션을 만드는 방법은 2가지가 있습니다. 예를 들어 (1-)는 1에서 주어진 값을 빼는 함수이고, (-1)는 주어진 값에서 1을 빼는 함수입니다.

> (-5) 3
-2
> (5-) 3
2

One thought on “하스켈 함수

  1. Pingback: 하스켈 함수 – 서광열의 하스켈 스쿨

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s