익명 레코드

익명 레코드는 사용하기 전에 선언할 필요가 없는 명명된 값의 간단한 집계입니다. 이를 구조체 또는 참조 형식으로 선언할 수 있습니다. 기본적으로 참조 형식입니다.

구문

다음 예제에서는 익명 레코드 구문을 보여 줍니다. 선택 사항으로 [item] 구분된 항목입니다.

// Construct an anonymous record
let value-name = [struct] {| Label1: Type1; Label2: Type2; ...|}

// Use an anonymous record as a type parameter
let value-name = Type-Name<[struct] {| Label1: Type1; Label2: Type2; ...|}>

// Define a parameter with an anonymous record as input
let function-name (arg-name: [struct] {| Label1: Type1; Label2: Type2; ...|}) ...

기본적인 사용 방법

익명 레코드는 인스턴스화 전에 선언할 필요가 없는 F# 레코드 형식으로 가장 적합합니다.

예를 들어 익명 레코드를 생성하는 함수와 상호 작용하는 방법은 다음과 같습니다.

open System

let getCircleStats radius =
    let d = radius * 2.0
    let a = Math.PI * (radius ** 2.0)
    let c = 2.0 * Math.PI * radius

    {| Diameter = d; Area = a; Circumference = c |}

let r = 2.0
let stats = getCircleStats r
printfn "Circle with radius: %f has diameter %f, area %f, and circumference %f"
    r stats.Diameter stats.Area stats.Circumference

다음 예제에서는 익명 레코드를 입력으로 사용하는 함수를 printCircleStats 사용하여 이전 레코드를 확장합니다.

open System

let getCircleStats radius =
    let d = radius * 2.0
    let a = Math.PI * (radius ** 2.0)
    let c = 2.0 * Math.PI * radius

    {| Diameter = d; Area = a; Circumference = c |}

let printCircleStats r (stats: {| Area: float; Circumference: float; Diameter: float |}) =
    printfn "Circle with radius: %f has diameter %f, area %f, and circumference %f"
        r stats.Diameter stats.Area stats.Circumference

let r = 2.0
let stats = getCircleStats r
printCircleStats r stats

입력 형식과 동일한 "셰이프"가 없는 익명 레코드 형식을 사용하여 호출 printCircleStats 하면 컴파일되지 않습니다.

printCircleStats r {| Diameter = 2.0; Area = 4.0; MyCircumference = 12.566371 |}
// Two anonymous record types have mismatched sets of field names
// '["Area"; "Circumference"; "Diameter"]' and '["Area"; "Diameter"; "MyCircumference"]'

익명 레코드 구조체

익명 레코드는 선택적 struct 키워드를 사용하여 구조체로 정의할 수도 있습니다. 다음 예제에서는 구조체 익명 레코드를 생성하고 사용하여 이전 레코드를 보강합니다.

open System

let getCircleStats radius =
    let d = radius * 2.0
    let a = Math.PI * (radius ** 2.0)
    let c = 2.0 * Math.PI * radius

    // Note that the keyword comes before the '{| |}' brace pair
    struct {| Area = a; Circumference = c; Diameter = d |}

// the 'struct' keyword also comes before the '{| |}' brace pair when declaring the parameter type
let printCircleStats r (stats: struct {| Area: float; Circumference: float; Diameter: float |}) =
    printfn "Circle with radius: %f has diameter %f, area %f, and circumference %f"
        r stats.Diameter stats.Area stats.Circumference

let r = 2.0
let stats = getCircleStats r
printCircleStats r stats

구조체 유추

구조체 익명 레코드를 사용하면 호출 사이트에서 키워드를 지정할 struct 필요가 없는 "구조체 유추"도 허용됩니다. 이 예제에서는 다음을 호출printCircleStats할 때 키워드를 struct 생략합니다.


