Power Query Mでクロージャーを理解する

LWP | Power Query Mでクロージャーを理解する

LWP TECHNICAL ARTICLE | 166

Power Query Mでクロージャーを理解する

レキシカルスコープと「関数+環境」からFunction.Fromまでつなげる

Copyright © 2026 LWP 山中 一弘

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

記事要約

クロージャーは、関数が定義された場所の環境を伴って保持される仕組みです。Mでは関数も値なので、外側の関数から内側の関数を返し、後から外側の変数を参照できます。

本記事では、スコープ、環境、第一級関数、高階関数を順に整理し、クロージャーを単なる記憶機能ではなく『関数値と、その関数が名前を解決するための環境』として捉えます。最後にFunction.FromとExpression.Evaluateを比較し、同じ“環境”という言葉でも役割が異なることを確認します。

本記事の対象とゴール

想定読者

  • Mのlet式と独自関数を使える方

  • クロージャーをコードの動きから理解したい方

  • Function.FromやExpression.Evaluateとの関係を整理したい方

本記事で得られること

  1. レキシカルスコープと環境を区別できます。

  2. 高階関数とクロージャーを区別できます。

  3. Mで環境を保持する関数を作り、動きを説明できます。

目次

  • 第1章 クロージャーを理解するための前提

  • 第2章 クロージャーはどのように成立するのか

  • 第3章 M言語の機能とクロージャー

  • 第4章 まとめ

  • まとめ クロージャーを読むときの3つの確認点

第1章 クロージャーを理解するための前提

第1節 最初にクロージャーを見てみる

第1項 M言語による最小例

まず、次のMコードを見てください。

let
    MakeAdder = (x as number) =>
        (y as number) => x + y,
 
    Add10 = MakeAdder(10),
 
    Result = Add10(5)
in
    Result

結果は次のようになります。

15

このコードでは、最初に MakeAdder という関数を定義しています。

MakeAdder = (x as number) =>
    (y as number) => x + y

この関数は数値を返すのではありません。

新しい関数を返しています。

MakeAdder(10) を実行すると、概念的には次のような関数が生成されます。

(y as number) => 10 + y

その関数を Add10 に保存しています。

その後、

Add10(5)

を実行するため、結果は15になります。

第2項 最初に持つべき疑問

ここで非常に重要な疑問があります。

Add10 = MakeAdder(10)

の実行が終了した時点で、MakeAdder の処理そのものは終了しています。

それにもかかわらず、その後で、

Add10(5)

を実行すると、内部では依然として、

x = 10

という値が使われています。

つまり、

「MakeAdder の実行は終わっているのに、なぜ x = 10 を覚えているのでしょうか」

という疑問が生まれます。

これが、クロージャーを理解するための出発点です。

第3項 クロージャーの最小定義

クロージャーは、次のように理解すると分かりやすくなります。

クロージャー
=
関数
+
その関数が参照する環境

英語では、概念的に次のように表せます。

Closure
=
Function
+
Lexical Environment

つまり、クロージャーは単なる関数ではありません。

関数本体と、その関数が利用する外側の変数を含む環境を一緒に保持したものです。

第2節 スコープと環境

第1項 変数はどこから探されるのか

次のコードを考えます。

let
    x = 10,
 
    f = (y as number) =>
        x + y,
 
    Result = f(5)
in
    Result

f の引数として定義されているのは y だけです。

それにもかかわらず、

x + y

と書くことができます。

x は関数の外側で定義されています。

概念的には、変数を次のように探します。

f のローカル環境
    ↓
y は見つかる
 
x は見つからない
    ↓
外側の let 環境
    ↓
x = 10

このように、関数は自分の外側にある環境を参照できます。

第2項 レキシカルスコープ

ここで重要なのが、

レキシカルスコープ(Lexical Scope)

です。

レキシカルスコープとは、

関数がどこから呼び出されたかではなく、どこで定義されたかによって、参照できる変数が決まる仕組みです。

たとえば、

let
    x = 10,
 
    f = () => x
in
    f

という関数 f は、x = 10 が存在する場所で定義されています。

そのため、後から f を呼び出しても、定義時の x を参照できます。

