Power Query Mのnullableとoptionalを使い分ける引数設計

LWP | Power Query Mのnullableとoptionalを使い分ける引数設計

LWP TECHNICAL ARTICLE | 164

Power Query Mのnullableとoptionalを
使い分ける引数設計

「nullを渡せる」と「引数を省略できる」を分け、壊れにくい関数インターフェースを作る

Copyright © 2026 LWP 山中 一弘

本資料は、出典を明記いただければ、商用・非商用を問わず、ご自由に複製・改変・再配布していただけます。なお、著作権表示は改変せず、そのまま記載してご利用くださいますようお願いいたします。

記事要約

Power Query Mで関数を作るとき、nullableとoptionalはどちらも「必須ではない」ように見えます。しかし、両者が扱う問題は異なります。nullableは、その場所へnullを置けるかを決める型の設計です。optionalは、関数を呼び出すときに引数を省略できるかを決めるインターフェースの設計です。

設計では、まず「その引数がなければ処理を定義できるか」を判断します。次に、省略時の自然な既定動作、nullの意味、省略と明示的なnullを同じ意味にしてよいかを決めます。任意設定が増える場合は、引数を横へ並べ続けるのではなく、options recordへまとめます。

この記事では、基本となる宣言、公式仕様から確認できる動作、型情報による観察方法、実務向けの設計例、避けたい設計を一つの流れで整理します。結論は単純です。nullableは受け入れる値の範囲、optionalは呼び出し側の省略可能性を表します。

本記事の対象とゴール

想定読者

  • Power Queryで独自関数を作り始めた方

  • 引数へnullable、optional、anyのどれを付けるべきか迷う方

  • 複数人で使うM関数やライブラリの公開インターフェースを設計する方

  • null、空文字、省略、既定値を明確に区別したい方

本記事で得られること

  1. nullableとoptionalを別の設計軸として説明できます。

  2. 必須・省略可能・null許容の組み合わせを正しく選べます。

  3. 「未指定」と「明示的なnull」を区別する設計へ切り替えられます。

  4. Value.TypeとType関数を使い、関数の契約を型情報から確認できます。

  5. 任意設定が増えても壊れにくいoptions recordを設計できます。

目次

  • はじめに 似て見える二つの言葉を分ける

  • 第1章 引数宣言を二つの軸で読む

  • 第2章 nullableは値の範囲を表す

  • 第3章 optionalは呼び出し方を表す

  • 第4章 引数インターフェースを決める

  • 第5章 省略と明示的なnullを扱う

  • 第6章 実務の関数へ当てはめる

  • 第7章 null、空文字、戻り値を分ける

  • 第8章 関数の型情報を観察する

  • 第9章 避けたい設計と公開前チェック

  • まとめ 二つの軸から関数の契約を作る

はじめに 似て見える二つの言葉を分ける

0.1 見た目は近くても役割は違う

Power Query Mの関数では、次のような引数宣言を見かけます。

(
    optional a_column_name as nullable text
) =>
    ...

nullableとoptionalが同じ行に並ぶため、どちらも「この引数は必須ではない」という意味に感じられます。しかし、二つは異なる問いへ答えています。

nullable
= 値としてnullを受け入れるか
 
optional
= 呼び出すときに引数を省略できるか

nullableは型の設計です。optionalは関数インターフェースの設計です。この区別ができると、すべての入力をanyへ逃がしたり、呼び出しやすさだけを理由に必須条件までoptionalにしたりせず、関数の前提をシグネチャへ表せます。

0.2 設計は「値」と「呼び出し」の二軸で考える

各引数について、最初に次の二問を分けます。

  1. 呼び出し側は、その引数を必ず渡す必要があるか。

  2. 渡された値として、nullを認めるか。

一問目がoptional、二問目がnullableの領域です。この二軸を混ぜずに判断することが、本記事全体の基礎になります。

第1章 引数宣言を二つの軸で読む

1.1 必須で、nullを許可しない

(
    a_name as text
) =>
    ...

呼び出し側は引数を必ず指定し、その値はtextでなければなりません。

FunctionA("ABC") // OK
FunctionA(null)  // 型エラー
FunctionA()      // 引数不足

