${{ fn(args) }} は、SDK のバインディングクロージャ (c) => fn(args) の中にある同一の呼び出しへと下げられます。組み込み関数専用の SDK メソッド面はありません。これらは式構文であり、SDK ビルダーではありません。そのため、以下では <CodeGroup> のペアは不要です。各関数につき1つの例を示し、その式形式が両方の場所で使われます。
16個すべては
packages/core/src/expr/eval.ts に実装され、packages/core/src/expr/ast.ts の閉じた BuiltinName union として列挙されています。このページは、間違えやすいエッジケースも含め、そのコードが正確に何をするかを文書化しています。概要
| 関数 | シグネチャ | 戻り値 |
|---|---|---|
len | len(v: string | array) | number |
lower | lower(v: any) | string |
upper | upper(v: any) | string |
trim | trim(v: any) | string |
json | json(v: any) | string |
default | default(v: any, fallback: any) | any |
map | map(arr: array, proj: expr) | array |
filter | filter(arr: array, pred: expr) | array |
contains | contains(hay: string | array, needle: any) | boolean |
split | split(s: string, sep: string) | array<string> |
join | join(arr: array, sep: string) | string |
replace | replace(s: string, find: string, repl: string) | string |
slice | slice(v: string | array, start: number, end?: number) | string | array |
nth | nth(v: string | array, i: number) | any | null |
to_number | to_number(v: any) | number |
unique | unique(arr: array) | array |
map と filter は特殊形式です。第2引数は値として事前に評価されません。要素ごとに1回評価される式であり、その要素は item 参照に、その位置は index に束縛されます。これは foreach 手順が公開する item/index バインディングと同じです。他のすべての組み込み関数は、まずすべての引数を通常の値として評価します。len
[...v].length です。文字列の spread はコードポイントを反復するため、絵文字やその他のサロゲートペアを使う文字は2ではなく1として数えられます。
- 文字列でも配列でもない引数は
ExprEvalErrorを投げます:len() expects a string or array。 len("")は0、len([])は0です。- Basic Multilingual Plane の外にある文字(例:
"😀")は1と数えられます。これは下記のsplit("", "")と一致します。どちらも同じ[...v]のコードポイント基準を使います。
lower
v を文字列に強制変換し(下記の文字列強制変換を参照)、.toLowerCase() を呼び出します。
- 文字列以外の入力は先に強制変換されます:
lower(42)→"42"、lower(true)→"true"、lower(null)→"null"。オブジェクトや配列は、小文字化の前に JSON 文字列化されます。 - JavaScript のロケール非依存な
.toLowerCase()を使います。ロケールパラメーターはありません。
upper
v を文字列に強制変換し、.toUpperCase() を呼び出します。強制変換の規則は lower と同じです。
trim
v を文字列に強制変換し、.trim() を呼び出して、先頭と末尾の空白を取り除きます(JavaScript の String.prototype.trim の空白定義に従います)。
trim(42)→"42"(数値は先に強制変換されるため、裸の数値から取り除く空白はありません)。- 取り除かれるのは先頭と末尾の空白だけです。内部の空白はそのまま残ります。
json
JSON.stringify により v を JSON 文字列にシリアライズします。オブジェクトや配列を含む任意の値で動作します。これは、共有の文字列強制変換ヘルパーを基盤にしていない、唯一の文字列生成系の組み込み関数です。
json("a")→"\"a\""(文字列は JSON クォートされます。文字列を裸のまま残すlower/upper/trim/joinの強制変換とは異なります)。json(null)→"null"。
default
v が null または undefined でない限り v を返します。その場合は fallback を返します。ほとんどの組み込み関数と異なり、これは文字列への強制変換を行いません。v または fallback がどの型であっても、そのまま通します。
- fallback を発火させるのは
null/undefinedだけです。falsy だが存在する値(0、""、false)はそのまま返され、置き換えられません。defaultは||ではありません。 - どちらの引数も通常の値であり、即時に評価されます(
map/filterの第2引数のように遅延評価されません)。
map
arr の各要素に proj を適用し、射影された値の新しい配列を返します。proj は特殊形式の引数です。事前には評価されません。各要素について、その要素を item に、そのインデックスを index に束縛した状態で評価されます。
- 第1引数は配列でなければなりません。それ以外は
ExprEvalErrorを投げます:map() expects an array。 - 射影の中の
item/indexは、外側のitem/indexバインディング(例:foreach手順の中にネストされたmap)をシャドーイングします。各呼び出しは自身の反復用にそれらを再束縛します。 map/filter/foreachのコンテキスト外でitemまたはindexを参照すると例外が投げられます("item" is only bound inside foreach/map/filter)。
filter
pred が truthy になる arr の要素を残し、それ以外を落とします。第2引数は map と同じ特殊形式です。pred は要素ごとに、item/index が束縛された状態で評価されます。
- 第1引数は配列でなければなりません。そうでない場合は
ExprEvalErrorを投げます:filter() expects an array。 - truthiness は式言語の truthy 規則に従います。
0、""、false、nullは falsy です。それ以外は、空配列やオブジェクトも含めて truthy です。
contains
hayが文字列の場合、これは部分文字列テストです:hay.includes(String(needle))(needle は文字列に強制変換されます)。hayが配列の場合、これは各要素に対して深い等価性(deepEqual)を使う membership テストです。そのため、containsは needle 自体がオブジェクトや配列である場合にも一致できます。スカラーだけに限りません。
hayが文字列または配列以外の場合はExprEvalErrorを投げます:contains() expects a string or array。- 配列 membership は
deepEqualを使います。これは==が使うものと同じ構造的等価性関数です。そのため、2つのオブジェクトが別参照であっても、contains([{a:1}], {a:1})はtrueです。 - 文字列 membership は共有の文字列強制変換規則で
needleを強制変換します。そのため、contains("id-42", 42)はtrueです(42は"42"に強制変換されます)。
split
sep で s を分割します。
split("café", "")→["c", "a", "f", "é"]、つまり4要素(コードポイント)です。len("café")=4と一致します。sの中に空でない区切り文字が見つからない場合は[s]を返します(標準のString.prototype.splitの挙動)。split("", ",")→[""](空文字列を空でない区切り文字で分割すると、JS セマンティクスに従い、単一の空文字列要素になります)。
join
arr のすべての要素を文字列に強制変換し、sep で結合します。
- 第1引数は配列でなければなりません。そうでない場合は
ExprEvalErrorを投げます:join() expects an array。 - 要素は結合前に共有の文字列強制変換規則で強制変換されます。
join([1, true, null], "-")→"1-true-null"。 join([], ",")→""。
replace
s 内にある find のリテラル出現をすべて repl に置き換えます。これはリテラルな部分文字列の全置換です。find が正規表現として扱われることはないため、find 内の正規表現メタ文字(.、*、(、[ など)は文字どおりそれ自身に一致します。実装は s.split(find).join(repl) であり、それこそが全出現を対象にし、正規表現を使わない理由です。
find === ""は何もしません。replace(s, "", repl)はreplに関係なくsを変更せず返します。(コードは空のfindで短絡し、s.split("").join(repl)に落ちません。そうでなければすべての文字の間にreplが挟まります。)- リテラルなので、
replace("a.b.c", ".", "-")→"a-b-c"です。.はリテラルのドットだけに一致し、正規表現の場合のように「任意の文字」には一致しません。 - 重なって見える置換は
split/joinにより左から右へ解決されます。そのため、置換済みテキストが再スキャンされることはありません。
slice
v の start から end の直前までのサブスライスを返します。end が省略された場合は v の末尾までです。セマンティクスは JavaScript の Array.prototype.slice / コードポイント安全な文字列 slice を反映します。末尾から数える負のインデックスもサポートします。
文字列の場合、v はまず Unicode コードポイント([...v])に分割され、スライスされ、再結合されます。そのため、スライスは UTF-16 ユニット安全ではなく、コードポイント安全です。
endは任意であり、値ではなく引数の存在によって検出されます。第3引数を省略すると末尾までスライスします。渡した場合は(0であっても)明示的な終端として尊重されます。- 負の
start/endは標準のArray.sliceのクランプ規則に従います(例:slice(arr, -2)は最後の2要素を返します)。 - 文字列でも配列でもない
vはExprEvalErrorを投げます:slice() expects a string or array。 - 範囲外のスライスは例外を投げません。ネイティブの
sliceの挙動に合わせて、空文字列または空配列を返します。
nth
i の要素(文字列の場合はコードポイント)を返します。範囲外の場合は null を返します。負のインデックスをサポートし、その扱いは Python 風です。負の i は slice によって末尾からインデックスするのではなく、v の長さを加えることで折り返されます(i < 0 ? length + i : i)。そのため、nth(v, -1) は最後の要素、nth(v, -2) は後ろから2番目の要素、という形になります。
- 範囲外のインデックスは、長さを加えた後もまだ負である折り返し後の負インデックス(例:
nth([1,2,3], -10))を含め、例外ではなくnullを返します。 - 文字列の場合、返される要素は単一のコードポイントです(
len/slice/splitのコードポイント基準と一致します)。UTF-16 コードユニットではありません。 - 文字列でも配列でもない
vはExprEvalErrorを投げます:nth() expects a string or array。
to_number
v を数値に強制変換します。
vがすでにnumberの場合は、変更せず返します(再パースしません)。- それ以外の場合、
vは文字列に強制変換され、.trim()され、すべてのカンマが取り除かれます(.replace(/,/g, ""))。これは桁区切りへの便宜です。その後、Number(...)でパースされます。
to_number("1,234.56")→1234.56。カンマは文字列内のどこにあっても取り除かれます。桁区切りグループだけではないため、to_number("1,2,3")→123にもなります(位置の検証はありません)。to_number("")は例外を投げます(trim とカンマ除去の後に空)。to_number("abc")は例外を投げます。to_number(" 42 ")→42(周囲の空白は先に trim されます)。- 通貨記号、単位、ロケール固有の小数区切りは処理されません。先頭の
"$"や、ヨーロッパ式の"1.234,56"のような数値はパースに失敗して例外を投げます。そのような文字は先にreplaceで取り除いてください。
unique
==、contains、map/filter の比較と同じ構造的な deepEqual 関数を使います。参照等価でも JSON 文字列キーでもありません。そのため、別参照にある構造的に同一の2つのオブジェクトは重複として扱われます。
- 引数は配列でなければなりません。そうでない場合は
ExprEvalErrorを投げます:unique() expects an array。 unique([{a:1}, {a:1}])→[{a:1}]。deep-equal なオブジェクトは、別々の参照であっても1つに畳み込まれます。- 実装は O(n²) です(要素ごとに
seen.some(...))。典型的な手順出力サイズでは問題ありませんが、Tool ではなく式の中で非常に大きな配列を重複排除する場合は知っておく価値があります。 unique([])→[]。
共有セマンティクス
上記の複数の組み込み関数に共通する挙動がいくつかあります。関数ごとに繰り返すのではなく、ここで一度だけまとめます。文字列強制変換(lower、upper、trim、join、contains、replace、split、to_number が使用)
文字列強制変換(lower、upper、trim、join、contains、replace、split、to_number が使用)
文字列を消費するほとんどの組み込み関数は、文字列以外の引数を同じ強制変換規則に通します。
string→ それ自身。変更なし。number/boolean→String(v)(例:42→"42"、true→"true")。null→ リテラル文字列"null"。- object / array →
JSON.stringify(v)。
json() が唯一の例外です。常に JSON シリアライズするため、文字列引数は裸の文字列ではなく JSON クォートされて戻ります。Truthiness(filter、三項演算子、! が使用)
Truthiness(filter、三項演算子、! が使用)
null→ falsy。boolean→ それ自身。number→0のときだけ falsy。string→ 空文字列("")のときだけ falsy。- array または object →
[]や{}であっても常に truthy。
深い等価性(==、!=、配列に対する contains、unique が使用)
深い等価性(==、!=、配列に対する contains、unique が使用)
2つの値は、次の場合に deep-equal です。
=== で等しい場合。または、両方が non-null の配列で、長さが等しく、対応する要素がそれぞれ deep-equal である場合。または、両方が non-null で配列ではないオブジェクトであり、同じキー集合を持ち、対応する値がそれぞれ deep-equal である場合。JS の typeof が異なる値は決して等しくありません(ただし null/null の場合は === によって処理されます)。Unicode コードポイント基準(len、空の区切り文字での split、slice、nth が使用)
Unicode コードポイント基準(len、空の区切り文字での split、slice、nth が使用)
4つの組み込み関数は、文字列を UTF-16 コードユニットではなく Unicode コードポイントの配列として扱います(spread の
[...v] によります)。これにより、マルチバイト文字(アクセント付き文字、ほとんどの CJK、多くの絵文字)が、len、split("", "")、slice、nth のすべてで一貫して1つの「文字」として数えられます。そのため、ある関数の結果を別の関数に正しく合成できます(例: len で境界付けされたループが返したインデックスで nth する場合)。関連
式言語
参照、演算子、リテラル、および式がどのようにコンパイルされチェックされるか。
制御フロー
when、branch、foreach。map/filter の代替ではない、手順レベルの構造です。シークレット
どの組み込み関数も
secret. 参照に触れられない理由と、secret_leak 診断がそれをどのように強制するか。