https://fsharpforfunandprofit.com/books/
Books | F# for fun and profit
This book starts with a discussion of Domain Driven Design, and then shows to how to model a design using types. The last part shows how to implement the design using functional programming with F# (composition of functions, “railway-oriented programming
fsharpforfunandprofit.com
함수형 프로그래밍 언어의 대수적 타입 시스템을 이용하여 도메인 모델의 개념을 코드에 명시적으로 나타낼 수 있습니다.
함수 이해하기
학교에서 함수는 입출력이 있는 블랙박스와 갖다 배운 적이 있을 겁니다.
타입 시그니처 with F#
(주 : 타입스크립트의 타입은 Type Alias일 뿐입니다. F#과 Haskell은 Data Type을 따로 제공합니다. 타입클래스로 해당 데이터 타입을 사용하기 위해선, 생성자를 따로 정의하거나 리터럴 타입을 사용해야 합니다!)
apple->banana는 타입(클래스) 간의 관계입니다. 즉 apple 클래스에 속하는 값을 banana클래스의 값으로 바꾸어 줍니다.
클래스는 종류, 값은 각각의 케이스(인스턴스) 입니다.
예를들어 int에 숫자를 더하는 x+1함수와, 숫자 두개의 합을 계산하는 함수가 있다 칩시다.
F#는 C# 혹은 haskell, scala와 유사합니다.
즉, 자동으로 커링이 지원되며, 타입 추론이 지원됩니다.
(연산자와 값을 이용해 명시적으로 데이터 타입을 드러내지 않아도 컴파일러가 자동으로 추론해줍니다.)
또한, F#의 모든 식은 표현식입니다. 즉 값은 반드시 평가됩니다.
let 키워드는 함수를 정의하는 데 사용됩니다. 매개변수는 괄호나 쉼표 없이 공백으로 구분됩니다.
C#이나 Java와 달리 return 키워드가 없습니다.
함수 정의의 마지막 표현식은 함수의 출력입니다.
let add1 x = x + 1 // signature is: int -> int
let add x y = x + y // signature is: int -> int -> int
// squarePlusOne : int -> int
let squarePlusOne x =
let square = x * x
square + 1
함수의 제네릭 타입
이전에 F#이 add 함수의 타입을 어떻게 int->int로 추론했을까요?
이는 + 함수가 덧셈에만 사용할 수 있기 때문입니다.
다양한 타입에서 사용할 수 있는 연산자는 어떨까요? (ex =)
=같은 연산자를 지원하기 위해선, haskell과 같은 순수함수 언어에선, 타입변수 a로 추론할 수 있는 타입이 Eq(==)라는 타입 클래스의 인스턴스 여야 합니다. 즉 eqality를 따질 수 있는 값이면 a의 위치에 파라미터로 들어올 수 있다는 것입니다.
자바스크립트로 따지면 number와 number, string과 string이며, 다른 케이스의 경우 할당할 수 없도록 합니다.
아래의 경우도 명시적으로 타입은 없지만, equality를 비교할 수 있는 타입이면 전부 사용할 수 있습니다.
즉 객체지향의 제네릭이 해당 클래스 내에서 사용되기 위한 타입 변수라면,
함수형 프로그래밍의 제네릭은, 해당 타입 클래스가 해당 연산을 지원하는지 판단하기 위해 사용됩니다.
// areEqual : 'a -> 'a -> bool
let areEqual x y =
(x = y)
F#에서 'a는 제네릭 타입을 나타냅니다. 즉, x와 y는 같은 타입이여야 하며, =가 ==의 역할을 하며, 리턴 타입은 bool인 것을 알 수 있습니다.
C#으로 보면 다음과 같습니다.
static bool AreEqual<T>(T x, T y)
{
return (x == y);
}
타입과 함수
사람들이 함수형 프로그래밍을 생각할때 타입의 중요성을 간과하지만, 함수형 프로그래밍의 가장 중요한 개념중 하나는 카테고리 이론입니다.
(lisp계열이나 js계열은 좀 덜중요한것 같긴 합니다만, ML계열에서는 아주 중요합니다.)
함수형 프로그래밍의 타입은 객체 지향 프로그래밍의 타입보다 훨씬 간단합니다.
함수의 입출력에 사용할수 있는 값의 집합에 지정된 이름(타입)일 뿐입니다.
타입은 함수의 입출력에 사용할수 있는 값의 집합에 지정된 이름입니다.
시그니처 : int16 -> someOutputType
시그니처는 이런 식으로 적을 수 있습니다
someInputType -> string
타입은 primitive일 필요는 없습니다.
개념적 관점에서 타입의 사물은 실제 또는 가상의 모든 종류의 사물이 될 수 있습니다.

Jargon Alert: “Values” vs. “Objects” vs. “Variables”
타입 합성하기
함수형 설계의 기본은 함수의 합성입니다.
레고블록처럼 작은 함수들을 조립하여 큰 함수를 만듭니다.
비슷하게 타입을 조립하여 새로운 타입을 만듭니다.
이전의 타입으로부터 새로운 타입을 만듭니다.
- 함께 _AND_함으로써
- 함께 _OR_함으로써
"AND" Types
F#에서 해당 개념은 레코드로 표현됩니다.
type FruitSalad = {
Apple: AppleVariety
Banana: BananaVariety
Cherries: CherryVariety
}
“OR” Types
type FruitSnack =
| Apple of AppleVariety
| Banana of BananaVariety
| Cherries of CherryVariety
FruitSnack은 AppleVariety(Apple 태그) 또는 BananaVariety(Banana 태그) 또는 CherryVariety(Cherry 태그) 중 하나입니다.
type AppleVariety =
| GoldenDelicious
| GrannySmith
| Fuji
type BananaVariety =
| Cavendish
| GrosMichel
| Manzano
type CherryVariety =
| Montmorency
| Bing
Product 타입과 Sum Type
또는 tagged union이라고 하며, F# 용어로 discriminated union이라고 합니다.
DDD에서는 OR 타입을 초이스 타입이라고 하는게 좋은 것 같습니다
간단한 타입
type ProductCode =
| ProductCode of string
/// or
type ProductCode = ProductCode of string
단순히 Primitive Type을 Wrapping하는 타입입니다.
필드값을 통해 해당 값에 접근하는 js, java 개발자들에겐 익숙하지 않은 방법이지만,
함수형 프로그래밍에서는 패턴 매칭, 구조분해을 통해 값을 가져옵니다. 즉 객체지만 해당 필드명을 명시적으로 지정할 필요가 없습니다.
(ex : js로 컴파일되는 리스크립트는 해당 문법을 지원하는데, 내부적으로 index를 이용해 처리합니다.)
/// Constrained to be a decimal between 0.0 and 1000.00
type Price = private Price of decimal
module Price =
/// Return the value inside a Price
/// Price -> decimal
let value (Price v) = v
우리는 도메인 모델링을 할 때 이러한 종류의 유형을 많이 보게 될 것입니다.
단일 케이스 union를 레코드 및 초이스 타입과 같은 복합 유형과 반대되는 "Simple types"으로 레이블을 지정합니다.
대수적 타입 시스템 (Algebraic Type Systems)
F# 타입 좀 더 알아보기
주의 : Discriminated Union의 각 Case는 서브 클래스와 동일하지 않습니다.
UnitQuantity 및 KilogramQuantity는 타입이 아니며 OrderQuantity 유형의 고유한 케이스일 뿐입니다.
위의 예에서 두 값 모두 동일한 타입인 OrderQuantity를 갖습니다.
(ex Maybe a의 Nothing과 Something a은 타입이 아니라 케이스입니다.)
module WorkingWithTypes =
//>WorkingRecord1
// 예를 들어, 레코드 타입을 정의하려면 다음과 같이 중괄호를 사용한 다음
// 각 필드에 대해 name:type 정의를 사용합니다.
type Person = {First:string; Last:string}
//<
//>WorkingRecord2
// 이 타입의 값을 생성하려면 동일한 중괄호 및 =를 사용하여 다음과 같이 필드에 값을 할당합니다.
let aPerson = {First="Alex"; Last="Adams"}
//<
//>WorkingRecord3
// 패턴 매칭을 통해 값을 분해할 수 있습니다!
// first와 last에 해당 값이 대입됩니다.
let {First=first; Last=last} = aPerson
//<
// >WorkingRecord3 와 같은 결과.
let first = aPerson.First
let last = aPerson.Last
module AlternatePropertySyntax =
//>WorkingRecord4
let first = aPerson.First
let last = aPerson.Last
//<
//>WorkingUnion1
type OrderQuantity =
| UnitQuantity of int
| KilogramQuantity of decimal
//<
//>WorkingUnion2
// 초이스 타입은 다음과 같이 매개변수로 전달된 관련 정보와 함께 케이스 레이블 중 하나를 생성자 함수로 사용하여 구성됩니다.
let anOrderQtyInUnits = UnitQuantity 10
let anOrderQtyInKg = KilogramQuantity 2.5M
//<
// 초이스 타입을 아래와 같이 패턴 매치로 분해하여 값을 사용할 수 있습니다.
//>WorkingUnion3
let printQuantity aOrderQty =
match aOrderQty with
| UnitQuantity uQty ->
printfn "%i units" uQty
| KilogramQuantity kgQty ->
printfn "%g kg" kgQty
//<
//>WorkingUnion4
printQuantity anOrderQtyInUnits // "10 units"
printQuantity anOrderQtyInKg // "2.5 kg"
//<
'BackEnd' 카테고리의 다른 글
타입으로 도메인 모델링하기 with F# - 단순, 복합 타입과 함수 (0) | 2022.03.18 |
---|---|
타입으로 코드 문서화하기 With F# - 타입을 조합하여 도메인 모델링 (0) | 2022.03.18 |
도메인 모델을 함수형 아키텍처로 - 컨텍스트 간 통신 2 (0) | 2022.03.17 |
도메인 모델을 함수형 아키텍처로 - 컨텍스트 간 통신 1 (0) | 2022.03.17 |
도메인을 문서화하기 (0) | 2022.03.17 |