Discriminated Unions(F#)
구별된 공용 구조체는 여러 가지 명명된 케이스 중 하나일 수 있는 값을 지원합니다. 각 케이스는 값과 형식이 저마다 다를 수 있습니다.구별된 공용 구조체는 유효한 케이스와 오류를 포함한 특수 케이스가 있을 수 있는 데이터, 인스턴스마다 형식이 다른 데이터 등과 같이 유형이 다른 데이터에 유용하게 사용되며, 개체 계층 구조가 크지 않은 경우 이를 대체하는 역할도 할 수 있습니다.또한 재귀적인 구별된 공용 구조체를 사용하면 트리 데이터 구조를 표현할 수 있습니다.
type type-name =
| case-identifier1 [of type1 [ * type2 ...]
| case-identifier2 [of type3 [ * type4 ...]
...
설명
구별된 공용 구조체는 다른 언어의 공용 구조체 형식과 비슷하지만 여기에는 몇 가지 차이점이 있습니다.C++의 공용 구조체 형식 또는 Visual Basic의 variant 형식과 마찬가지로 값에 저장되는 데이터가 고정적이지 않으며 여러 가지 구별된 옵션 중 하나가 될 수 있습니다.그러나 이러한 기타 언어의 공용 구조체와 달리 구별된 공용 구조체의 사용 가능한 옵션 각각에는 케이스 식별자가 부여됩니다.케이스 식별자는 해당 형식의 개체가 가질 수 있는 값의 여러 가지 가능한 형식에 대한 이름입니다. 값은 선택적 요소입니다.값이 없으면 케이스가 열거형 케이스와 같은 의미를 갖습니다.값이 있으면 각 값은 지정된 형식의 단일 값이거나, 형식이 동일하거나 서로 다른 여러 값을 집계하는 튜플이 될 수 있습니다.
option은 F# 핵심 라이브러리의 구별된 공용 구조체로서 단순한 형식입니다.option 형식은 다음과 같이 선언됩니다.
// The option type is a discriminated union.
type Option<'a> =
| Some of 'a
| None
위 코드에서 Option 형식은 Some과 None이라는 두 케이스가 있는 구별된 공용 구조체입니다.Some 케이스에는 형식 매개 변수 'a로 해당 형식을 표현하는 관련 값이 있습니다.None 케이스에는 관련 값이 없습니다.따라서 option 형식은 일부 형식의 값이 있거나 값이 없는 제네릭 형식을 지정합니다.Option 형식에는 소문자로 된 형식 별칭인 option도 있으며 이 별칭이 더 일반적으로 사용됩니다.
케이스 식별자를 구별된 공용 구조체 형식에 대한 생성자로 사용할 수 있습니다.예를 들어 다음 코드는 option 형식의 값을 만드는 데 사용됩니다.
let myOption1 = Some(10.0)
let myOption2 = Some("string")
let myOption3 = None
케이스 식별자는 패턴 일치 식에도 사용됩니다.패턴 일치 식에서는 개별 케이스에 관련된 값에 대해 식별자가 제공됩니다.예를 들어 다음 코드에서 x는 option 형식의 Some 케이스와 관련된 값이 주어진 식별자입니다.
let printValue opt =
match opt with
| Some x -> printfn "%A" x
| None -> printfn "No value."
일반적으로 케이스 식별자는 공용 구조체의 이름으로 정규화하지 않고도 사용할 수 있습니다.공용 구조체의 이름을 사용하여 이름을 항상 정규화하려면 공용 구조체 형식 정의에 RequireQualifiedAccess 특성을 적용하면 됩니다.
개체 계층 구조 대신 구별된 공용 구조체 사용
많은 경우에 크기가 작은 개체 계층 구조를 대신하는 더 간단한 방법으로 구별된 공용 구조체를 사용할 수 있습니다.예를 들어 원, 사각형 등에 대한 파생 형식이 있는 Shape 기본 클래스 대신 다음과 같은 구별된 공용 구조체를 사용할 수 있습니다.
type Shape =
// The value here is the radius.
| Circle of float
// The value here is the side length.
| EquilateralTriangle of double
// The value here is the side length.
| Square of double
// The values here are the height and width.
| Rectangle of double * double
개체 지향 구현에 흔히 사용되는 가상 메서드로 면적이나 둘레를 계산하는 대신 패턴 일치를 사용하여 적절한 수식으로 분기한 다음 이러한 수량을 계산할 수 있습니다.다음 예제에서는 도형에 따라 서로 다른 수식을 사용하여 면적을 계산합니다.
let pi = 3.141592654
let area myShape =
match myShape with
| Circle radius -> pi * radius * radius
| EquilateralTriangle s -> (sqrt 3.0) / 4.0 * s * s
| Square s -> s * s
| Rectangle (h, w) -> h * w
let radius = 15.0
let myCircle = Circle(radius)
printfn "Area of circle that has radius %f: %f" radius (area myCircle)
let squareSide = 10.0
let mySquare = Square(squareSide)
printfn "Area of square that has side %f: %f" squareSide (area mySquare)
let height, width = 5.0, 10.0
let myRectangle = Rectangle(height, width)
printfn "Area of rectangle that has height %f and width %f is %f" height width (area myRectangle)
출력은 다음과 같습니다.
Area of circle that has radius 15.000000: 706.858347
Area of square that has side 10.000000: 100.000000
Area of rectangle that has height 5.000000 and width 10.000000 is 50.000000
트리 데이터 구조에 대해 구별된 공용 구조체 사용
구별된 공용 구조체는 재귀적일 수 있습니다. 즉, 공용 구조체 자체가 하나 이상의 케이스에 대한 형식에 포함될 수 있습니다.재귀적인 구별된 공용 구조체를 사용하면 프로그래밍 언어에서 식을 모델링하는 데 사용되는 트리 구조를 만들 수 있습니다.다음 코드에서는 재귀적인 구별된 공용 구조체를 사용하여 이진 트리 데이터 구조를 만듭니다.이 공용 구조체는 정수 값과 왼쪽 및 오른쪽 하위 트리를 포함하는 노드인 Node와 트리를 종료하는 Tip이라는 두 케이스로 이루어집니다.
type Tree =
| Tip
| Node of int * Tree * Tree
let rec sumTree tree =
match tree with
| Tip -> 0
| Node(value, left, right) ->
value + sumTree(left) + sumTree(right)
let myTree = Node(0, Node(1, Node(2, Tip, Tip), Node(3, Tip, Tip)), Node(4, Tip, Tip))
let resultSumTree = sumTree myTree
위 코드에서 resultSumTree의 값은 10입니다.다음 그림에서는 myTree의 트리 구조를 보여 줍니다.
myTree의 트리 구조
구별된 공용 구조체는 트리의 노드가 서로 다른 유형이더라도 제대로 작동합니다.다음 코드에서 Expression은 숫자와 변수의 덧셈 및 곱셈을 지원하는 간단한 프로그래밍 언어의 식으로 이루어진 추상 구문 트리를 나타냅니다.공용 구조체 케이스 중 일부는 재귀적이지 않으며 숫자(Number) 또는 변수(Variable)를 나타냅니다.다른 케이스는 재귀적이며 연산(Add 및 Multiply)을 나타냅니다. 여기서는 피연산자도 식입니다.Evaluate 함수는 일치 식을 사용하여 구문 트리를 재귀적으로 처리합니다.
type Expression =
| Number of int
| Add of Expression * Expression
| Multiply of Expression * Expression
| Variable of string
let rec Evaluate (env:Map<string,int>) exp =
match exp with
| Number n -> n
| Add (x, y) -> Evaluate env x + Evaluate env y
| Multiply (x, y) -> Evaluate env x * Evaluate env y
| Variable id -> env.[id]
let environment = Map.ofList [ "a", 1 ;
"b", 2 ;
"c", 3 ]
// Create an expression tree that represents
// the expression: a + 2 * b.
let expressionTree1 = Add(Variable "a", Multiply(Number 2, Variable "b"))
// Evaluate the expression a + 2 * b, given the
// table of values for the variables.
let result = Evaluate environment expressionTree1
이 코드를 실행했을 때 result의 값은 5입니다.