Discriminated Unions(F#)

구별된 공용 구조체는 여러 가지 명명된 케이스 중 하나일 수 있는 값을 지원합니다. 각 케이스는 값과 형식이 저마다 다를 수 있습니다. 구별된 공용 구조체는 유효한 케이스와 오류를 포함한 특수 케이스가 있을 수 있는 데이터, 인스턴스마다 형식이 다른 데이터 등과 같이 유형이 다른 데이터에 유용하게 사용되며, 개체 계층 구조가 크지 않은 경우 이를 대체하는 역할도 할 수 있습니다. 또한 재귀적인 구별된 공용 구조체를 사용하면 트리 데이터 구조를 표현할 수 있습니다.

type type-name =
   | case-identifier1 [of [ fieldname1 : ] type1 [ * [ fieldname2 : ] type2 ...]
   | case-identifier2 [of [fieldname3 : ]type3 [ * [ fieldname4 : ]type4 ...]
   ...

설명

구별된 공용 구조체는 다른 언어의 공용 구조체 형식과 비슷하지만 여기에는 몇 가지 차이점이 있습니다. C++의 공용 구조체 형식 또는 Visual Basic의 variant 형식과 마찬가지로 값에 저장되는 데이터가 고정적이지 않으며 여러 가지 구별된 옵션 중 하나가 될 수 있습니다. 그러나 이러한 기타 언어의 공용 구조체와 달리 구별된 공용 구조체의 사용 가능한 옵션 각각에는 케이스 식별자가 부여됩니다. 케이스 식별자는 해당 형식의 개체가 가질 수 있는 값의 여러 가지 가능한 형식에 대한 이름입니다. 값은 선택적 요소입니다. 값이 없으면 케이스가 열거형 케이스와 같은 의미를 갖습니다. 값이 있으면 각 값은 지정된 형식의 단일 값이거나, 같거나 다른 형식의 여러 필드를 집계하는 튜플일 수 있습니다. F# 3.1부터는 개별 필드에 이름을 지정할 수 있지만 같은 사례의 다른 필드 이름이 있더라도 이름은 옵션입니다.

예를 들어, 다음 Shape 형식 선언이 있다고 가정합니다.

type Shape =
    | Rectangle of width : float * length : float
    | Circle of radius : float
    | Prism of width : float * float * height : float

이전 코드는 구별된 공용 구조체 도형을 선언하며 사각형, 원 및 프리즘의 세 가지 경우에 대한 값을 가질 수 있습니다. 각 경우에 다른 필드 집합이 있습니다. 사각형의 경우 두 개의 명명된 필드가 있으며 둘 다 이름 너비 및 길이가 있는 float 형식입니다. 원형 케이스는 명명된 필드, 반지름 하나만 있습니다. 프리즘의 경우 세 개의 필드가 있으며 이 중 두 개는 익명 필드로 참조되는 명명되지 않은 필드입니다.

다음 예제에 따라 이름이 지정된 필드 및 익명 필드에 대한 값을 제공하여 개체를 생성합니다.

let rect = Rectangle(length = 1.3, width = 10.0)
let circ = Circle (1.0)
let prism = Prism(5., 2.0, height = 3.0)

이 코드는 초기화에서 명명된 필드를 사용하거나 선언에서 필드 순서에 따라 차례대로 각 필드에 대해 값을 제공할 수 있습니다. 이전 코드의 rect에 대한 생성자 호출은 명명된 필드를 사용하지만 circ에 대한 생성자 호출은 순서 지정을 사용합니다. prism을 생성할 때와 같이 순서 및 이름이 지정된 필드를 혼합할 수 있습니다.

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."

패턴 일치 식에서 구별된 공용 구조체의 일치 항목을 지정하는 데 명명된 필드를 사용할 수 있습니다. 이전에 선언된 셰이프 형식의 경우 다음 코드와 같이 명명된 필드를 사용하여 필드 값을 추출할 수 있습니다.

let getShapeHeight shape =
    match shape with
    | Rectangle(height = h) -> h
    | Circle(radius = r) -> 2. * r
    | Prism(height = h) -> h

일반적으로 케이스 식별자는 공용 구조체의 이름으로 정규화하지 않고도 사용할 수 있습니다. 공용 구조체의 이름을 사용하여 이름을 항상 정규화하려면 공용 구조체 형식 정의에 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입니다.

참고 항목

기타 리소스

F# 언어 참조