次の方法で共有


匿名レコード

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

構文

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

入力型と同じ "shape" を持たない匿名レコード型で 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 |}

x値とy値の型が異なり、相互に互換性がありません。 これらは等価でなく、比較できません。 これを説明するために、次のような名前付きレコードを考えてみましょう。

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

匿名型の渡しを必要とするさまざまな API が .NET 全体で使用されています。 匿名レコードは、それらを操作するためのツールです。

制限事項

匿名レコードの使用には、いくつかの制限があります。 デザインに固有のものもありますが、変更が可能なものもあります。

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

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

  1. 名前付きレコード型とは異なり、パターンは匿名レコードのすべてのフィールドを考慮する必要があります。 これは、匿名レコードは構造サブタイピングをサポートしていないためです。これらは名目型です。
  2. (1) のため、パターン 一致式に追加のパターンを含める機能はありません。各パターンは異なる匿名レコード型を意味するためです。
  3. (2) のため、匿名レコード パターンは、"dot" 表記を使用するよりも詳細になります。

限られたコンテキストでパターン マッチングを許可するオープンな言語提案があります。

変更可能性に関する制限事項

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

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

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