クロージャーは、このレキシカルスコープを前提として成立します。

第3項 環境とは何か

ここでいう「環境」とは、

その場所から参照できる名前と値の集合

と考えると分かりやすいです。

たとえば、

let
    x = 10,
    y = 20,
    f = (z) => x + y + z
in
    f

なら、f が定義された場所には、

x = 10
y = 20

という名前と値があります。

このような情報が環境です。

クロージャーでは、関数が必要とする環境を、関数本体と一緒に保持します。

第2章 クロージャーはどのように成立するのか

第1節 関数を値として扱う

第1項 Mでは関数も値です

M言語では、関数は特別な命令ではなく、値として扱うことができます。

たとえば、

let
    f = List.Sum,
    Result = f({1, 2, 3})
in
    Result

と書けます。

ここで、

f = List.Sum

は List.Sum を実行しているわけではありません。

List.Sum という関数そのものを f に格納しています。

結果として、

f({1, 2, 3})

は、

List.Sum({1, 2, 3})

と同じ意味になります。

第2項 関数を返す関数

Mでは、関数から別の関数を返すこともできます。

let
    MakeAdder = (x) =>
        (y) => x + y
in
    MakeAdder

MakeAdder は、数値を受け取って新しい関数を返します。

たとえば、

Add10 = MakeAdder(10)

なら、Add10 は関数になります。

この時点で、

x = 10

という情報も、Add10 が利用できるように保持されます。

第3項 高階関数とは何か

関数を、

  • 引数として受け取る

  • 戻り値として返す

関数を、高階関数と呼びます。

たとえば、

let
    Apply = (f as function, x as any) =>
        f(x)
in
    Apply

は関数を引数として受け取るので、高階関数です。

ただし、

高階関数とクロージャーは同じものではありません。

整理すると、

高階関数
=
関数を扱う関数

です。

一方、

クロージャー
=
関数 + 環境

です。

ただし、高階関数が新しい関数を生成するときに、クロージャーが作られることはよくあります。

第2節 クロージャーの仕組み

第1項 関数と環境

先ほどの例をもう一度見ます。

let
    MakeAdder = (x) =>
        (y) => x + y,
 
    Add10 = MakeAdder(10)
in
    Add10

Add10 を概念的に分解すると、次のようになります。

Add10
│
├─ 関数本体
│    (y) => x + y
│
└─ 環境
     x = 10

つまり、Add10 は単なる、

(y) => x + y

ではありません。

その x が何であるかを知るための環境も持っています。

第2項 外側の関数が終了しても変数が残る理由

通常の関数呼び出しだけを考えると、

MakeAdder開始
↓
x = 10
↓
関数を返す
↓
MakeAdder終了

となります。

普通なら、MakeAdder が終了した時点で x は不要に見えます。

しかし、返された関数の中には、

x + y

があります。

つまり、後から x が必要になります。

そのため、Mの実行環境は、返された関数が必要とする x = 10 を保持します。

これが、

関数の実行終了後も変数を参照できるように見える理由です。

第3項 複数のクロージャー

次のコードを考えます。

let
    MakeAdder = (x) =>
        (y) => x + y,
 
    Add10 = MakeAdder(10),
    Add20 = MakeAdder(20),
 
    A = Add10(5),
    B = Add20(5)
in
    {A, B}

結果は、

{15, 25}

です。

概念的には、

Add10
 ├─ 関数本体
 └─ x = 10
 
Add20
 ├─ 関数本体
 └─ x = 20

となります。

関数本体は同じですが、保持している環境が違います。

そのため、それぞれ別のクロージャーとして動作します。

第3節 クロージャーによる状態保持

第1項 状態を持つ関数

一般的なプログラミング言語では、クロージャーを使って内部状態を保持できます。

ただし、Mは基本的に不変値を中心とした関数型言語なので、JavaScriptのように内部変数を直接書き換えるカウンターを作る、という理解は適切ではありません。

Mではむしろ、

値を保持した関数を生成する

という方向で理解するとよいでしょう。

たとえば、

let
    MakeMultiplier = (rate as number) =>
        (value as number) =>
            value * rate,
 
    Tax10 = MakeMultiplier(1.1),
 
    Result = Tax10(1000)