処理対象、検索キー、変換規則など、値がなければ処理自体を定義できない引数の基本形です。

1.2 必須だが、nullを許可する

(
    a_name as nullable text
) =>
    ...

引数の指定は必須ですが、値としてnullを渡せます。

FunctionB("ABC") // OK
FunctionB(null)  // OK
FunctionB()      // 引数不足

たとえば、置換値としてnullを指定できる関数や、「上限なし」をnullで表す関数に適します。

1.3 省略可能で、省略時はnullになる

(
    optional a_name as text
) =>
    ...

M言語仕様では、optional引数が省略された場合、その引数の値としてnullが使われます。また、Type.FunctionParametersでこの関数の型を観察すると、a_nameはnullable textとして取得されます。

FunctionC("ABC") // OK
FunctionC(null)  // OK
FunctionC()      // OK。関数内のa_nameはnull

次のようにnullableを明示する書き方もできます。

(
    optional a_name as nullable text
) =>
    ...

動作上はoptional引数がすでにnullを受け取れるため、nullableは重複して見えます。それでも、公開関数のコード上で「nullを既定値の入口として扱う」と意図を強調したい場合には、明示する価値があります。重要なのは、optionalとnullableを同じ機能だと説明しないことです。

1.4 必須だが、任意の型を受け取る

(
    a_value as any
) =>
    ...

anyはnullを含むすべての値を分類します。そのため、nullable anyはanyと同等であり、通常は書く必要がありません。

ただし、anyでは関数が要求する値の形を読み手へ伝えられません。入力型を特定できるなら、text、table、record、binaryなどを明示するほうが安全です。anyは型を決められないときの逃げ道ではなく、複数種類の値を意図的に受け入れるときの宣言です。

1.5 四つの形を一覧で比べる

宣言 指定 null 主な用途
a_value as text 必須 不可 処理成立に必要な文字列
a_value as nullable text 必須 可 null自体に意味がある文字列
optional a_value as text 省略可 可 省略時に既定動作がある文字列
a_value as any 必須 可 複数の型を意図的に受ける値

表の「指定」と「null」は別々の列です。ここを一列にまとめないことが、設計上の誤解を防ぎます。

第2章 nullableは値の範囲を表す

2.1 元の型へnullを加える

nullableは、指定した型にnullを加えた型を表します。

text
= 文字列
 
nullable text
= 文字列またはnull

たとえば、置換値として文字列またはnullを受け取る関数なら、次の宣言が自然です。

replace_value =
    (
        a_table as table,
        a_replacement as nullable text
    ) as table =>
        ...

a_replacementは処理を成立させるために必ず指定します。ただし、指定する値としてnullを選べます。

Result =
    replace_value(
        Source,
        null
    )

したがって、nullableは「指定しなくてもよい」という意味ではありません。引数の存在ではなく、その引数へ格納できる値の範囲を表します。

2.2 nullを許す理由を説明できるか

型をnullableへ広げる前に、nullが正常な入力かを確認します。

  • 置換値としてnullを指定する

  • 上限がないことをnullで表す

  • 見つからないことを正常な結果としてnullで返す

  • 外部ソースを使わないことをnullで表す

一方、本来必要な値が欠けているのに関数を止めないためだけにnullableへ広げると、異常が後段へ伝わります。「nullを受け入れる」と「欠損を黙って無視する」は別の判断です。

2.3 nullable anyは情報を増やさない

公式仕様では、type nullable anyとanyは同等です。すべての値を受け入れるanyへ、さらにnullを加えても範囲は広がりません。

// 意味は増えない
a_value as nullable any
 
// 通常はこちらで十分
a_value as any

反対に、anynonnullはnull以外のすべての値を表します。複数種類の値を許しつつnullだけを除外したい、という明確な契約がある場合に検討できます。

第3章 optionalは呼び出し方を表す

3.1 省略時のnullを関数内で既定値へ変換する

optionalは、関数を呼び出すときに、その引数を渡さなくてもよいことを表します。

get_column =
    (
        a_table as table,
        optional a_column_name as text
    ) as list =>
        let
            column_name =
                if a_column_name = null
                then "Name"
                else a_column_name,
 
            out =
                Table.Column(
                    a_table,
                    column_name
                )
        in
            out

