0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PowerApps 開発メモ #1 式? 〜 PowerFxの「何もしない」はなぜ Blank() なのか? 〜

0
Posted at

IfError から Notify を削ろうとしたら構文エラーになる

ストアドプロシージャの実行を IfError で受け取り、エラー時に Notify を表示していました。
また、呼び出しと値の受け取りが問題なければデータ処理を実行しています。

IfError(
    // ストアドプロシージャ呼び出し → 結果を変数に格納
    UpdateContext({
        locSaveDataSpResults: First(
            sqlConnector.dboSaveDataSp({
                UserId: gblUserId,
                SaveData: gblSaveDataText,
                ...
            }).ResultSets.Table1
        )
    });

    // 戻り値をチェック
    If(
        locSaveDataSpResults.Result = 1,
        // 成功時の処理...
    ),

    // エラーが発生した場合
    Notify("処理中にエラーが発生しました", NotificationType.Error)
)

ある時、この Notify が冗長だと気づきました。ストアドプロシージャ側でシステムエラーが発生した場合、Power Apps は自動的にエラーメッセージを表示します。アプリ側でさらに「処理中にエラーが発生しました」と重ねて通知しても、ユーザーに具体的な対処を案内して復帰できるわけでもない。

そこでシンプルに Notify(...) を削除しようとしました。

IfError(
    UpdateContext({
        locSaveDataSpResults: First(
            sqlConnector.dboSaveDataSp({
                UserId: gblUserId,
                SaveData: gblSaveDataText,
                ...
            }).ResultSets.Table1
        )
    });
    If(
        locSaveDataSpResults.Result = 1,
        // 成功時の処理...
    )
    // ← エラー時: 何もしない
)

構文エラーになりました。

IfError は第2引数が必須です。コメントだけ書いても引数として認識されません。省略という選択肢がない。

では「何もしない」を明示的に書くにはどうすればいいのか? AIに聞いたところ、Blank() を使うと教えてもらいました。

IfError(
    UpdateContext({
        locSaveDataSpResults: First(
            sqlConnector.dboSaveDataSp({
                UserId: gblUserId,
                SaveData: gblSaveDataText,
                ...
            }).ResultSets.Table1
        )
    });
    If(
        locSaveDataSpResults.Result = 1,
        // 成功時の処理...
    ),

    Blank()  // エラー時: 何もしない(空の値を返す)
)

動きました。しかしなぜ Blank() で「何もしない」になるのか?

Notify(...) なら「通知を出す」という処理なので、書くことに違和感がない。でも Blank() そのものは「空」。これが「何もしない処理」として機能するのか?

Ifの引数がずれるバグから気づいたこと

この疑問がもっと切実になったのは、あるバグに遭遇したときでした。

保存ボタンのOnSelectにこんなコードがあったとします。

If(
    // 変更データがない場合
    CountRows(colChangeData) = 0,
    Notify("保存対象のデータがありません", NotificationType.Error),

    // ユーザーが特定されていない場合
    IsBlank(gblUserId),
    //Notify("ユーザIDが設定されていません", NotificationType.Error),

    // バリデーションOK → 保存処理に進む
    With({...}, ...)
)

2つ目の Notify(...) をコメントアウトしているだけに見えますが、実はこれ、意図通りに動いていませんでした

PowerFxの IfIf(条件1, 結果1, 条件2, 結果2, ..., デフォルト) という形式です。結果部分をコメントアウトすると、引数の位置がずれます。

実際にパースされるとこうなります。

If(
    CountRows(colChangeData) = 0,                 // ← 条件1
    Notify("保存対象のデータがありません", ...),       // ← 結果1
    IsBlank(gblUserId),                           // ← 条件2(位置は合っている)
    With({...}, ...)                              // ← 結果2(!?)
                                                  //   本来はデフォルトのはず
)
// → gblUserIdがBlankのとき With が実行される(保存されてしまう)
// → 両条件がfalseの正常ケースでは Blank() が返り、保存されない

