[Power BI] Power Queryの『型』を探る (5) カスタム型

https://learn.microsoft.com/ja-jp/powerquery-m/m-spec-types

https://bengribaudo.com/blog/2020/06/02/5259/power-query-m-primer-part18-type-system-iii-custom-types

# リスト型

リスト型は `type list` または `{}` で型名を囲って指定します。

```text:リスト型の指定
type list
type any
type { any }
type { number }
type { text }
type { { function } }
```

リストはany型で作成されますが、型を指定する場合は `Value.ReplaceType` を使用します。ただし、互換性のチェックは行われませんので、変換できない値でもエラーにはなりません。

```
let
    // 自動的に type list　つまり type { any ) の型になります。
    MyList = { 1, 2, 3 } 
    // number型の要素を持つリストが返されます。
    Result = Value.ReplaceType(MyList, type { number }) 
in
    Result
```

しかし、ここで設定された型は、 `Value.Type` で確認しても `list` としか表示されません。リストの中身の型を確認するには、 `Value.ListItem` を使用します。

```text:ListItem
let
    MyList = {1,2,3},
    ReplaceType = 
        Value.ReplaceType(
            MyList,
            type { number }
        ),
    Result = 
        Type.ListItem( 
            Value.Type( ReplaceType ) 
        )
in
    Result  // numberが返されます。
```


# レコード型

　レコード型は型名を `[]` で囲って指定します。型名を指定することができますが、指定しない場合は `any` 型になります。

```text:レコード型の指定
type [ItemCode, Amount = number]
```
　上記は、商品コードと金額の２つのフィールドを持つレコード型を定義しています。

```
let
    // 初期の型は type [Name = any, Age = any]
    Person = [Name = "Joe", Age = 50], 
    NewType = type [FullName = text, Age = number],
    Result = Value.ReplaceType(Person, NewType) 
in
    Result
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/9df1eedf-3f74-606f-4e70-eb1ca5a9ed00.png)

　上記では、最初は自動的に `type [Name = any, Age = any]` の型が与えられます。`Value.ReplaceType` によって返される `[FullName = "Joe", Age = 50]` のレコードの型は `type [FullName = text, Age = number]` に変更されます。

　新しい型の項目の情報は、項目の位置によって適用されます。したがって、１番目の項目名 "Name" が "FullName" と定義されていたら "FullName" に変更されます。ただし、 **あくまで新しい型を割り当てるときにフィールド名には同じものを指定するようにしてください** 。マッシュアップエンジンは、値が型に適合しているかチェックする際に項目名によってマッチングしており、予期しない動作が発生する可能性があります。

　また、リストの場合と同様に互換性のチェックは行われません。

# 抽象型

　以下のような抽象型を記述することができます。

```
type [FirstName = text, optional LastName = text, Age = number]  // オプション指定
type [Amount = number, ...]  // オープン指定
type record  // type[...]と同様のオープン指定
type []  // 空のレコード型
```

# テーブル型
　これまでの話を踏まえて、テーブルの定義は以下のように表されます。

```	
type table [SomeColumn = text, AnotherColumn = any, YetOneMore]
```
　`[]` の中で、行の型を定義しています。行の型は、レコード型から抽象型を除いたものと同様になります。

```
#table({"Name", "Age"},{{"Joe", 50}})
```

これは、いかのような型のテーブルを作成します。

```
type table [Name = any, Age = any]
```

テーブルの型に主キーを定義できます。

```text:Type.AddTableKey構文
Type.AddTableKey(
    table as type, 
    columns as list, 
    isPrimary as logical
) as type
```

```text:Type.AddTableKey例文
let
    Source =
        #table(
            2,
            {
                {"List1", {1,2,3}},
                {"List2", {4,5,6}}
            }
        ),
    UpdateType = 
        Type.AddTableKey(
            Value.Type( Source ),
            { "Column1" },
            true
        ),
    ReplaceType =
        Value.ReplaceType(Source, UpdateType)
in
    ReplaceType
```

標準ライブラリにはキーを追加する `Table.AddKey` 関数があり、こちらの方が簡潔です。

```text:Table.AddKey構文
Table.AddKey(
    table as table, 
    columns as list, 
    isPrimary as logical
) as table
```

```text:Table.Addkey例文
let
    Source =
        Table.AddKey(
            #table(
                2,
                {
                    {"List1", {1,2,3}},
                    {"List2", {4,5,6}}
                }
            ),
            {"Column1"}, true
        )
in
    Source
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/8f7a5f4f-b5e1-fdcb-ece3-d672b5236cc7.png)
テーブルにキーが設定されていない場合、上の図にある"List2"の行の "List" をクリックした場合、以下のように `Source{1}[Column2]` という表現になります。

