FC++로 함수형 프로그래밍을 시도하는 이유는 무엇인가?
함수형 프로그래밍에는 OOP 등의 다른 프로그래밍 패러다임에 비해 다음과 같은 장점이 있다.
- 코드의 간결함
- 부작용이 없는 프로그래밍(무한 set/get 루틴에서 글로벌/정적 변수를 조작하지 않음)
- 빠른 프로토타입
- FC++에는 Haskell 프로그래머가 무리 없이 옮겨올 수 있도록 지원하는 다양한 구문 및 라이브러리 함수가 있다.
C++에는 라이브러리를 사용하는 함수형 프로그래밍 구문이 없다. FC++는 기존 C++ 코드와 함께 플러그인할 수 있는 C++ 기반 함수형 프로그래밍 라이브러리의 가장 좋은 오픈 소스 구현이다. FC++는 C++로 함수형 대용량 동기 병렬 프로그래밍을 수행하기 위한 라이브러리인 BSFC++와 같은 프로젝트에서 사용되었다.
FC++는 SourceForge에서 다운로드할 수 있다(참고자료
참조). 설치된 압축 (.zip) 파일을 언팩하면 헤더 파일 콜렉션이 나타난다. 사용자 애플리케이션
소스에 prelude.h 헤더를 포함시키기만 하면 시작할 준비가 완료된다. Listing
1에서는 FC++ 코드를 사용하는 소스를 컴파일하는 방법을 보여 준다. 이 방법은 헤더 종속적
설치이며 다른 라이브러리가 포함되어 있지 않다.
Listing 1. FC++ 코드를 사용하는 소스 컴파일하기
g++ user_source1.cpp I<path to FC++ installation> |
참고: 이 기사의 모든 코드는 FC++ 1.5와 g++ 3.4.4를 사용하여 테스트되었다.
함수형 프로그래밍 패러다임에서는 함수가 다른 함수를 인수로 사용할 수 있다. 이에 반해
기본 버전의 C/C++에서는 그러한 구문이 허용되지 않는다. 이 문제점을 해결하기 위해 FC++ 함수는
특정 코드 규칙을 따르는 클래스의 인스턴스로 표현되며, 이를 위해 CFunType이
사용된다. C++ 함수 오브젝트의 특징은 클래스 정의에 operator( )가
있다는 것이다. Listing 2에 이에 대한 예제가 있다.
Listing 2. C++ 함수 오브젝트의 일반적인 사용
struct square {
int operator( ) (int x) { return x * x; }
};
square sqr1;
int result = sqr1(5);
|
Listing 2 구현의 문제점은 수학적인 용어로 sqr1의 함수 유형은
int —> int이지만 sqr1의 C++
유형은 struct square라는 것이다. FC++에서는 유형 시그니처 정보를 인코딩하는
데 사용되는 CFunType를 사용한다. CFunType의
마지막 인수는 함수의 리턴 유형이며 나머지 인수는 함수 프로토타입에 표시되는 순서와 동일한 순서의
입력 유형 정보이다. Listing 3에서는 CFunType을 사용하는
square를 보여 준다.
Listing 3.
CFunType을 사용하여 사각형 조작을 위한 함수 시그니처 인코딩하기
#include prelude.h
struct square : public CFunType<int, int> {
int operator( ) (int x) { return x * x; }
};
square sqr1;
int result = sqr1(5);
|
Listing 4는 정수를 목록에 삽입하고 업데이트된 목록을 리턴하는 또 다른 예제이다.
Listing 4.
CFunType을 사용하여 목록 조작을 위한 함수 시그니처 인코딩하기
#include prelude.h
struct Insert : public CFunType<int,List<int>,List<int> > {
List<int> operator()( int x, const List<int>& l ) const {
// code for determining where to insert the data goes here
}
|
참고: Listing 4의 List 데이터 유형은 미리 정의된 FC++ 유형이며, 이에 대해서는 이 기사의 뒷부분에서 설명한다.
함수에서 함수를 입력 인수로 사용하려면 함수를 오브젝트로 변환해야 한다. FC++는 CFunType을
기반으로 빌드된 클래스의 FunN 카테고리와 실제로 변환을 수행하는 ptr_to_fun
루틴을 정의한다. Listing 5를 살펴보자.
Listing 5.
ptr_to_fun을 사용하여 함수를 FC++ 함수 오브젝트로 변환하기
int multiply(int m, int n) { return m*n; }
Fun2<int, int, int> mult1 = ptr_to_fun (&multiply);
int result = mult1(8, 9);
// result equals 72
|
CFunType에서처럼 Fun2에 대한
시그니처는 이 오브젝트가 두 개의 정수 입력을 사용하고 정수를 리턴한다는 것을 의미한다. 이와
마찬가지로 Fun3<int, double, double, string>를 사용할 수
있다. 이 시그니처는 하나의 정수와 두 개의 double을 사용하고 문자열을 리턴하는 함수를 나타낸다.
목록 조작은 함수형 프로그래밍의 핵심이다. FC++에는 STL(Standard Template Library) 목록과는 다른 고유한 목록 데이터 유형이 정의되어 있다. FC++ 목록에는 lazy 속성이 있다. FC++에서 무한 요소로 구성된 목록을 작성할 수 있다. 하지만 이러한 목록은 필요한 경우에만 평가된다. Listing 6의 예제를 통해 이 말의 의미를 알 수 있다.
Listing 6. lazy 목록 정의 및 사용하기
List<int> numbers = enumFrom (33); List<int> even_and_greater_than_33 = filter (even, numbers); assert (take(4, even_and_greater_than_33)) = list_with (34, 36, 38, 40); |
enumFrom, filter, even,
take 및 list_with 요소는 FC++의 미리 정의된 기능 중
일부이다. 위 Listing 6에서 enumFrom은 33부터 시작하는 무한 수 목록을 리턴한다. filter
루틴은 33보다 크거나 같은 수로 구성된 또 하나의 무한 목록을 리턴한다. 마지막으로 take
루틴은 이 목록의 처음 네 개의 요소를 실제로 추출한다. 지금까지 살펴본 어느 목록에서도 무한 수 목록을 저장하지 않으며,
필요한 경우에만 엄격하게 평가가 수행된다.
표 1에서는 FC++에서 목록과 함께 사용되는 일반적인 몇 가지 함수를 설명한다.
표 1. FC++와 함께 사용되는 함수
| 함수 | 설명 |
|---|---|
head(<list>) | 목록의 첫 번째 요소를 리턴한다. |
tail(<list>) | 첫 번째 요소를 제외하고 <list>와 동일한 요소로 구성된 목록을 리턴한다. |
cons(<element >, <list>) | 목록의 맨 앞에 <element>가 추가된 목록을 리턴한다. |
NIL | 빈 목록을 의미한다. |
list_with(<element1, element2>,
, <elementN>) | N개의 요소로 구성된 목록을 작성한다. |
enumFrom(<element1>) | element1로 시작하는 무한 목록을 작성한다. |
compose(<func1>, <func2>) | Compose(f, g)는 f(g(x))를 의미하며 여기서, f(x)와 g(x)는 두 개의 함수이다. |
filter(<func1>, <list>) | <func1> 함수를 사용하여 필터링된 <list>의 요소 목록을 리턴한다. |
take(<N>, <list>) | <list>의 처음 N개의 요소로 구성된 목록을 리턴한다. |
map(<function>, <list>) | 첫 번째 <function> 함수를 첫 번째 <list>의 각 요소에 적용한다. |
Listing 7은 목록의 내용을 작성 및 표시하는 방법을 보여 주는 또 하나의 예제이다.
Listing 7. 목록 작성, 내용 확인 및 데이터 표시
#include <iostream>
#include prelude.h
int main( )
{
int x=1, y=2, z=3;
List<int> li = cons(x,cons(y,cons(z,NIL)));
// head also removes the 1st element from the list
assert( head(li) == 1 );
// tail returns whatever is left of in the list, and list_with is
// used to define small sized list
assert( tail(li) == list_with(2,3) );
while( li ) {
std::cout << li.head() << " ";
li = li.tail();
}
return 0;
}
|
참고: li 목록을 작성할 때 cons 루틴이
목록의 앞쪽에 요소를 추가한다. 즉, 최종 목록을 작성하기 위해 z, y 및 x 순으로 추가된다.
FC++ 1.5에는 List 데이터 구조체의 추가 변형인 OddList가
있으며 이 구조체는 list.h에 정의되어 있다. OddList는 List와
동일한 인터페이스를 가지고 있지만 더 빠르다. List에 대해 작동하는 모든 FC++ 루틴은
OddList에 대해서도 작동한다. OddList는 목록의 다음 노드를
캐싱하기 때문에 빠르게 수행된다. Listing 8에서는 OddList의 사용과
관련된 몇 가지 자세한 특성을 보여 준다.
Listing 8.
OddList 사용과 관련된 자세한 특성OddList<int> odd1 = enumFrom (1); List<int> list1 = odd1.tail ( ); // always returns List<int>!! OddList<int> odd2 = enumFrom (1); List<int> list2 = odd2.delay ( ); // create a List<int> with same data as odd2 List<int> list3 = enumFrom (1); OddList<int> odd3 = list3.force ( ); // creates an OddList<int> with same data as list3 |
OddList는 List용으로 존재하는
STL 스타일 반복자를 지원하지 않는다. OddList 구현에 대한 자세한
정보는 참고자료를 확인한다.
Listing 6에서 고유 필터를 작성하려면(예를 들어, 100으로
나눌 수 있고 33보다 큰 모든 수) 고유 필터 함수를 정의한 다음 ptr_to_fun을
호출하여 함수 오브젝트로 변환하면 된다. Listing 9에서 그 방법을 보여 준다.
Listing 9.
CFunType을 사용하여 목록 조작을 위한 함수 시그니처 인코딩하기
bool div_by_100 (int n) {
return n % 100 ? false : true;
}
List<int> num = enumFrom(34);
List<int> my_nums = filter( ptr_to_fun(&div_by_100), num);
|
FC++ List 및 filter는 기본적으로
완전한 제네릭이므로 모든 데이터 유형을 수용할 수 있다.
이제 커링(currying)과 컴포지션이라는 두 가지 기본적인 함수 기술을 살펴보자.
커링은 일부 함수 인수의 서브세트를 고정된 값에 바인드한 후 새 함수를 작성하는
함수형 프로그래밍 기술이다. Listing 10은 f
함수를 커링하는 예제이다.
Listing 10. 커링을 사용하여 새 함수 작성하기
int multiply(int m, int n) { return m * n; }
Fun2<int, int, int> f2 = ptr_to_fun (&multiply);
Fun1<int, int> f1 = curry2 (f2, 9);
std::cout << f1(4) << std::endl; // equivalent to multiply(9, 4)
Fun1<int, int> f1_implicit = f2(9);
std::cout << f1_implicit(4) << std::endl; // same as f1(4)
|
미리 정의된 curry2 루틴은 f2의
첫 번째 인수를 9에 바인드한다. FC++ 1.5에는 처음 N개의
인수를 특정 값에 고정하는 curry1, curry2
및 curry3 연산자가 있다. 또한 FC++에는 기존 함수의 특정 인수에 값을
미리 지정하여 새 함수를 정의할 수 있는 바인드 루틴이 정의되어 있다. 예를 들어, bind2and3of3 (f, 8, 9)는
f(x, 8, 9)에 해당하며 여기서, f(x, y, z)는 입력이 3개인 함수이다. 그리고
인수를 지정하는 또 하나 흥미로운 방법으로 밑줄(_)을 사용하는 방법이 있다. 예를
들어, greater (_, 10)는 f(x) = (x > 10)와 같다. greater는 FC++에 미리
정의되어 있다. Listing 11에서는 몇 가지 추가 커링 예제를 보여 준다.
Listing 11. 추가 커링 예제
List<int> integers = enumFrom (1); List<int> int_gt_100 = filter(greater(_, 100), integers); // This list will add 3 to all elements of integers. List<int> plus_3 = map (plus(3), integers); |
Listing 12에서는 숫자 자체를 포함한 숫자의 모든 인수를 표시하는 코드 스니펫을 보여 준다.
Listing 12. 숫자의 모든 인수 표시하기
#include "prelude.h"
using namespace fcpp;
#include <iostream>
using namespace std;
bool divisible( int x, int y ) { return x%y==0; }
struct Factors : public CFunType<int,OddList<int> > {
OddList<int> operator()( int x ) const {
return filter( curry2(ptr_to_fun(&divisible),x), enumFromTo(1,x) );
}
} factors;
int main()
{
OddList<int> odd = factors(20);
while (odd) {
cout << head(odd) << endl;
odd = tail(odd);
}
return 0;
}
|
Listing 12를 이해하기 위해 가장 중요한 부분은 return filter( curry2(divisible,x), enumFromTo(1,x) );
행이다. 여기에서는 20을 완전히 나누는 모든 숫자로 구성된 최종 목록을 작성하기 위해 enumFrom(1, 20)에서
리턴된 목록에 적용할 필터를 작성하고 있다. curry2 루틴은 20을 divisible 함수의
첫 번째 인수에 바인드한다. ptr_to_fun은 divisible 함수를 curry2에
인수로 전달할 수 있는 함수 오브젝트로 변환한다.
함수형 프로그래밍은 기존 코드를 결합하여 새로운 기능을 작성한다. compose()
연산자는 h(x) = f(g(x))와 같이 두 개의 단항 함수인 f(x)와 g(x)를
작성하여 새 함수 h(x)를 생성한다. 예를 들어, 목록에 대한 compose(head, tail)은
목록의 두 번째 요소를 리턴한다. 이는 함수형 코딩에 적합한 방법이며, g(x)가 f(x)의
인수로 사용된다. "Functional Programming with the FC++ Library"(참고자료 참조)에서 가져온 Listing
13은 컴포지션을 사용하는 예제이다.
Listing 13.
compose 및 tail을
사용하여 목록의 두 번째 요소 가져오기std::string s=foo, t=bar, u=qux; List<std::string> ls = cons(s, cons(t, cons(u, NIL))); ls = compose(tail, tail) (ls); // tail(tail(ls)); assert (head(ls) == qux); // s, t are removed |
Listing 14는 목록의 모든 요소를 2씩 증가시키는 또 하나의 예제이다.
Listing 14.
compose를 사용하여 목록 요소 증가시키기List<int> integers = enumFrom (1); map (compose(inc, inc), integers); // this modifies integers to an infinite list [3, 4, 5 ...] |
함수형 프로그래밍에 대한 논의에서는 람다 함수를 언급하지 않을 수 없다. 람다 추상화는
익명 함수를 정의하는 데 사용된다. 이 방법은 간단한 코드에서 별도의 함수를 정의하지 않으려는
경우에 유용하다. 코드에서 람다 기능을 사용하려면 FCPP_ENABLE_LAMBDA
매크로를 정의해야 한다. Listing 15에서는 기존 코드를 활용하여 새로운 수학
및 논리 함수를 간략하게 정의한다. factorial을 정의하는 방법을 자세히
살펴보기 바란다.
Listing 15. 람다 함수 정의하기
// a new function where f(x) = 3*x+1 lambda(X)[ plus[multiplies[3,X],1] ] // a new function where f(x) = x! (factorial x) lambda(X)[ l_if[equal[X,0],1,multiplies[X,SELF[minus[X,1]]]] ] |
Listing 15는 그 자체로 쉽게 이해할 수 있다. plus, multiplies 등의
루틴은 FC++ 라이브러리의 일부로 정의되어 있으며 개발자는 lambda 연산자를 사용하여 기존 코드를 기반으로
새 함수를 작성할 수 있다.
FC++에서 제공하는 기능은 다음과 같다.
- 함수형 프로그래밍 요구를 충족하기 위해 쉽게 확장할 수 있는
CFunType유형의 오브젝트 - 잠재적으로 무한 시퀀스를 보유할 수 있는 lazy 목록의 구현
head,tail,map,filter,ptr_to_fun등의 여러 함수형 프로그래밍 연산자- 커링 연산자,
lambda또는compose를 사용하여 기존 함수를 기반으로 새 함수를 작성할 수 있는 기능
FC++의 단점이라면 헤더에 정의된 함수를 설명하는 표준화된 문서가 없다는 점일 것이다. 이 기사에서는
compose, curry, bind, take, map, ptr_to_fun
및 filter 등의 가장 유용한 함수를 살펴보았다.
교육
-
Wikipedia에서 초보자를 대상으로 하는 Functional
programming에 대한 흥미로운 소개를 볼 수 있다.
-
Getting started with FC++에서는
C++에 익숙하지만 함수형 프로그래밍에는 익숙하지 않은 독자를 위한 비공식 문서를 제공한다.
-
자세한 전체 개요를 보려면 Brian McNamara 및 Yannis Smaragdakis가 집필한 "Functional
Programming with the FC++ Library"를 읽어보자.
-
OddList구현에 대한 자세한 정보를 보려면 Georgia College of Tech Computing 웹 사이트의 FC++ lazy list implementation을 읽어보자. -
Currying in FC++에서 커링에 대한 자세한 정보를 볼 수 있다.
-
람다 기능에 대해 자세히 알아보려면 FC++ lambda를 읽어보자.
- developerWorks
기술 행사 및 웹 캐스트를 통해 다양한 IBM 제품 및 IT 산업 주제에 대한 최신 정보를 얻을 수 있다.
- 무료 developerWorks Live!
briefing을 통해 최신 IBM 제품 및 도구에 대한 정보뿐만 아니라 IT 업계의 최신 경향까지도 빠르게 확인할 수 있다.
- developerWorks
on-demand demos에서는 입문자를 위한 제품 설치 및 설정부터 숙련된 개발자를 위한 고급 기능까지 망라된 다양한 데모를 제공한다.
제품 및 기술 얻기
-
FC++를 다운로드할 수 있다.
-
FC++ client를 다운로드하여 작업을 자세히 살펴보자.
-
자신에게 가장한 적합한 방법으로 IBM
제품을 평가해 보자. 시험판 제품을 다운로드하거나, 온라인으로 제품을 사용해 보거나, 클라우드 환경에서 제품을 사용하거나,
SOA Sandbox에서
SOA(Service Oriented Architecture)를 효과적으로 구현하는 방법을 배울 수 있다.
토론
- My developerWorks 커뮤니티에 참여하자.
개발자가 운영하고 있는 블로그, 포럼, 그룹 및 위키를 살펴보면서 다른 developerWorks 사용자와 의견을 나눌 수 있다.
- Twitter의 developerWorks 페이지를 살펴보자.
- My developerWorks 커뮤니티에 참여하자.
-
AIX 및 UNIX® 포럼에 참여하자.
- AIX Forum
- AIX Forum for developers
- Cluster Systems Management
- Performance Tools Forum
- Virtualization Forum
- 기타 AIX and UNIX Forums