IsBlank(gblUserId) の位置はずれていないように見えますが、本来「デフォルト(全条件がfalseのとき)」のはずだった With({...}) が「条件2がtrueのときの結果」として解釈されてしまいます。意図と真逆の動きになります。

修正はこうです。

If(
    CountRows(colChangeData) = 0,
    Notify("保存対象のデータがありません", NotificationType.Error),

    IsBlank(gblUserId),
    Blank(),    // ← Blank() で枠を維持
    //Notify("ユーザIDが設定されていません", NotificationType.Error),

    With({...}, ...)  // ← デフォルト: 保存処理(意図通り)
)

Blank() を書くことで引数の位置が正しくなり、意図通りに動くようになりました。

でも……なぜこうなるのか? JavaScriptのように if (...) { } と空ブロックにすれば済む話ではないのか?

AIと深く理解してみる

この疑問をClaudeに投げてみたところ、「PowerFxの IfSwitch は、他言語のような制御構文(statement)ではなく、関数(expression)です」 と。

関数だから、各引数の位置には「値を返す式」が必要になる。Blank() は「何もしない処理」ではなく「空の値を返す式」で、引数の枠を埋めるために書いている、という説明でした。
なるほど。作法はわかりました。でも、「文と式の違い」って?「宣言的」「関数型」ってどういうことだ? 改めて自分で調べることにしました。

公式ドキュメントを見にいく

まず Microsoft Power Fx overview を読みました。

冒頭のあたりに、PowerFxは 「宣言型」 、そして 「関数型」 のプログラミング言語であるという記述があります。

正直、これだけ読んでも「だから何?」という感じです。でもこの2つの言葉が、掘り下げていくと全部つながってきます。

「宣言的」で「関数型」って、どういうこと?

一番わかりやすいのは、Excelとの比較です。

Excelのセルに =IF(A1>0, "正", "負") と書くとき、「A1が0より大きければ"正"を実行しろ」とは考えません。「A1が0より大きければ、このセルの値は"正"になる」と考えます。

これが 宣言的 ということです。「何をするか」ではなく「どういう状態であるか」を記述する。

そして =IF(...) は関数です。引数を受け取って、値を返す。「上から順に実行する手続き」ではなく、「入力に対して出力を返す関数の組み合わせ」でプログラムが構成される。これが 関数型 の発想ということです。

PowerFxはこのExcelの設計をそのまま引き継いでいます。だから IfSwitch も関数であり、必ず値を返します。

「制御構文」と「関数」の違い

ここがこの記事のポイントです。

JavaScriptの if制御構文(statement) です。「条件に応じて、処理の流れを分岐させる」ための命令であり、それ自体は値を返しません。

// JavaScript: if文は値を返さない
if (count === 0) {
    showError();   // ← 処理を実行する
}
// if文そのものを変数に入れることはできない

一方、PowerFxの If関数(expression) です。引数を受け取り、条件に応じた値を返します。

// PowerFx: If関数は値を返す
If(
    CountRows(colChangeData) = 0,
    false,           // ← If() の戻り値が false になる
    With({...}, ...) // ← If() の戻り値が With() の結果になる
)

関数なので、各引数の位置には「値を返す式」が入る必要があります。だからこそ、「何もしない」ケースでも Blank()false で枠を埋めないと、後ろの引数の位置がずれてしまうわけです。
最初に遭遇したバグの原因は、まさにここでした。

戻り値と副作用

関数は値を返す。これはJavaScriptを書いたことがあるので知ってます。でも Notify(...) を書いたときは通知が画面に出ます。「値を返す」だけじゃなく「何かが起きている」。この違いは何でしょうか。

プログラミングでは、関数が「値を返す」以外に外の世界に影響を与えることを 副作用(side effect) と呼びます。

Notify(...) は副作用のある関数です。値を返しつつ、画面にも通知を出す。だから「処理をしている」という実感があります。

一方、Blank()false は副作用がゼロです。値を返すだけで、画面にも変数にも何も影響しない。だから「何もしていない」と感じる。

