함수 프로그래밍은 함수 및 변경할 수 없는 데이터의 사용을 강조하는 프로그래밍 스타일입니다. 형식화된 함수 프로그래밍은 기능 프로그래밍을 F#과 같은 정적 형식과 결합하는 경우입니다. 일반적으로 기능 프로그래밍에서는 다음과 같은 개념이 강조됩니다.
- 사용하는 기본 구문으로서의 함수
- 문장 대신 식
- 변수에 대한 변경할 수 없는 값
- 명령적 프로그래밍에 대한 선언적 프로그래밍
이 시리즈 전체에서는 F#을 사용하여 기능 프로그래밍의 개념과 패턴을 살펴봅니다. 그 과정에서 F#도 배우게 됩니다.
용어
다른 프로그래밍 패러다임과 마찬가지로 기능 프로그래밍에는 결국 학습해야 하는 어휘가 함께 제공됩니다. 다음은 항상 표시되는 몇 가지 일반적인 용어입니다.
- 함수 - 함수는 입력이 제공될 때 출력을 생성하는 구문입니다. 좀 더 공식적으로는 한 집합의 항목을 다른 집합으로 매핑 합니다. 이러한 형식주의는 특히 데이터 컬렉션에서 작동하는 함수를 사용하는 경우 여러 가지 면에서 구체적으로 해제됩니다. 함수 프로그래밍에서 가장 기본적이고 중요한 개념입니다.
- 식 - 식은 값을 생성하는 코드의 구문입니다. F#에서 이 값은 바인딩되거나 명시적으로 무시되어야 합니다. 식은 간단하게 함수 호출로 대체할 수 있습니다.
- 순도 - 순도는 함수의 특성으로, 동일한 인수의 경우 반환 값이 항상 같으며, 평가에 부작용이 없습니다. 순수 함수는 전적으로 인수에 따라 달라집니다.
- 참조 투명도 - 참조 투명도는 식의 속성이므로 프로그램의 동작에 영향을 주지 않고 출력으로 바꿀 수 있습니다.
- 불변성 - 불변성은 값을 현재 위치에서 변경할 수 없음을 의미합니다. 이는 현재 위치에서 변경 될 수있는 변수와는 대조적입니다.
예시
다음 예제에서는 이러한 핵심 개념을 보여 줍니다.
기능
함수 프로그래밍에서 가장 일반적이고 기본적인 구문은 함수입니다. 다음은 정수에 1을 추가하는 간단한 함수입니다.
let addOne x = x + 1
형식 서명은 다음과 같습니다.
val addOne: x:int -> int
서명은 "addOne는 int라는 이름의 x를 수락하고 int을 생성합니다"로 읽을 수 있습니다. 더 공식적으로는 addOne 정수 집합의 값을 정수 집합으로 매핑 하는 것입니다. 토큰은 -> 이 매핑을 의미합니다. F#에서는 일반적으로 함수 시그니처를 확인하여 함수 시그니처가 수행하는 작업을 파악할 수 있습니다.
그렇다면 서명이 중요한 이유는 무엇일까요? 형식화된 함수 프로그래밍에서 함수 구현은 실제 형식 서명보다 덜 중요한 경우가 많습니다.
addOne가 정수에 값 1을 추가하는 것은 런타임에서 흥미로운 부분이지만, 프로그램을 작성할 때 중요한 것은 이 함수가 int을(를) 받아들이고 반환한다는 사실이며, 이 점이 실제로 이 함수를 사용하는 방식에 영향을 미칩니다. 또한 형식 서명과 관련하여 이 함수를 올바르게 사용하면 함수 본문 addOne 내에서만 문제를 진단할 수 있습니다. 이것이 형식화된 함수 프로그래밍의 원동력입니다.
표현식
식은 값으로 계산되는 구문입니다. 동작을 수행하는 문과 달리 식은 값을 반환하는 작업을 수행하는 것으로 생각할 수 있습니다. 함수형 프로그래밍에서는 거의 항상 명령문 대신 표현식을 사용합니다.
이전 함수를 고려합니다 addOne.
addOne의 본문은 표현식입니다.
// 'x + 1' is an expression!
let addOne x = x + 1
함수의 결과 형식을 정의하는 식의 addOne 결과입니다. 예를 들어 이 함수를 구성하는 식은 다음과 같은 string다른 형식으로 변경될 수 있습니다.
let addOne x = x.ToString() + "1"
이제 함수의 서명은 다음과 같습니다.
val addOne: x:'a -> string
F#의 모든 형식에서 ToString()를 호출할 수 있으므로, x은 일반적으로 자동 일반화라고 불리는 제네릭 형식이 되었으며, 그 결과 형식은 string입니다.
표현식은 함수의 본문만이 아닙니다. 다른 곳에서 사용하는 값을 생성하는 식을 사용할 수 있습니다. 일반적인 것은 다음과 같습니다 if.
// Checks if 'x' is odd by using the mod operator
let isOdd x = x % 2 <> 0
let addOneIfOdd input =
let result =
if isOdd input then
input + 1
else
input
result
식은 if .라는 result값을 생성합니다.
result을(를) 완전히 생략하고, if 식을 addOneIfOdd 함수의 본문으로 만들 수 있습니다. 식에 대해 기억해야 할 중요한 점은 값을 생성한다는 것입니다.
반환할 항목이 없을 때 사용되는 특수 형식 unit이 있습니다. 예를 들어 다음 간단한 함수를 고려해 보세요.
let printString (str: string) =
printfn $"String is: {str}"
서명은 다음과 같습니다.
val printString: str:string -> unit
이 형식은 unit 반환되는 실제 값이 없음을 나타냅니다. 이 기능은 해당 작업의 결과로 반환할 값이 없음에도 불구하고 "작업을 수행"해야 하는 루틴이 있는 경우에 유용합니다.
이는 명령형 프로그래밍과 극명히 대조되며, 명령형 프로그래밍에서는 동일한 if 구문이 명령문으로 사용되고, 변수 변경을 통해 값을 생성하는 경우가 많습니다. 예를 들어 C#에서 코드는 다음과 같이 작성될 수 있습니다.
bool IsOdd(int x) => x % 2 != 0;
int AddOneIfOdd(int input)
{
var result = input;
if (IsOdd(input))
{
result = input + 1;
}
return result;
}
C# 및 기타 C 스타일 언어는 식 기반 조건부 프로그래밍을 허용하는 3항식을 지원한다는 점을 주목할 가치가 있습니다.
함수형 프로그래밍에서는 문을 사용하여 값을 변경하기는 드뭅니다. 일부 기능 언어는 문과 변형을 지원하지만 함수 프로그래밍에서 이러한 개념을 사용하는 것은 일반적이지 않습니다.
순수 함수
앞에서 설명한 것처럼 순수 함수는 다음과 같은 함수입니다.
- 항상 동일한 입력에 대해 동일한 값으로 평가합니다.
- 부작용이 없습니다.
이 컨텍스트에서 수학 함수를 생각하는 것이 유용합니다. 수학에서 함수는 인수에만 의존하며 부작용이 없습니다. 수학 함수 f(x) = x + 1에서 값 f(x) 은 값에 x만 따라 달라집니다. 함수 프로그래밍의 순수 함수도 동일합니다.
순수 함수를 작성할 때 함수는 인수에만 의존해야 하며 부작용을 초래하는 작업을 수행하지 않아야 합니다.
다음은 변경 가능한 전역 상태에 따라 달라지므로 순수하지 않은 함수의 예입니다.
let mutable value = 1
let addOneToValue x = x + value
함수는 addOneToValue 1과 다른 값을 가지도록 언제든지 변경할 수 있으므로 value 명확하게 불순물입니다. 전역 값에 따라 이러한 패턴은 기능 프로그래밍에서 피해야 합니다.
다음은 부작용을 수행하기 때문에 순수하지 않은 함수의 또 다른 예입니다.
let addOneToValue x =
printfn $"x is %d{x}"
x + 1
이 함수는 전역 값에 의존하지 않지만 프로그램의 출력에 값을 x 씁니다. 이 작업을 수행하는 데 본질적으로 잘못된 것은 없지만 함수가 순수하지 않다는 것을 의미합니다. 프로그램의 다른 부분이 출력 버퍼와 같은 프로그램 외부에 종속된 경우 이 함수를 호출하면 프로그램의 다른 부분에 영향을 줄 수 있습니다.
문을 제거하면 printfn 함수가 순수하게 됩니다.
let addOneToValue x = x + 1
비록 이 함수가 문을 포함한 이전 버전보다 본질적으로 printfn 나은 것은 아니지만, 이 함수가 수행하는 것은 오직 값을 반환한다는 것을 보장합니다. 이 함수를 여러 번 호출하면 동일한 결과가 생성됩니다. 값만 생성합니다. 순도에서 제공하는 예측 가능성은 많은 기능 프로그래머가 추구하는 것입니다.
불변성
마지막으로 형식화된 기능 프로그래밍의 가장 기본적인 개념 중 하나는 불변성입니다. F#에서 모든 값은 기본적으로 변경할 수 없습니다. 즉, 변경 가능한 것으로 명시적으로 표시하지 않는 한 현재 위치에서 변경될 수 없습니다.
실제로 변경할 수 없는 값을 사용한다는 것은 프로그래밍 방식을 "변경해야 합니다"에서 "새 값을 생성해야 합니다"로 변경한다는 것을 의미합니다.
예를 들어 값에 1을 추가하면 기존 값이 변경되지 않고 새 값이 생성됩니다.
let value = 1
let secondValue = value + 1
F#에서 다음 코드는 함수를 변경value 않고 대신 같음 검사를 수행합니다.
let value = 1
value = value + 1 // Produces a 'bool' value!
일부 기능 프로그래밍 언어는 변형을 전혀 지원하지 않습니다. F#에서는 지원되지만 값에 대한 기본 동작은 아닙니다.
이 개념은 데이터 구조로 더욱 확장됩니다. 기능 프로그래밍에서 집합(및 기타)과 같은 변경할 수 없는 데이터 구조에는 처음에 예상한 것과 다른 구현이 있습니다. 개념적으로 집합에 항목을 추가하는 것과 같이 집합은 변경되지 않으며 추가 값이 있는 새 집합을 생성합니다. 이 작업은 데이터의 적절한 표현을 결과로 지정할 수 있도록 값을 효율적으로 추적할 수 있는 다른 데이터 구조에 의해 수행되는 경우가 많습니다.
값 및 데이터 구조로 작업하는 이 스타일은 새 버전의 해당 항목을 만드는 것처럼 수정하는 작업을 강제로 처리하도록 하기 때문에 매우 중요합니다. 이렇게 하면 같음 및 비교 가능성과 같은 항목이 프로그램에서 일관될 수 있습니다.
다음 단계
다음 섹션에서는 함수를 함수 프로그래밍에 사용할 수 있는 다양한 방법을 자세히 설명합니다.
F#에서 함수를 사용하면 함수를 자세히 살펴보고 다양한 컨텍스트에서 함수를 사용하는 방법을 보여 줍니다.
추가 읽기
Thinking Functionly 시리즈는 F#을 사용한 기능 프로그래밍에 대해 알아볼 수 있는 또 다른 유용한 리소스입니다. F# 기능을 사용하여 개념을 설명하는 실용적이고 읽기 쉬운 방식으로 기능 프로그래밍의 기본 사항을 다룹니다.
.NET