呼び出し側が列名を省略すると、既定の"Name"列を使います。

DefaultValues =
    get_column(
        Source
    )

列名を指定すれば、別の列を取得できます。

CodeValues =
    get_column(
        Source,
        "Code"
    )

Mでは、関数宣言へ既定値を直接割り当てるのではなく、省略時に入るnullを関数本体で実際の既定値へ変換する形が基本です。

actual_value =
    if a_argument = null
    then default_value
    else a_argument

3.2 optionalは既定動作があるときだけ使う

省略可能にできることと、省略可能にすべきことは別です。次のように、一文で説明できる自然な既定動作がある場合にoptionalが候補になります。

列名を省略
→ "Name"列を使う
 
ワークブックソースを省略
→ 現在のワークブックを使う
 
optionsを省略
→ 空の設定recordとして扱う

既定動作が説明しにくい場合や、利用者によって期待が分かれる場合は、必須引数のほうが安全です。

3.3 optional引数は必須引数の後ろへ置く

M言語仕様では、必須引数が先、optional引数が後です。

(
    a_table as table,
    a_key as record,
    optional a_options as record
) =>
    ...

次の順序は構文上認められません。

(
    optional a_options as record,
    a_table as table
) =>
    ...

関数の引数は、概念的にも次の順に並べると読みやすくなります。

処理対象
↓
処理を決める必須条件
↓
省略可能な設定

第4章 引数インターフェースを決める

4.1 最初に処理成立の必須条件を決める

処理対象、検索キー、変換規則など、処理の意味を決める値は必須にします。

table_lookup =
    (
        a_table as table,
        a_key as record
    ) as record =>
        ...

a_tableまたはa_keyがなければ、どこから何を検索するのか決まりません。このような引数へ、呼び出しやすさだけを理由にoptionalを付けるべきではありません。

4.2 次に省略時の既定動作を決める

引数がなくても処理を定義できるなら、省略時に何をするかを決めます。判断基準は、既定動作が次の条件を満たすかです。

  1. 一文で説明できる。

  2. 利用者によって期待が大きく分かれない。

  3. 環境が変わっても意味が不安定にならない。

  4. 省略したことが重大な誤処理を隠さない。

たとえば、「列名を省略したら先頭列を使う」は便利そうでも、列順が変わると結果が変わります。「Name列を使う」のように、既定動作が明示的で安定しているほうが安全です。

4.3 nullが正常値か異常値かを決める

引数は必須でも、nullが処理上の意味を持つ場合はnullableを使います。一方、nullを受け取っても処理を定義できないなら、型を広げる必要はありません。

nullが正常値
→ nullableを検討する
 
nullが入力不備
→ 非nullableを基本にし、入口でエラーにする

エラーを早く出すことは、利用者に不親切とは限りません。関数の入口で前提違反が分かるほうが、後段で原因不明のエラーになるより修正しやすくなります。

4.4 判断手順を固定する

各引数について、次の順に判断します。

その引数が処理成立に必要か
↓
省略時の自然な既定動作があるか
↓
nullが正当な値か
↓
省略と明示的nullを同じ意味にしてよいか
↓
設定が増えるならoptions recordにするか

型を先に書いてから意味を後付けするのではなく、処理の契約を決めてから型とoptionalへ翻訳します。

第5章 省略と明示的なnullを扱う

5.1 単純なoptional引数では区別できない

次の二つの呼び出しを考えます。

some_function()
some_function(null)

単純なoptional引数では、どちらも関数内の引数値がnullになります。したがって、次の二つを値だけから区別できません。

呼び出し側が何も指定しなかった
 
呼び出し側が意図的にnullを指定した

両者を同じ意味にしてよいなら問題はありません。違う意味にしたい場合は、別のインターフェースが必要です。

5.2 options recordのフィールド有無で区別する

「未指定」と「null指定」を区別したいなら、options recordのフィールド有無を使います。

resolve_column =
    (
        optional a_options as record
    ) as nullable text =>
        let
            options =
                if a_options = null
                then []
                else a_options,
 
            has_column =
                Record.HasFields(
                    options,
                    "Column"
                ),
 
            out =
                if has_column
                then options[Column]
                else "Name"
        in
            out