in
    Result

結果は、

1100

です。

Tax10 は、

rate = 1.1

という状態に相当する値を保持しています。

第2項 オブジェクトとの類似

クロージャーは、

関数
+
保持された値

という構造を持っています。

一方、オブジェクトは、

データ
+
メソッド

という構造を持ちます。

そのため両者には類似性があります。

ただしMでは、オブジェクト指向的な状態変更よりも、

関数と不変の環境を組み合わせる

という考え方の方が自然です。

第3章 M言語の機能とクロージャー

第1節 Mにおけるクロージャー

第1項 Mでクロージャーを作る

クロージャーの最小例は次の形です。

let
    MakeAdder = (x as number) =>
        (y as number) =>
            x + y,
 
    Add100 = MakeAdder(100),
 
    Result = Add100(50)
in
    Result

結果は、

150

です。

Add100 は、

関数本体
(y) => x + y
 
環境
x = 100

を持つものとして理解できます。

第2項 関数をレコードに格納する

Mでは関数をレコードのフィールドにも格納できます。

たとえば、

let
    Lib = [
        Add = (a, b) => a + b,
        Multiply = (a, b) => a * b
    ],
 
    Result = Lib[Add](10, 20)
in
    Result

結果は30です。

これは、Mで共通ライブラリを作るときにも利用できます。

第3項 共通ライブラリとの関係

たとえば TLib を次のようなレコードとして持つことができます。

[
    path_join = ...,
    table_from = ...,
    env_read = ...,
    table_lookup = ...
]

この仕組みが成立するのは、Mでは関数が値として扱えるからです。

つまり、

ファーストクラス関数
↓
関数をレコードに格納
↓
ライブラリ化

という流れになっています。

クロージャー、高階関数、ライブラリ設計は別々の話ではなく、かなり近い位置にあります。

第2節 Function.From

第1項 Function.From の基本

次の例を考えます。

let
    f =
        Function.From(
            type function (
                a as number,
                b as number
            ) as number,
            List.Sum
        ),
 
    Result = f(2, 3)
in
    Result

結果は、

5

です。

元の List.Sum は、

List.Sum({2, 3})

のように、リストを1つ受け取る関数です。

しかし、Function.From を使うと、

f(2, 3)

という2引数関数として使えるようになります。

第2項 関数アダプターとして理解する

概念的には、次のような関数を生成していると考えると分かりやすいです。

f = (a, b) =>
    List.Sum({a, b})

つまり、

Listを1つ受け取る関数
        ↓
   Function.From
        ↓
複数引数を受け取る関数

という変換を行っています。

この意味で、Function.From は、

関数アダプター

と考えることができます。

第3項 なぜクロージャーのように見えるのか

次のコードを見ます。

let
    Original =
        (list as list) =>
            List.Sum(list),
 
    Converted =
        Function.From(
            type function (
                a as number,
                b as number
            ) as number,
            Original
        ),
 
    Result = Converted(10, 20)
in
    Result

Converted は、生成された後にも Original を利用する必要があります。

概念的には、

Converted =
    (a, b) =>
        Original({a, b})

と考えられます。

そのため、

Converted
│
├─ 関数本体
│    (a, b) => Original({a, b})
│
└─ 環境
     Original = 元の関数

というクロージャーのモデルで理解すると非常に自然です。

ただし、用語としては、

Function.From 自体はクロージャーではありません。

Function.From は、

  • 関数を受け取り

  • 新しい関数を返す

ので、高階関数です。

さらに、既存関数の呼び出し方を変えるので、関数アダプターでもあります。

したがって、

Function.From
=
高階関数
+
関数アダプター

と考えるのが基本です。

その戻り値を理解する際に、クロージャーの考え方が役立ちます。

第3節 Expression.Evaluate と環境

第1項 式と評価環境

Mには、

Expression.Evaluate(
    expression,
    environment
)

という関数があります。

たとえば、

let
    Result =
        Expression.Evaluate(
            "x + 1",
            [x = 10]
        )
in
    Result

結果は11です。

ここでは、

式
"x + 1"
 