```text:キーが指定されていない場合
let
    Source =
        #table(
            2,
            {
                {"List1", {1,2,3}},
                {"List2", {4,5,6}}
            }
        ),
    Column2 = Source{1}[Column2]
in
    Column2
```

テーブルにキーが指定されている場合は、`Table.AddKey` と `Type.AddTableKey` のいずれも以下のように `List2 = ReplaceType{[Column1="List2"]}[Column2]` となります。

```text:キーが指定されている場合
let
    Source =
        #table(
            2,
            {
                {"List1", {1,2,3}},
                {"List2", {4,5,6}}
            }
        ),
    UpdateType = 
        Type.AddTableKey(
            Value.Type(Source),
            {"Column1"},
            true
        ),
    ReplaceType =
        Value.ReplaceType(Source, UpdateType),
    List2 = ReplaceType{[Column1="List2"]}[Column2]
in
    List2
```

https://qiita.com/PowerBIxyz/items/cc8f183ea9fe38287e91

# 関数型
関数のパラメーターと戻り値に任意で型を指定することができます。ただし、関数型の定義を行う場合は、両方指定する必要があります。

```text
// 関数にはパラメータの型と戻り値の型の指定は任意。
(total) => total * 0.1

// 関数型の指定では、両方を指定しなければならない
type function(total as number) as number
```

関数で型の指定を省略した場合は、`any`が振られます。上記の例では、`function(total as any) as any` となります。

# Type Context
型名のリテラルは、"type" というキーワードによって Type Context が参照されます。つまり、"type" に続く識別子が型名の場合はその型を参照し、型名出ない場合は通常の識別子参照として解釈されます。

```text:TypeContext
let
  ColumnType = type any
in
  type table [Col1 = ColumnType]
```

型名と同じ名前の変数を参照するようなコードでは、どちらが選択されるでしょうか。

```
let
  record = type table [A = text, B = logical]
in
  Type.ListItem(type { (record) })     // (1) return table
//Type.ListItem(type { record })       // (2) return record
//Type.ListItem(type { (type record) } // (3) return record
```

# 型の値だけを使用する
型の指定を行う場合の説明をしてきましたが、関数自体の定義ではプリミティブな型名だけが使用できます。この場合、"type"は使用せず、カスタムタイプも使用できません。

```
1 as nullable number // valid syntax
1 as type nullable number // invalid syntax -- number should not be prefixed with type
  
SomeRecord is record // valid syntax
SomeRecord is [Name = text] // invalid syntax -- can't use a custom type here
  
(input as table) => ... // valid syntax
(input as [Col1 = any]) => ... // invalid syntax -- can't use a custom type here
(input as SomeType) => ... // invalid syntax -- can't use an expression for the type here
```

# 型の比較

型の比較を行ってみます。

```
type {number} = type list  // false
```
2つの型が等しいか確認するのに、この方法は適切ではありません。等値演算子を使用して型の値を比較しないでください。型の比較には `Type.Is` という関数があります。

```
Type.Is(type {number}, type list) // true
```

2番めの引数はnull許容プリミティブ型である必要があります。null非許容プリミティブ型を指定してもエラーになりませんが、信頼される結果が得られません。

# 型のチェック
Power Query は動的に型指定されます。値が特定の型と互換性があるかどうかのすべてのチェックは、実行時に値の型を調べることによって行われます。変数が型を持つという概念はなく、型キャストも関係なく、実際の値の型が重要です。ここで重要なのは、チャイルドタイプは単なる型の主張であるということです。Mはそれらを検証していませんし、マッシュアップエンジンレベルでそれらを強制することもありません。たとえ検証されていなくても、他のコードやホスト・アプリケーションはそれらを信頼し、そこから価値を引き出すことができるのです。 


# 関連記事
1. [[Power BI] Power Queryの『型』を探る (1) number](https://qiita.com/spumoni/items/9459635b69db773eca30)
1. [[Power BI] Power Queryの『型』を探る (2) text, temporal falimy](https://qiita.com/spumoni/items/ce38958d9858ea132a8f)
1. [[Power BI] Power Queryの『型』を探る (3) number 蛇足](https://qiita.com/spumoni/items/c46b3ddfb8173b6e9aa2)
1. [[Power BI] Power Queryの『型』を探る (4) null](https://qiita.com/spumoni/items/d8279858ceb627af8101)
1. [[Power BI] Power Queryの『型』を探る (5) カスタム型]()
1. [Power BIデータモデルの型システム (1) 数値型、日付／時刻型](https://qiita.com/spumoni/items/c73e465b691b15694ba0)
1. [Power BIデータモデルの型システム (2) テキスト型、論理型](https://qiita.com/spumoni/items/476494dc7818edc5e190)