フィールドがなければ既定値を使います。

resolve_column()
// "Name"

フィールドが存在し、その値がnullなら、明示的なnullとして扱えます。

resolve_column(
    [
        Column = null
    ]
)
// null

つまり、optional値ではなくrecordの構造へ意図を持たせます。

5.3 任意設定が増える場合もrecordへまとめる

省略可能な引数が増え続ける場合は、引数を横へ増やすより、options recordへまとめます。

transform_table =
    (
        a_table as table,
        optional a_options as record
    ) as table =>
        let
            options =
                if a_options = null
                then []
                else a_options,
 
            culture =
                Record.FieldOrDefault(
                    options,
                    "Culture",
                    "ja-JP"
                ),
 
            missing_field =
                Record.FieldOrDefault(
                    options,
                    "MissingField",
                    MissingField.Error
                ),
 
            out =
                ...
        in
            out

呼び出し側は必要な設定だけを指定できます。

Result =
    transform_table(
        Source,
        [
            Culture = "ja-JP"
        ]
    )

options recordには、引数増加を抑えるだけでなく、設定名を呼び出し側へ残せる利点があります。ただし、フィールド名の入力ミスを黙って無視する設計は避け、必要に応じて許可フィールドの検査を加えます。

第6章 実務の関数へ当てはめる

6.1 ソースを省略できる関数

次は、binaryを渡したときは外部ワークブックを読み、引数を省略したときは現在のワークブックを使う設計例です。

workbook_from =
    (
        optional a_source as binary
    ) as table =>
        let
            out =
                if a_source = null
                then Excel.CurrentWorkbook()
                else Excel.Workbook(
                    a_source,
                    null,
                    true
                )
        in
            out

このインターフェースでは、省略時の意味を一文で説明できます。

a_sourceを省略またはnull指定
→ 現在のワークブックを使う
 
binaryを指定
→ 外部ワークブックを使う

省略と明示的なnullを区別する必要もありません。そのため、optional a_source as binaryは自然な設計です。ただし、二つのExcel関数が返すテーブルの列構造は同一ではないため、実際のライブラリでは後段で共通コンテキストへ正規化する責務も必要です。

6.2 処理対象と取得条件が必須の関数

table_from =
    (
        a_context as table,
        a_resource as record
    ) as table =>
        ...

a_contextはどのワークブックコンテキストから探すか、a_resourceはどのテーブル、シート、定義名を取得するかを表します。どちらかがなければ処理対象を決められません。

この二つにはoptionalを付けません。nullを正当な入力として扱わないなら、nullableにもする必要はありません。

6.3 対象は必須、列名だけ省略可能な関数

env_read =
    (
        a_table as table,
        optional a_name_column as text,
        optional a_value_column as text
    ) as record =>
        let
            name_column =
                if a_name_column = null
                then "Name"
                else a_name_column,
 
            value_column =
                if a_value_column = null
                then "Value"
                else a_value_column,
 
            out =
                Record.FromList(
                    Table.Column(a_table, value_column),
                    Table.Column(a_table, name_column)
                )
        in
            out

設定tableは処理対象そのものなので必須です。一方、名前列と値列には安定した既定値があります。

DefaultEnv =
    env_read(
        SettingsTable
    )
JapaneseEnv =
    env_read(
        SettingsTable,
        "項目",
        "設定値"
    )

必須の対象と省略可能な設定が、関数シグネチャから読み取れます。

第7章 null、空文字、戻り値を分ける

7.1 nullと空文字は別の値

nullable textが受け入れるnullと、空文字""は別の値です。

null
= 値が存在しない
 
""
= 長さ0のtextが存在する
 
"   "
= 空白文字を含むtextが存在する

次の処理は、nullだけを既定値へ置き換えます。

column_name =
    if a_column_name = null
    then "Name"
    else a_column_name

空文字や空白文字も未指定相当として扱うなら、別の規則を明示します。

column_name =
    if a_column_name = null
        or Text.Trim(a_column_name) = ""
    then "Name"
    else Text.Trim(a_column_name)