評価環境
x = 10

を組み合わせて評価しています。

第2項 クロージャーとの共通点と違い

クロージャーでは、

関数
+
定義時の環境

を保持します。

一方、Expression.Evaluate では、

式
+
評価時に渡した環境

を組み合わせます。

したがって、両者は同じものではありません。

ただし、

コードだけでは意味が決まらず、どの環境で評価されるかが重要

という点では非常によく似ています。

比較すると、次のようになります。

クロージャー
 
関数
+
定義時の環境
↓
後から実行
Expression.Evaluate
 
式
+
指定した評価環境
↓
評価

第3項 #shared、TLib とのつながり

たとえば、共通ライブラリ TLib を動的評価コードから使いたい場合、

Expression.Evaluate(
    code,
    Record.Combine({
        #shared,
        [TLib = TLib]
    })
)

のように評価環境を渡せます。

ここでは、

  • #shared

  • TLib

という環境を明示的に評価式へ渡しています。

つまり、

クロージャー
Function.From
Expression.Evaluate
#shared
TLib

は、すべて完全に同じ機能ではありません。

しかし、

「Mでは関数や式を理解するとき、コードそのものだけでなく環境が重要である」

という共通した考え方で整理できます。

第4章 まとめ

第1節 重要概念の整理

第1項 クロージャー

クロージャーとは、

関数
+
その関数が定義された環境

です。

より正確には、関数が参照するレキシカル環境を伴った関数値と考えられます。

第2項 高階関数

高階関数とは、

  • 関数を引数として受け取る

  • 関数を返す

関数です。

Function.From は高階関数です。

第3項 環境

環境とは、

ある場所から参照できる名前と値の集合

です。

たとえば、

x = 10
f = List.Sum
TLib = 共通ライブラリ

などが環境を構成します。

第2節 最終理解

第1項 一言で表すクロージャー

クロージャーは、次のように覚えると分かりやすいです。

クロージャーとは、「関数が、自分が定義された環境を背負って持ち歩く仕組み」です。

第2項 理解の流れ

クロージャーは次の順序で理解すると自然です。

変数があります
↓
変数にはスコープがあります
↓
Mはレキシカルスコープを持ちます
↓
Mでは関数も値です
↓
関数から関数を返せます
↓
返された関数が外側の変数を参照します
↓
外側の関数は終了します
↓
しかし外側の変数がまだ必要です
↓
関数と環境を一緒に保持します
↓
これがクロージャーです

第3項 次に学ぶテーマ

クロージャーを理解した後は、次の順番で学ぶと、M言語全体の理解につながります。

  1. 高階関数

  2. ラムダ式

  3. each の正体

  4. _ のスコープ

  5. 部分適用

  6. カリー化との違い

  7. Function.From

  8. Function.Invoke

  9. Expression.Evaluate

  10. #shared

  11. environmentレコード

  12. TLibのような共通ライブラリ

  13. 再帰関数

  14. 遅延評価

  15. Mの評価モデル

まとめ クロージャーを読むときの3つの確認点

1. クロージャーは「関数の中に関数を書くこと」ではありません

本質は、

関数 + 環境

です。

2. クロージャーを理解するにはレキシカルスコープが必要です

「この変数をどこから探すのか」というルールが前提になります。

3. Mの関数を見るときは、関数本体だけを見てはいけません

常に、

この関数は何を実行するのか

と同時に、

この関数はどの環境を参照しているのか

を確認します。

この視点を持つと、Function.From、Expression.Evaluate、#shared、共通ライブラリなど、M言語の少し高度な機能も一つの流れとして理解できるようになります。

出典メモ

  • Microsoft Learn『Power Query M formula language specification - Functions』 https://learn.microsoft.com/en-us/powerquery-m/m-spec-functions

  • Microsoft Learn『Basic concepts』 https://learn.microsoft.com/en-us/powerquery-m/m-spec-basic-concepts

  • Microsoft Learn『Function.From』 https://learn.microsoft.com/en-us/powerquery-m/function-from

  • Microsoft Learn『Expression.Evaluate』 https://learn.microsoft.com/en-us/powerquery-m/expression-evaluate