匿名のレコード

匿名レコードは、使用前に宣言する必要のない名前付きの値を単純に集めたものです。 これらは、構造体または参照型として宣言できます。 既定では、これらは参照型です。

構文

次の例は、匿名レコードの構文を示しています。 [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 |}

匿名レコードのプロパティ

匿名レコードには、それらの使用方法を十分に理解するために不可欠ないくつかの特性があります。

匿名レコードは標準である

匿名レコードは標準型です。 これらは、事前の宣言を必要としない名前付きレコード型 (標準でもあります) として考えるのが最適です。

2 つの匿名レコード宣言がある次の例について考えてみます。

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

xy の値は型が異なり、相互に互換性がありません。 これらに等値性はなく、比較できません。 これを説明するために、同等の名前付きレコードについて考えてみます。

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

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

型の等価性または比較に関して、同等の名前付きレコードと比較した場合、匿名レコードについて本質的に違いはありません。

匿名レコードでは構造的等価性と比較が使用される

レコードの型と同様に、匿名レコードは構造的な等値性があり、比較可能です。 これは、レコード型の場合と同様に、構成するすべての型によって等式と比較がサポートされる場合にのみ当てはまります。 等式または比較をサポートするには、2 つの匿名レコードの "シェイプ" が同じである必要があります。

{| 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 では、匿名型を渡す必要があります。 匿名レコードは、それらを操作するためのツールです。

制限事項

匿名レコードの使用にはいくつかの制限があります。 設計に固有のものもあれば、変化に対応しているものもあります。

パターン マッチングに関する制限事項

名前付きレコードとは異なり、匿名レコードではパターン マッチングがサポートされていません。 3 つの理由があります。

  1. 名前付きレコードの型とは異なり、匿名レコードのすべてのフィールドをパターンで考慮する必要があります。 これは、匿名レコードでは構造的サブタイプがサポートされていないためです。これらは標準型です。
  2. (1) のため、パターン マッチ式に追加のパターンを含めることはできません。これは、個別のパターンがそれぞれ異なる匿名レコードの型を示すためです。
  3. (2) のため、匿名レコード パターンは、"ドット" 表記を使用する場合よりも詳細です。

限られたコンテキストでのパターン マッチングを可能にするためのオープン言語の推奨があります。

変更性に関する制限事項

現在、mutable データを含む匿名レコードを定義することはできません。 変更可能なデータを可能にするためのオープン言語の推奨があります。

構造体の匿名レコードに関する制限事項

構造体の匿名レコードを IsByRefLike または IsReadOnly として宣言することはできません。 IsByRefLikeIsReadOnly の匿名レコードには、オープン言語の推奨があります。