First-Class Procedure in C

sicp2014 스터디에 참여하면서 first-class procedure (function)에 대해 공부하게 되었는데, 여기에 그 내용을 올린다. 먼저, SICP 책에서 first-class procedure는 다음 4가지로 정리된다.

정의 (SICP 1.3절 (pp.98))

  1. 변수의 값이 될 수 있다. 다시 말해, 이름이 붙을 수 있다.
  2. 프로시저 인자로 쓸 수 있다.
  3. 프로시저의 결과로 만들어질 수 있다.
  4. 데이터 구조 속에 집어 넣을 수 있다.

C Implementation

C 언어의 함수는 위 요구사항들을 어떻게 만족시킬 수 있을까? 함수 포인터가 알파요 오메가다.

변수의 값으로 사용

typedef int (*operator_f)(int, int);

int my_mul(int a, int b)
{
  return (a*b);
}

int main(void)
{
  operator_f mul = my_mul; // (1)
  int a = 4, b = 5;
  printf("%d x %d = %d\n", a, b, mul(a, b));
  return 0;
}

프로시저의 인자로 사용

typedef int (*operator_f)(int, int);

int my_mul(int a, int b)
{
  return (a*b);
}

int op(int a, int b, operator_f operation)
{
  return operation(a, b);
}

int main(void)
{
  int a = 4, b = 5;
  printf("%d x %d = %d\n", a, b, op(a, b, my_mul)); // (2)
  return 0;
}

프로시저의 결과로 받기

typedef int (*operator_f)(int, int);

int my_mul(int a, int b)
{
  return (a*b);
}

operator_f get_op_mul(void)
{
  return my_mul; // (3)
}

int main(void)
{
  int a = 4, b = 5;
  printf("%d x %d = %d\n", a, b, get_op_mul()(a, b));
  return 0;
}

데이터 구조 속에 집어 넣기

typedef int (*operator_f)(int, int);

typedef struct {
  int a;
  int b;
  operator_f op;
} calc_t;

int my_mul(int a, int b)
{
  return (a*b);
}

int main(void)
{
  calc_t calc = { 4, 5, my_mul };
  printf("%d x %d = %d\n", calc.a, calc.b, calc.op(calc.a, calc.b));
  return 0;
}

기타

SICP에서 first-class procedure의 조건을 정의한 이후로, 함수형 언어에 대한 연구가 계속 이어져 오면서 몇 가지 조건들이 더 추가되었다.

  • 중첩된 합수 정의
  • 익명 함수
  • 클로저 (closure)

C 언어는 위 조건들을 충족하지 못한다. 최신 C 언어 표준인 C11에서도 포함되어 있지 않다.
다만 OS X의 clang에서는 block의 형태로 closure를 지원한다. 아래 코드는 영문판 wikipedia에 있는 것이다.

#include <stdio.h>
#include <Block.h>

typedef int (^IntBlock)();

IntBlock MakeCounter(int start, int increment)
{
  __block int i = start;

  return Block_copy(^{
      int ret = i;
      i += increment;
      return ret;
    });
}

int main(void)
{
  IntBlock mycounter = MakeCounter(5, 2);
  printf("1st call: %d\n", mycounter());
  printf("2nd call: %d\n", mycounter());
  printf("3rd call: %d\n", mycounter());

  Block_release(mycounter);

  return 0;
}