61. 함수와 함수형 프로그래밍 소개

  • 9 minutes to read

C#은 본래 OOP(개체 지향 프로그래밍) 기반의 프로그래밍 언어입니다. 하지만 C#은 절차 지향과 더불어 함수형 프로그래밍 스타일을 지원합니다. 함수형 프로그래밍은 함수 형태로 계산을 진행하고 변경되는 변수의 사용을 멀리하는 스타일을 말합니다.이번에는 함수형 프로그래밍의 몇 가지 특징을 C#으로 표현해보겠습니다.

> // 함수형 프로그래밍: 함수 형태로 계산을 진행하고 변경되는 변수 사용을 멀리하는 스타일

61.1. 함수형 프로그래밍

프로그래밍을 작성할 때 사용될 수 있는 프로그래밍 패러다임에는 절차 지향, 개체 지향, 함수형, 3가지가 가장 일반적입니다. 이 강의에서도 31강의 알고리즘과 절차 지향 프로그래밍, 47강의 개체와 개체 지향 프로그래밍 그리고 이번 강의 함수와 함수형 프로그래밍에서 3가지 체계에 대해서 정리하고 있습니다.

함수형 프로그래밍은 상태값을 가지지 않는 함수들을 연속해서 호출해서 사용하는 개발 스타일로 볼 수 있습니다. 이미 우리가 사용해 온 메서드 체이닝을 사용하는 LINQ의 확장 메서드들이 함수형 프로그래밍의 전형적인 예입니다.

61.2. 문(Statement)과 식(Expression)

C#의 문법은 (statement)과 (expression)으로 표현됩니다. 가장 간단한 차이는 '문은 여러 줄로 표현하고, 식은 한 줄로 표현할 수 있다'는 점입니다. 참고로 함수형 프로그래밍에서는 문 대신 식을 사용하는 것이 좋습니다. 자세한 설명은 어렵지만 문은 파생 작업(부작용, side effect)이 발생할 가능성이 높기에 식을 사용하여 결괏값을 바로 가져오는 형태로 프로그래밍하기를 권장합니다.   다음 예제는 동일한 기능을 하는 함수를 만들 때 [1]번은 문을 사용하여 만들고 [2]번은 식을 사용하여 만드는 것을 비교해서 보여줍니다.

코드: LicenseResult.cs

> //[1] 문(Statement)을 사용하여 시험 결과 판정
> static string GetResultWithStatement(int score)
. {
.     string r;
.     if (score >= 60)
.     {
.         r = "합격";
.     }
.     else
.     {
.         r = "불합격";
.     }
.     return r; // 변하는 값 반환
. }
> 
> //[A] 문 호출
> Console.WriteLine(GetResultWithStatement(60)); // 합격
합격
> 
> //[2] 식(Expression)을 사용하여 시험 결과 판정
> static string GetResultWithExpression(int score) =>
.     score >= 60 ? "합격" : "불합격"; // 변하지 않는 값 반환
> 
> //[B] 식 호출
> Console.WriteLine(GetResultWithExpression(60)); // 합격
합격

문과 식을 구분하기 위한 예제이기에 어느 게 더 좋은 방식이라고 하지 않습니다. 하지만 최근 C#의 경향은 문보다는 식을 사용하여 코드를 간결하게 유지하는 형태가 많아져서 이번 예제를 소개했습니다.

61.2.1. 변경할 수 없는(Immutable)

프로그래밍에서는 영어 단어로 Immutable 또는 Immutability가 자주 나옵니다. 우리 말로 해석해보면 '변경할 수 없는'입니다. 만약 함수와 같은 프로그램 코드 내에서 변경할 수 있는 값으로 무언가를 관리하면 중간에 잘못된 값으로 변경될 수 있는 파생 작업(부작용)이 발생할 수 있습니다. 대신에 변경할 수 없는 변수 또는 값으로 결과가 주어지는 환경에서는 부작용이 발생하지 않습니다. 그렇기에 문보다는 식의 사용을 권장하는 것입니다.

61.3. 고차 함수(Higher-Order Function)

함수 자체를 매개 변수 또는 반환값으로 가지는 함수를 고차 함수(Higher-Order Function)라고 합니다. 고차 함수는 함수 자체를 데이터로 봅니다.

다음 이어지는 4개의 함수 예제를 보고 고차 함수의 4가지 유형을 정리하겠습니다. 따로 추가 설명은 하지 않고, 함수 생성 및 호출의 단계로 한번씩 실행해보는 시간을 갖도록 하겠습니다.

