본문 바로가기

C#

LINQ(1) - 함수형 프로그래밍에 대해서

함수형 프로그래밍은 프로그래밍을 하다 보면 누구나 한번쯤은 빠져들게 되는 주제이다. 

이 글에서는 C#의 가장 중요한 기능 중 하나인 LINQ에 대해 설명하기 전, 함수형 프로그래밍에 대해 간략하게 설명한다.


 

명령형 프로그래밍vs선언형 프로그래밍

 

함수형 프로그래밍에 대해 생각하기 전에 먼저 명령형imperative 프로그래밍과 선언형declarative 프로그래밍의 개념부터 알아야 한다.

 

명령형 프로그래밍이란 어떻게 해야 하는지를 서술하는 것이다. 반면에 선언형 프로그래밍은 내가 무엇을 할지를 서술하는 것이다. 예를 들어 지금 Q라는 글자를 타이핑하려고 해보자.

 

명령형 프로그래밍의 관점에서 Q를 타이핑하는 방법은 다음과 같다:

  1. 새끼손가락을 왼쪽으로 0.5cm, 앞쪽으로 0.5cm 이동한다.
  2. 새끼손가락 끝에 힘을 주어 아래로 내린다

한편 선언형 프로그래밍의 관점에서 Q를 타이핑하는 방법은 다음과 같다 :

  1. Q를 누른다.

 

그런데 어떻게 "Q를 누른다" 라는 명령만 내렸는데 Q를 찾아서 적합한 손가락을 가져가 누를 수 있을까? 당연한 이야기지만 그런 로직은 전부 다 "Q를 누른다" 는 행위 안에 이미 짜여져 있다. 다른 방식으로 표현하면, "Q를 누른다"라는 단순한 명령은 키보드의 수많은 키보드 중 Q를 찾아서 손가락을 가져다 대고 누른다는 복잡한 동작을 추상화한 것이다.

 

마치 클래스가 절차적 변수와 프로시저의 모음을 추상화해놓은것처럼, 선언형 프로그래밍도 명령형 프로그래밍의 명령들을 추상화 해 놓은 것이라고 볼 수 있다. 

 

이제부터 함수형 프로그래밍에 필수적인 몇 가지 개념을 소개한다.


일급 함수와 고차 함수란?

 

사실 이 두가지는 C#을 약간이라도 다뤄 봤으면 모두가 사용해봤을 기능이라서 C# 사용자에게는 오히려 이런 걸 굳이 설명해야 하나 싶은 개념이다.

 

일급 함수란 값으로 다뤄질 수 있는 함수이다. 값으로 다뤄진다는 의미는 저장하고, 대입하고, 비교하는 등의 행위를 할 수 있다는 뜻이다. 고차 함수는 함수를 매개변수 혹은 반환값으로 사용할 수 있는 함수이다.

 

C#에선 delegate가 존재하기 때문에 delegate를 사용하면 곧 일급 함수와 고차 함수를 사용하는 것이라고 볼 수 있다. delegate의 인스턴스 자체가 일급 함수이고, 매개변수나 반환값으로 delegate를 사용하는 함수가 고차 함수이다.

Func<int, int> addOne = (v) => v + 1;               // 함수를 값처럼 다루고 있다.
int Apply(int v, Func<int, int> func) => func(v);   // 함수를 매개변수로 받고 있다.

하나 더, 순수 함수란?

개념 한가지가 더 필요하다.

순수 함수란 쉽게 설명하면 함수 외부를 참조하지 않고, 함수 외부를 변경하지도 않는 함수이다.

C#에서는 간단히 static이라면, 그리고 그 안에서 다른 정적 필드에 접근하지 않는다면 순수 함수라고 볼 수 있다.

 


그래서 함수형 프로그래밍이 뭐고 왜 쓸까?

함수형 프로그래밍은 여러 종류의 선언형 프로그래밍 패러다임 중, 유일하게 널리 쓰이고 있는 패러다임이다.

 

함수형 프로그래밍은 명령문statement 대신 식expression을 사용한다. 일반적으로 문과 식은 반환값이 있냐 없냐로 구분하는데, 대체로는 맞지만 100% 그렇지는 않다. 왜 식을 사용하냐 하면, 명령문은 상태를 바꾸는 것이고, 식은 어떤 입력을 다른 출력으로 변환하는 것 , 즉 함수이기 때문이다. 다시 말하면, 함수형 프로그래밍은 상태를 바꾸는 대신 함수를 사용하는 것으로 논리를 만드는 프로그래밍 접근 방법이다. 

 

 

몇몇 엄격한 함수형 언어는 이와 같은 패러다임에 부응하기 위해 꼬리재귀Tail Recursion 등을 도입해서 루프 대신 재귀호출을 적극 장려하고, 바뀔 수 있는 변수 대신 불변값 개념을 도입하고, 여러 인자를 가지는 함수를 분해해서 한 개의 매개변수를 가지는 함수들의 연속 호출로 바꾸는 기능Currying 등을 제공한다.

 

하지만 C#은 명령형으로도, 함수형으로도 프로그래밍 할 수 있는 범용 언어를 표방하고 있기 때문에 위와 같은 기능들은 제공하지 않는다. 그러나 함수형 프로그래밍을 하기 위한 최소 조건이라고 할 수 있는 일급 함수First-class functoin와 고차 함수Higher-order function은 지원하기 때문에 함수형 프로그래밍의 방법론으로 접근하는 데에는 문제가 없다.

 

그렇다면 이런 함수형 프로그래밍을 왜 사용할까? 함수형 프로그래밍의 장단점은 다음과 같다.

 

  1. 추상화 단계가 높아서 읽고 쓰기가 편하다.
    • 명령 하나하나를 합해서 전체 의도를 파악해야 하는 명령형과 다르게 코드만 보고도 많은 정보를 빠르게 알 수 있다.
    • 지나치게 높은 추상화 단계가 겉핥기로만 알게 되는 현상을 유발하고 예기치 못한 문제 발생 시 수정이 어렵다.
  2. 상태를 극단적으로 줄이므로 훨씬 버그가 적고 유지보수가 편하다.
    • 특히 순수 함수를 사용하면 언제나 입력이 같으면 같은 출력이 나오기 때문에 재사용성이 높고 버그 발생 시 재현 및 디버그도 쉽다.
    • 가끔가다가, 특히 게임 프로그래밍에서는 생각보다 자주, 상태를 두면 쉽게 해결될 문제를 억지로 복잡하게 만드는 경향이 심하다. 게임이라는 애플리케이션 자체가 수많은 상태를 실시간으로 변화시키고 또 감시해야 하는 특성이 있기 때문이다.

종합

여기까지 설명하면 부품은 모두 마련되었다.

종합해보자.

  1. 함수형 프로그래밍은 상태와 명령문 대신 식을 사용한다.
  2. 값을 읽고 쓴다는 개념 대신 값을 변환한다는 개념으로 접근한다.
  3. 실제 프로그래밍에서는 일급 함수와 고차 함수를 이용하다.
  4. 순수 함수를 사용하면 좋다.

 

그런데 글 제목은 LINQ 설명처럼 써놓고 내용은 하나도 관련 없어보이는 함수형 프로그래밍 이야기만 했을까? 그건 LINQ가 함수형 프로그래밍의 원리에 따라 설계되었기 때문이다. 반대로 C#에서 함수형 프로그래밍의 방법론대로 코딩할 때 가장 편리한 방법은 LINQ를 사용하는 것이다.

 

다음 포스팅부터는 LINQ에 대해 좀 더 구체적인 내용을 살펴볼 것이다..