「空文字を未指定とみなす」ことは、nullableやoptionalの機能ではありません。その関数が独自に定める正規化ルールです。型の問題と空白判定の問題を混同しないことが重要です。

7.2 戻り値にもnullableを使える

nullableは引数だけでなく、戻り値型にも使えます。

find_name =
    (
        a_table as table,
        a_key as text
    ) as nullable text =>
        ...

これは、関数がtextまたはnullを返すという契約です。ただし、検索結果が存在しない場合にnullを返すか、エラーにするか、空recordを返すかは別の設計判断です。

見つからないことが正常系
→ nullableな戻り値を検討する
 
見つからないことがデータ異常
→ 非nullableな戻り値とエラーを検討する

たとえば、「必ず一件だけ存在する」ことを要求する検索なら、戻り値をrecordとして、0件や複数件をエラーにするほうが契約を明確にできます。

7.3 入力の許容と異常時の方針を分ける

型がnullableであることは、nullを受け取った後に必ず処理を続けるという意味ではありません。nullを特別な指定として解釈することも、説明付きのエラーへ変えることもできます。

入力型は「何を受け取れるか」を表します。関数本体は「受け取った値をどう扱うか」を表します。両者を分けて設計すると、エラー回避だけを目的に型を広げる必要がなくなります。

第8章 関数の型情報を観察する

8.1 Value.Typeで関数型を取得する

Mの関数は、引数名、引数型、戻り値型を含む関数型を持ちます。Value.Typeで関数型を取得し、Type関数でその構造を観察できます。

let
    FunctionA =
        (
            a_name as text
        ) as text =>
            a_name,
 
    FunctionB =
        (
            a_name as nullable text
        ) as nullable text =>
            a_name,
 
    FunctionC =
        (
            optional a_name as text
        ) as nullable text =>
            a_name,
 
    out =
        [
            FunctionA = Value.Type(FunctionA),
            FunctionB = Value.Type(FunctionB),
            FunctionC = Value.Type(FunctionC)
        ]
in
    out

値そのものを呼び出すテストだけでなく、型情報を観察すると、関数の公開契約を機械的に確認できます。

8.2 引数型と必須数を取り出す

Type.FunctionParametersは、引数名と引数型をrecordとして返します。Type.FunctionRequiredParametersは、呼び出しに必要な引数の最小数を返します。

let
    Sample =
        (
            a_table as table,
            optional a_column_name as text
        ) as list =>
            Table.Column(
                a_table,
                if a_column_name = null
                then "Name"
                else a_column_name
            ),
 
    FunctionType =
        Value.Type(Sample),
 
    Parameters =
        Type.FunctionParameters(
            FunctionType
        ),
 
    RequiredCount =
        Type.FunctionRequiredParameters(
            FunctionType
        ),
 
    out =
        [
            Parameters = Parameters,
            RequiredCount = RequiredCount
        ]
in
    out

この例では、Parametersの概念上の結果は次の形になります。

[
    a_table = type table,
    a_column_name = type nullable text
]

RequiredCountは1です。つまり、a_tableは必須で、a_column_nameは省略できます。optional a_column_name as textが型情報ではnullable textとして現れることも確認できます。

8.3 観察できる情報とできない情報を分ける

引数型のrecordだけでは、どの引数がoptionalかを完全には判別できません。必須のnullable textも、optionalなtextも、Type.FunctionParametersではnullable textとして現れ得るからです。

そのため、少なくとも次の二つを組み合わせます。

  1. Type.FunctionParametersで引数名と型を取得する。

  2. Type.FunctionRequiredParametersで先頭から何個が必須かを取得する。

M言語仕様ではoptional引数は必須引数の後ろに並ぶため、引数順と必須数を合わせれば、公開関数のシグネチャを説明できます。これは、関数一覧やドキュメントを自動生成する仕組みにもつながります。

第9章 避けたい設計と公開前チェック

9.1 何でもoptionalにする

(
    optional a_table as table,
    optional a_key as any,
    optional a_column as text
) =>
    ...

一見呼び出しやすそうですが、何も指定しない呼び出しの意味を定義できません。必須条件まで省略可能にすると、関数本体に大量の入力検査が必要になり、責務も曖昧になります。

