2021년 2월 16일 11시 59분
아래 내용은 인사이트 출판사의 제안으로 작성 중인 책의 초고입니다. 실제 출판 시에는 내용이 달라질 수 있습니다. 많은 의견 부탁드립니다.
함수(function)는 비슷한 모양의 여러 식을 하나로 요약한 것으로, 함수를 호출하면 함수의 몸통이 계산되어 결괏값이 정해진다. 함수를 호출할 때 인자(argument)를 함께 전달할 수 있으며 인자의 값에 따라 함수의 동작이 달라질 수 있다.
def x(x1, …, xn) = e
def x(x1, …, xn) = e에서 x는 함수 이름으로, 함수를 호출(call)할 때 함수를 지칭하는 데 사용된다. x1부터 xn까지는 매개변수(parameter)로, 함수가 호출되어 함수의 몸통을 계산할 때 인자의 값에 접근하기 위해 사용된다. e는 몸통으로, 함수가 호출되었을 때 계산되는 식이며 이 식을 계산한 값이 함수 호출의 결과이다.
def double(n) = n + n
위 함수는 이름이 double, 매개변수가 하나이며 그 이름은 n, 몸통이 n + n인 함수를 정의한다. 이 함수가 호출되면 n이 인자의 값을 나타내는 상황에서 n + n을 계산하고 그 결과가 함수 호출의 결과가 된다. 따라서 double은 정수 하나를 인자로 받아서 그 정수를 두 배 한 값을 결과로 내는 함수이다.
x(e1, …, en)
x는 호출하고자 하는 함수의 이름으로, 이전에 정의된 함수 중 이름이 x인 함수가 이 식에 의해 호출되는 함수이다. e1부터 en은 함수 호출 시 함수에 전달할 인자의 값을 결정한다. 위의 예시에서 정의한 double이라는 함수를 호출하려면 double(1) 같은 식을 쓰면 된다. 1을 계산한 결과는 1이므로 인자의 값이 1이고, double(1)의 값은 1의 두 배인 2이다. 인자의 값을 정하는 데는 아무 식이나 사용할 수 있으므로 double(1 + 2)나 double(double(4))와 같은 식도 생각할 수 있다. 6과 16이 각 식의 결과이다.
함수를 호출한다는 표현을 사용하는 대신 함수를 적용(application)한다고 말하는 경우도 있다. 예를 들면, double(1)이라는 식을 1을 인자로 하여 double을 호출했다고 설명할 수도 있지만, double을 1에 적용했다고 설명할 수도 있다. 두 가지 설명은 완전히 같다. 인자의 값을 신경 쓰지 않고 부르는 함수에만 관심이 있는 경우에는 함수를 호출한다는 표현이 자연스럽지만, 인자의 값도 언급하려는 경우에는 함수를 인자에 적용한다는 표현이 더 간결하다. 이 책에서는 함수 호출과 함수 적용이란 표현을 모두 사용하며, 문맥에 따라 둘 중 더 간결한 쪽을 선택할 것이다.
몸통에서 자기 자신을 호출하는 함수를 재귀 함수(recursive function)라고 부른다. 어떤 양의 정수를 인자로 받아서 1부터 그 정수까지의 합을 결과로 내는 함수는 다음과 같이 재귀 함수 형태로 정의될 수 있다.
def sum(n) = if (n < 1) 0 (sum(n - 1) + n)
만약 n이 1 미만이면 0이 결과이다. 한편, n이 1 이상인 경우에는 1부터 n보다 1 작은 수까지의 합을 구한 뒤 그 값에 n을 더해서 결과를 얻는다. 그 값은 당연히 1부터 n까지의 합과 동일하다.
함수형 언어와 함수형 언어의 관점을 일부 적용한 최근의 언어에서는 함수를 값으로 사용할 수 있다. 함수가 값이라는 것은 함수를 정수나 불 같은 다른 종류의 값들과 비슷한 방법으로 사용할 수 있음을 뜻한다. 즉, 함수를 변수에 저장하거나 함수에 인자로 전달하거나 함수의 결괏값으로 삼을 수 있다.
다음은 함수를 변수에 저장하는 예시이다.
def double(n) = n + n;
let f = double;
f(2)
double이라는 함수가 정의된 상황에서 let f = double이라고 변수를 정의하면 f의 값은 인자를 두 배한 값을 결과로 내는 함수이다. 따라서 f(2)의 결과는 4이다.
함수를 인자로 받거나 결과로 내는 예시도 보자.
def twice(f) = (
def g(a) = f(f(a));
g
);
let quadruple = twice(double);
quadruple(3)
twice는 어떤 함수 f를 인자로 받은 뒤 f를 사용해 새로운 함수 g를 정의한다. g가 하는 일은 어떤 인자 a를 받아 a에 f를 두 번 적용하는 것이다. twice의 결과는 함수 g 그 자체이다. twice를 double에 적용하면 새로운 함수가 만들어진다. 이 함수가 하는 일은 받은 인자에 double을 두 번 적용하는 것이다. twice(double)을 quadruple이라는 변수에 저장한 뒤 quadruple(3)을 계산한 결과는 3의 두 배의 두 배인 12이다.
함수가 값이라면 호출할 함수를 반드시 이름으로 정할 필요는 없다. 임의의 식을 계산한 결과가 함수일 수 있으니 식을 계산해 얻은 함수를 호출할 수 있게 하는 것이 당연하다.
e0(e1, …, en)
예를 들면 (if false double sum)(4)는 if false double sum을 계산해 나온 함수를 호출한다. if false double sum의 결과가 sum이므로 전체 식의 결과는 10이다. 또, 앞의 예시에서 quadruple이라는 변수에 twice(double)을 저장한 뒤 quadruple을 3에 적용했는데, 이 대신 twice(double)(3)이라고 써도 같은 결과를 얻을 수 있다.
함수가 값인 언어에는 익명 함수(anonymous function)라는 개념도 있는 것이 일반적이다. 익명 함수는 단어 그대로 이름이 없는 함수로, 매개변수와 몸통만으로 이루어진다.
(x1, …, xn) => e
재귀 함수가 아닌 이상, 함수의 이름은 함수를 지칭하는 데 필요할 뿐, 함수의 동작에는 영향을 주지 않는다. 매개변수와 몸통만으로 충분히 함수의 동작을 표현할 수 있으므로 익명 함수는 다른 이름 붙은 함수만큼이나 제대로 된 함수이다. 앞서 정의한 double이라는 함수를 익명 함수 형태로 다시 만들면 (n) => n + n이 된다.
let double = (n) => n + n;
double(1)
함수가 값이므로 익명 함수 형태로 만든 함수를 변수에 저장한 뒤 처음부터 이름이 있는 것처럼 사용할 수 있다. 반대로, ((n) => n + n)(1)처럼 이름을 붙이지 않고 바로 인자에 적용할 수도 있다. 익명 함수를 사용하여 앞서 정의했던 twice를 더 간결하게 만드는 것도 가능하다.
def twice(f) = (a) => f(f(a))