코드: HigherOrderFunction.cs

> //[?] 고차 함수(Higher-Order Function): 함수 자체를 매개 변수 또는 반환값으로 가지는 함수
> //[1] 매개 변수가 Action<T>
> static void FunctionParameterWithAction(Action<string> action, string message)
. {
.     action(message);
. }
> 
> //[A] Action<T> 매개 변수 전달: 문자열을 받아 출력하는 함수 정의
> Action<string> action = message => Console.WriteLine(message);
> FunctionParameterWithAction(action, "고차 함수: 매개 변수");
고차 함수: 매개 변수
> 
> //[2] 매개 변수가 Func<T>
> static void FunctionParameterWithFunc(Func<int, int> func, int number)
. {
.     int result = func(number);
.     Console.WriteLine($"{number} * {number} = {result}");
. }
> 
> //[B] Func<T> 매개 변수 전달: 정수 값을 받아 두 번 곱한 후 다시 정수 값 반환
> Func<int, int> func = x => x * x;
> FunctionParameterWithFunc(func, 3);
3 * 3 = 9
> 
> //[3] 반환값이 Action<T>
> static Action<string> FunctionReturnValueWithAction() =>
.     msg => Console.WriteLine($"{msg}");
> 
> //[C] Action<T> 반환값 
> FunctionReturnValueWithAction()("고차 함수: 반환값");
고차 함수: 반환값
> 
> //[4] 반환값이 Func<T>
> static Func<int, int> FunctionReturnValueWithFunc() => x => x * x;
> 
> //[D] Func<T> 반환값
> int number = 3;
> int result = FunctionReturnValueWithFunc()(number);
> Console.WriteLine($"{number} * {number} = {result}"); // 9 
3 * 3 = 9

61.3.1. LINQ의 Aggregate 메서드 사용하여 최댓값, 최솟값 구하기

LINQ에서 제공하는 Aggregate 확장 메서드는 매개 변수로 Func<T> 형태의 대리자를 받는 대표적인 메서드입니다. 다음 코드처럼 Aggregate 확장 메서드에 람다 식을 전달하여 최댓값 또는 최솟값을 구할 수 있습니다.

코드: AggregateDemo.cs

> int[] numbers = { 1, 2, 3, 4, 5 };
> 
> //[1] Aggregate 확장 메서드로 최댓값 구하기
> int max = numbers.Aggregate((f, s) => f > s ? f : s);
> Console.WriteLine(max);
5
> 
> //[2] Aggregate 확장 메서드로 최솟값 구하기
> int min = numbers.Aggregate((c, n) => c < n ? c : n);
> Console.WriteLine(min);
1

Aggregate() 확장 메서드는 이외에도 다양하게 접근될 수 있으니, 관심 있는 독자분들은 Microsoft Learn의 자료를 참고하세요.

61.4. LINQ로 함수형 프로그래밍 스타일 구현

이미 우리는 LINQ를 배웠고 활용하고 있습니다. 사실, C#에서의 함수형 프로그래밍 스타일은 LINQ를 통해서 구현할 수 있습니다. LINQ의 파이프라인(메서드 체이닝)을 사용하면 하나의 코드 흐름으로 원하는 결과를 얻어낼 수 있습니다.

다음 샘플 코드처럼 x 개체에 F(), G(), H(), I(), Run() 메서드를 파이프라인 형태로 호출하는 형태로 함수형 프로그래밍 스타일을 표현할 수 있습니다.

x.F().G(1).H(2, 3).I(4, 5, 6).Run()

61.5. 장 요약

C#은 제네릭, 확장 메서드, LINQ의 제공으로 함수형 프로그래밍 스타일을 쉽게 표현할 수 있습니다. 절차 지향형, 개체 지향형, 함수형의 3가지 스타일을 지니는 C#을 사용하면 프로그래머에게 여러 가지 도구를 제공하여 편리합니다.

VisualAcademy Docs의 모든 콘텐츠, 이미지, 동영상의 저작권은 박용준에게 있습니다. 저작권법에 의해 보호를 받는 저작물이므로 무단 전재와 복제를 금합니다. 사이트의 콘텐츠를 복제하여 블로그, 웹사이트 등에 게시할 수 없습니다. 단, 링크와 SNS 공유, Youtube 동영상 공유는 허용합니다. www.VisualAcademy.com
박용준 강사의 모든 동영상 강의는 데브렉에서 독점으로 제공됩니다. www.devlec.com