9.2 何でもanyにする

(
    a_source as any,
    a_resource as any
) =>
    ...

anyは複数種類の値を意図的に受け付ける場合には有効です。しかし、単に型を決めるのが面倒という理由で使うと、利用者は関数本体や別の説明を読まなければ、何を渡せるのか判断できません。

9.3 optionalなのに省略時の意味が不明

(
    optional a_key as text
) =>
    ...

検索キーを省略した場合に、先頭行を返すのか、全件を返すのか、エラーにするのかが明確でないなら、検索キーは必須にすべきです。

9.4 nullをエラー回避のためだけに許す

本来必要な値が欠けているのに、関数を止めないためだけにnullableへ広げると、異常が後段まで伝わります。nullを正当な値として扱うなら、その意味と処理方針を説明します。正当でないなら、入口で契約違反として扱います。

9.5 実務向け判断表

状況 推奨する宣言または設計
必須でnull不可 a_value as text
必須だがnullに意味がある a_value as nullable text
省略時に明確な既定動作がある optional a_value as text
値の型を意図的に限定しない a_value as any
任意設定が複数ある optional a_options as record
未指定と明示的nullを区別したい options recordとRecord.HasFields
引数がなければ処理対象が決まらない 必須・非nullableを基本にする

9.6 設計時のチェックリスト

関数を公開する前に、各引数について次を確認します。

  1. この引数がなくても処理を定義できるか。

  2. 省略時の既定動作を一文で説明できるか。

  3. nullは正当な入力値か、それとも異常値か。

  4. 省略と明示的なnullを同じ意味にしてよいか。

  5. 空文字や空白文字も未指定として扱うか。

  6. anyより具体的な型を指定できないか。

  7. optional引数は必須引数より後ろにあるか。

  8. optional引数が増えすぎていないか。

  9. options recordへまとめたほうが拡張しやすくないか。

  10. 戻り値のnullは正常系か、データ異常か。

  11. 型情報から必須数と引数型を確認できるか。

まとめ 二つの軸から関数の契約を作る

nullableとoptionalは、似た目的の修飾子ではありません。

nullable
= その場所にnullという値を置ける
 
optional
= 呼び出し側がその引数を渡さなくてもよい

関数インターフェースでは、次の順に判断します。

  1. その引数が処理成立に必要か。

  2. 省略時の自然で安定した既定動作があるか。

  3. nullが正当な値か。

  4. 省略と明示的なnullを同じ意味にしてよいか。

  5. 設定が増えるならoptions recordにするか。

optionalは、付けられるから付けるものではありません。明確で安全な既定動作があるときに使います。nullableも、エラーを避けるために広げるものではありません。nullが契約上の正当な値であるときに使います。

二つの軸を分けて考えれば、関数の必須条件、既定動作、許容値がシグネチャから読めるようになります。それが、呼び出しやすさと壊れにくさを両立したM関数のインターフェースです。

参考資料

  • Microsoft Learn「M Language Functions」

  • https://learn.microsoft.com/en-us/powerquery-m/m-spec-functions

  • Microsoft Learn「M Language types」

  • https://learn.microsoft.com/en-us/powerquery-m/m-spec-types

  • Microsoft Learn「Type.FunctionParameters」

  • https://learn.microsoft.com/en-us/powerquery-m/type-functionparameters

  • Microsoft Learn「Type.FunctionRequiredParameters」

  • https://learn.microsoft.com/en-us/powerquery-m/type-functionrequiredparameters

  • Microsoft Learn「Record.FieldOrDefault」

  • https://learn.microsoft.com/en-us/powerquery-m/record-fieldordefault

  • Microsoft Learn「Record.HasFields」

  • https://learn.microsoft.com/en-us/powerquery-m/record-hasfields

出典メモ

  • 元原稿: PowerQuery_nullableとoptionalの引数設計.md

  • 元原稿の中心論点、コード例、判断基準を保持し、公開記事として構成と表現を再編集しました。

  • optional引数の省略時動作、引数順、nullable型、nullable any、関数型情報はMicrosoft LearnのM言語仕様と関数リファレンスで確認しました。

  • 公式資料の確認日: 2026年8月10日