でもPowerFxの視点では、どちらも同じ「式として値を返している」という行為です。If 関数の引数の枠に入っている、という点で Notify(...)Blank() も同等の存在です。

Blank() が「何もしない」と感じるのは自然な感覚ですが、正確には「副作用のない値を返している」ということですね。

falseBlank() の使い分け

ちなみに、実用上の違いはほぼありません。ただし:

  • Blank() → 「ここは意図的に何もしない」という意思表示として読みやすい
  • false → If/Switchの戻り値を後続で使う場合(バリデーション結果として返すなど)に意味が出る

コードの可読性を考えると、「何もしない」を表現するには Blank() の方が意図が伝わりやすいと思います。

フォールスルーって何?

最後にもう1つ。JavaScriptの switch 文を使ったことがある方は聞いたことがあるかもしれません。フォールスルー(fall-through) という挙動です。

// JavaScript: break を忘れるとフォールスルーする
switch (action) {
    case "init":
        doInit();
        // break を書き忘れた!
    case "applyFilter":
        applyFilter();  // ← "init" のときもここが実行されてしまう
        break;
    default:
        break;
}

JavaScriptの switch は制御構文なので、case に一致した地点から下に向かって処理が流れ落ちていきます(fall-through)。break を書いて明示的に止めないと、次のケースも実行されてしまう。これは有名なバグの温床です。

PowerFxの Switch にはこの問題がありません。

// PowerFx: 引数のペアで構造が決まる
Switch(
    locNavigationContext.action,
    "init",        UpdateContext({ locExeFlags: { InitUI: true, LoadData: true } }),
    "applyFilter", UpdateContext({ locExeFlags: { InitUI: false, LoadData: true } }),
    Blank()
)

関数なので、引数の位置で「条件」と「結果」のペアが構造的に決まっています。「条件に一致したら、対応する結果の式を評価して値を返す」という動きしかしない。フォールスルーという概念がそもそも存在しません。

break の書き忘れで意図しないケースが実行される、ということが言語レベルで起きない。これはPowerFxの Switch が関数であることのメリットの1つです。

ここまでを整理する

JavaScript PowerFx
If / Switch の性質 文(statement) 関数(expression)
「何もしない」の表現 空ブロック {} or 書かない Blank() で戻り値の枠を埋める
フォールスルーの罠 あり(break 忘れ) 原理的に起きない
false; の意味 副作用のない式文(linter警告) 「戻り値がfalse」という有効な式

公式ドキュメントへのリンク

この記事で触れた言語設計については、Microsoft公式ドキュメントでも解説されています。

  • Microsoft Power Fx overview — PowerFxが「汎用、厳密な型指定、宣言型、そして関数型」であるという設計思想
  • Power Fx expression grammar — 式の文法仕様。セミコロンが「チェーンセパレーター」として定義されている

おわりに

IfError の第2引数を消したら構文エラーになった。じゃあ Blank() でいいのか? でもなぜ Blank() が『何もしない』になるんだ?」という疑問が出発点でした。

AIに聞いたら「IfError は関数だから」と返ってきて、そこから「宣言的とは」「制御構文と関数の違いとは」「戻り値と副作用とは」「フォールスルーとは」を1つずつ調べていったら、PowerFxという言語の設計思想が少しずつ見えてきました。

PowerFxはExcel由来の式ベース言語で、IfSwitch も関数。だから引数の枠を埋める必要があるし、フォールスルーのような「知らないと踏む地雷」が言語レベルで排除されている。

他の言語(JavaScriptなど)を少しだけかじった私がPowerFxに入り、制御構文のつもりで書いて引数のずれに気づかない、ということが起きました。逆にプログラミング未経験でPowerAppsから入った方は、そもそもこの違いを意識する機会がない。

どちらの入口から来ても、「PowerFxのIf/Switchは関数である」 を一度理解しておくと、設計判断の見通しがずっと良くなると感じました。


PowerApps 開発メモ シリーズ

  • #1: PowerFxの「何もしない」はなぜ Blank() なのか?(本記事)
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?