let printCircleStats r (stats: struct {| Area: float; Circumference: float; Diameter: float |}) =
    printfn "Circle with radius: %f has diameter %f, area %f, and circumference %f"
        r stats.Diameter stats.Area stats.Circumference

printCircleStats r {| Area = 4.0; Circumference = 12.6; Diameter = 12.6 |}

입력 형식이 구조체 익명 레코드가 아닌 경우를 지정하는 struct 역방향 패턴은 컴파일에 실패합니다.

다른 형식 내에 익명 레코드 포함

사례가 레코드인 차별된 공용 구조체를 선언하는 것이 유용합니다. 그러나 레코드의 데이터가 구분된 공용 구조체와 동일한 형식인 경우 모든 형식을 상호 재귀적으로 정의해야 합니다. 익명 레코드를 사용하면 이 제한을 피할 수 있습니다. 다음은 패턴이 일치하는 예제 형식 및 함수입니다.

type FullName = { FirstName: string; LastName: string }

// Note that using a named record for Manager and Executive would require mutually recursive definitions.
type Employee =
    | Engineer of FullName
    | Manager of {| Name: FullName; Reports: Employee list |}
    | Executive of {| Name: FullName; Reports: Employee list; Assistant: Employee |}

let getFirstName e =
    match e with
    | Engineer fullName -> fullName.FirstName
    | Manager m -> m.Name.FirstName
    | Executive ex -> ex.Name.FirstName

식 복사 및 업데이트

익명 레코드는 복사 및 업데이트 식을 사용하여 생성을 지원합니다. 예를 들어 기존 데이터를 복사하는 익명 레코드의 새 인스턴스를 생성하는 방법은 다음과 같습니다.

let data = {| X = 1; Y = 2 |}
let data' = {| data with Y = 3 |}

그러나 명명된 레코드와 달리 익명 레코드를 사용하면 복사 및 업데이트 식을 사용하여 완전히 다른 양식을 생성할 수 있습니다. 다음 예제에서는 이전 예제와 동일한 익명 레코드를 가져와서 새 익명 레코드로 확장합니다.

let data = {| X = 1; Y = 2 |}
let expandedData = {| data with Z = 3 |} // Gives {| X=1; Y=2; Z=3 |}

명명된 레코드의 인스턴스에서 익명 레코드를 생성할 수도 있습니다.

type R = { X: int }
let data = { X = 1 }
let data' = {| data with Y = 2 |} // Gives {| X=1; Y=2 |}

참조 및 구조체 익명 레코드에서 데이터를 복사할 수도 있습니다.

// Copy data from a reference record into a struct anonymous record
type R1 = { X: int }
let r1 = { X = 1 }

let data1 = struct {| r1 with Y = 1 |}

// Copy data from a struct record into a reference anonymous record
[<Struct>]
type R2 = { X: int }
let r2 = { X = 1 }

let data2 = {| r1 with Y = 1 |}

// Copy the reference anonymous record data into a struct anonymous record
let data3 = struct {| data2 with Z = r2.X |}

익명 레코드의 속성

익명 레코드에는 사용할 수 있는 방법을 완전히 이해하는 데 필수적인 여러 특성이 있습니다.

익명 레코드는 명목입니다.

익명 레코드는 명목 형식입니다. 선행 선언이 필요하지 않은 명명된 레코드 형식(명목 형식이기도 함)으로 가장 적합합니다.

두 개의 익명 레코드 선언이 있는 다음 예제를 살펴보겠습니다.

let x = {| X = 1 |}
let y = {| Y = 1 |}

x 값은 y 형식이 다르며 서로 호환되지 않습니다. 그들은 동일시할 수 없으며 비교할 수 없습니다. 이를 설명하기 위해 다음과 같은 명명된 레코드를 고려합니다.

type X = { X: int }
type Y = { Y: int }

let x = { X = 1 }
let y = { Y = 1 }

형식 동등성 또는 비교와 관련하여 명명된 레코드와 비교할 때 익명 레코드에 대해 본질적으로 다른 것은 없습니다.

