OSS化されたPower Fxで遊んでみる
(この記事は、2021年にQiitaに上げた記事の再投稿です。内容が古くなっている可能性がありますのでご了承ください。)
はじめに
Microsoft Power Apps等に組み込まれているプログラミング言語Power Fxが11月にOSS化されました1。
microsoft/Power-Fx: Power Fx low-code programming language
公式サンプルにREPLがあったので、軽く触ってみた内容を紹介したいと思います。
実行環境
公式のサンプルアプリにREPLのCLIがあったので、こちらを使用しました。
https://github.com/microsoft/power-fx-host-samples/tree/main/Samples/ConsoleREPL
推奨ビルド環境はVisual Studio 2019ですが、準備が面倒実行環境を手軽に用意したかったのでDocker image化しました。
https://github.com/Syuparn/power-fx-image
使用バージョン: 0.2.1-preview
(Docker imageのビルドにはハマりポイントがありましたがそちらは後述)
$ docker run -it --rm ghcr.io/syuparn/power-fx-console-repl:main ConsoleREPL
Microsoft Power Fx Console Formula REPL, Version 0.2
Enter Excel formulas. Use "Help()" for details.
> 2 + 3
5
> Exit()
特徴
公式サイトで「Excelのような数式バーで操作できる」と書かれているように、セルに打ち込む数式をそのままプログラミング言語にしたような雰囲気の言語です。
- 関数型・宣言的
- コレクションは
レコード
とテーブル
で表現(RDB, Excel的) - 値が依存する変数の変更にあわせて更新(リアクティブプログラミング?)
- UIや(ローコード、ノーコード)ツールに組み込んで使う想定
- 処理系はC#製
詳細については公式リファレンスをご覧ください2。
https://github.com/microsoft/Power-Fx/tree/main/docs
REPL
1行ごとに評価された値が表示されます。
使い方は Help()
で確認可能です(ただし、引数の説明は無いのでテストケースを見よう見まねで使いました)。
> 2 + 3
5
> Help()
"
Set( <identifier>, <expression> ) creates or changes a variable's value.
<identifier> = <expression> defines a formula with automatic recalc.
<expression> alone is evaluated and the result displayed.
Available functions (all are case sensitive):
Help Reset Exit Abs AddColumns
And Average Average Blank Concat
Coalesce Char Concatenate CountIf CountRows
Date DateAdd DateDiff DateValue DateTimeValue
Day EndsWith Exp IsBlank IsError
IsToday If IfError Int Filter
First FirstN ForAll Hour Last
LastN Left Len Ln Log
Lower Max Max Mid Min
Min Minute Mod Month Not
Now Or Power Replace Right
Round RoundUp RoundDown Second Sequence
Sort StartsWith Sum Sum Split
Sqrt Substitute Switch Table Text
Time TimeValue Today Trim TrimEnds
Trunc Upper Value With Year
Set
Available operators: = <> <= >= + - * / % && And || Or ! Not in exactin
Record syntax is { < field >: < value >, ... } without quoted field names.
Example: { Name: ""Joe"", Age: 29 }
Use the Table function for a list of records.
Example: Table( { Name: ""Joe"" }, { Name: ""Sally"" } )
Use [ <value>, ... ] for a single column table, field name is ""Value"".
Examples: [ 1, 2, 3 ]
Records and Tables can be arbitrarily nested.
Once a formula is defined or a variable's type is defined, it cannot be changed.
Use the Reset() function to clear all formulas and variables.
変数は Set(var, val)
で定義できます。グローバルが汚れてきたら Reset()
で変数を全削除できます。
> 2 + 3
5
> Set(x, 2)
x: 2
> Set(y, 3)
y: 3
> x + y
5
> Reset()
True
> x
Errors: Error 0-1: Name isn't valid. This identifier isn't recognized.
With
で、閉じたスコープでのみ変数を使うこともできます。
> With({x: 2}, x * 3)
6
> With({x: 2}, With({y: 3}, x * y))
6
> x
Errors: Error 0-1: Name isn't valid. This identifier isn't recognized.
(上記のような書き方ができるところから、引数は遅延評価されていそうです)
最後に、使い終わったら Exit()
で終了します。
値の自動更新
依存する変数の値に応じて値が更新されます。Excelで1セル書き直すと、そこを参照するセルも芋づる式に更新されるのと同じイメージです。
> Set(x, 3)
x: 3
> y = x * 2 # 式で定義
y: 6
> Set(x, 7)
x: 7
y: 14
一方、Setで変数に代入した場合は自動更新されません。
> Set(x, 2)
x: 2
> Set(y, x * 3)
y: 6
> Set(x, 5)
x: 5
> y
6
リファレンスにも
Power Fx and Excel both automatically recalculate formulas as the input data changes, so you usually don’t need to create and update variables. (Power FxとExcelはいずれも、入力データの変更に従って自動で式を再計算します。そのため、普段は変数を作成、更新する必要はありません。)
とあり、式を使うことが推奨されているようです。
式で定義した場合、Setで変数の上書きはできないようです(このあたりの挙動はまだよくわかってない)。
> x = 1
x: 1
> Set(x, 2)
x: 2
x: 1
> x
1
> Set(y, x * 2)
y: 2
コレクション処理
Table
を利用します。
> Filter(Table({x: 1, y: 2}, {x: 3, y: 5}), x > 2)
Table({x:3, y:5})
> ForAll(Table({x: 1, y: 2}, {x: 3, y: 5}), x + y)
[3, 8]
配列のように見える []
は、Tableのシンタックスシュガーです。
> Table({Value: 1}, {Value: 2}, {Value: 3})
[1, 2, 3]
数列の生成もできます。
# pythonのrangeと違って(個数、初期値、公差)なので注意!
> Sequence(5, 10, 2)
[10, 12, 14, 16, 18]
入れ子のTableでも、disambiguation(曖昧性解消)演算子 @
でシャドーイングを回避できます。
# &は文字列結合
> ForAll( X, ForAll( Y, Value & Text( X[@Value] )))
[["A1", "B1"], ["A2", "B2"]]
> ForAll(X, ForAll( Y, Value & Text(ThisRecord.Value)))
[["AA", "BB"], ["AA", "BB"]]
> ForAll(Table({a: 1, b: 2}, {a: 3, b: 4}), a)
[1, 3]
Docker image化する際にはまったところ
イメージ化にあたりASP.NET Coreを使用しましたが、そのままでは CultureInfo
が読み込めずevaluatorが動きませんでした。
# dotnet ConsoleREPL.dll
Microsoft Power Fx Console Formula REPL, Version 0.2
Enter Excel formulas. Use "Help()" for details.
> l
The type initializer for 'Microsoft.PowerFx.Core.Texl.BuiltinFunctionsCore' threw an exception.
エラーの概要は以下の通りです。
- いくつかの組み込み関数でロケールを使うため、処理系が
CultureInfo
を参照 - ASP.NET Coreのイメージではロケールが設定されていない(?)
- 結果
CultureInfo.CurrentCulture.Name
がnullとなりバリデーションチェック失敗、評価が異常終了するCultureInfo.CurrentCulture.Name
利用箇所 https://github.com/microsoft/Power-Fx/blob/47f0dfc2b13dcbdd8a2310289c902e19f9752314/src/libraries/Microsoft.PowerFx.Core/Localization/CurrentLocale.cs#L12- ここでnullチェック https://github.com/microsoft/Power-Fx/blob/47f0dfc2b13dcbdd8a2310289c902e19f9752314/src/libraries/Microsoft.PowerFx.Core/Localization/StringResources.cs#L131
対策として、REPLのコードを以下のように書き替えています。3
using System;
using System.Globalization; // 追加
using System.Text.RegularExpressions;
using Microsoft.PowerFx;
using Microsoft.PowerFx.Core.Public.Values;
using Microsoft.PowerFx.Core.Public.Types;
namespace PowerFxHostSamples
{
class ConsoleRepl
{
private static RecalcEngine engine;
public static void Main()
{
// CurrentCultureがnullになるのを防ぐため、評価前にデフォルト値(英語)を設定
CultureInfo.DefaultThreadCurrentCulture = CultureInfo.CreateSpecificCulture("en-US"); // 追加
ResetEngine();
// ...
おわりに
以上、Power Fxの簡単な紹介でした。これからも継続的に関数が追加されていくようなので注目していきたいです。また、サンプルにはweb版のREPLもあったので、機会があればこちらもDockerで動かしてみたいと思います。