익명 레코드는 구조적 같음 및 비교를 사용합니다.

레코드 형식과 마찬가지로 익명 레코드는 구조적으로 동일하고 비교할 수 있습니다. 모든 구성 요소 형식이 레코드 형식과 같이 같음 및 비교를 지원하는 경우에만 마찬가지입니다. 같음 또는 비교를 지원하려면 익명 레코드 두 개에 동일한 "셰이프"가 있어야 합니다.

{| a = 1+1 |} = {| a = 2 |} // true
{| a = 1+1 |} > {| a = 1 |} // true

// error FS0001: Two anonymous record types have mismatched sets of field names '["a"]' and '["a"; "b"]'
{| a = 1 + 1 |} = {| a = 2;  b = 1|}

익명 레코드를 직렬화할 수 있습니다.

명명된 레코드와 마찬가지로 익명 레코드를 직렬화할 수 있습니다. Newtonsoft.Json을 사용하는 예제는 다음과 같습니다.

open Newtonsoft.Json

let phillip' = {| name="Phillip"; age=28 |}
let philStr = JsonConvert.SerializeObject(phillip')

let phillip = JsonConvert.DeserializeObject<{|name: string; age: int|}>(philStr)
printfn $"Name: {phillip.name} Age: %d{phillip.age}"

익명 레코드는 직렬화/역직렬화된 형식에 대한 도메인을 미리 정의할 필요 없이 네트워크를 통해 경량 데이터를 보내는 데 유용합니다.

C# 익명 형식과 상호 운용되는 익명 레코드

C# 익명 형식을 사용해야 하는 .NET API를 사용할 수 있습니다. C# 익명 형식은 익명 레코드를 사용하여 상호 운용하는 데 매우 간단합니다. 다음 예제에서는 익명 레코드를 사용하여 익명 형식이 필요한 LINQ 오버로드를 호출하는 방법을 보여 줍니다.

open System.Linq

let names = [ "Ana"; "Felipe"; "Emilia"]
let nameGrouping = names.Select(fun n -> {| Name = n; FirstLetter = n[0] |})
for ng in nameGrouping do
    printfn $"{ng.Name} has first letter {ng.FirstLetter}"

.NET 전체에서 익명 형식으로 전달해야 하는 다양한 다른 API가 사용됩니다. 익명 레코드는 해당 레코드를 사용하기 위한 도구입니다.

제한 사항

익명 레코드의 사용에는 몇 가지 제한 사항이 있습니다. 일부는 디자인에 내재되어 있지만 다른 디자인은 변경할 수 있습니다.

패턴 일치의 제한 사항

익명 레코드는 명명된 레코드와 달리 패턴 일치를 지원하지 않습니다. 세 가지 이유가 있습니다.

  1. 패턴은 명명된 레코드 형식과 달리 익명 레코드의 모든 필드를 고려해야 합니다. 익명 레코드는 구조적 하위 스타일을 지원하지 않으므로 명목 형식입니다.
  2. (1) 때문에 각 고유 패턴이 다른 익명 레코드 형식을 의미하므로 패턴 일치 식에 추가 패턴을 사용할 수 없습니다.
  3. (2) 때문에 익명 레코드 패턴은 "점" 표기법을 사용하는 것보다 더 자세한 정보입니다.

제한된 컨텍스트에서 패턴 일치를 허용하는 개방형 언어 제안이 있습니다.

변경 가능성의 제한 사항

현재는 데이터로 익명 레코드 mutable 를 정의할 수 없습니다. 변경 가능한 데이터를 허용하는 개방형 언어 제안이 있습니다.

구조체 익명 레코드의 제한 사항

구조체 익명 레코드를 로 IsByRefLike 선언할 IsReadOnly수 없습니다. 익명 레코드에 대해 IsByRefLikeIsReadOnly열려 있는 언어 제안이 있습니다.