プロジェクトは OMakefile を用いてomakeにどのようにビルドするのか指定しており、構文は Makefile と似ています。 OMakefile は3つの構文規則『変数の定義』『関数の定義』『ルールの定義』を持ち合わせています。
変数は以下のような構文で定義されます。変数名は任意のアルファベットとアンダースコア _ 、ハイフン - を用いることができます。
<name> = <value>
値(value)にはリテラル文字と展開された変数が定義できます。変数の展開は $(name) のような形で表されて、 <name> 変数は現在の環境下において <value> に置き換わります。いくつかの例を以下に示します。
CC = gcc
CFLAGS = -Wall -g
COMMAND = $(CC) $(CFLAGS) -O2
この例では、 COMMAND 変数の値は文字列 gcc -Wall -g -O2 となります。
make(1) とは違い、変数の展開は 先行して(eager) 行われ、 純粋な(pure) メカニズムとなっています(詳細は “4.9 スコーピング、セクション”,”6.1 動的なスコーピング” を参照してください)。これはつまり、変数の値は即座に展開されることによって、新しい変数への束縛が古い値に影響されないことを意味しています。例えば、前回の例を以下のような変数の束縛に拡張した場合について考えてみましょう。
X = $(COMMAND)
COMMAND = $(COMMAND) -O3
Y = $(COMMAND)
この例では、変数 X の値は前回のように文字列 gcc -Wall -g -O2 が定義されて、変数 Y の値は gcc -Wall -g -O2 -O3 となります。
変数はまた、既存の変数に新しい文字列を追加する演算子 += を用いることができます。例えば、以下の2つの文は等価です。
# CLAGS変数にオプションを追加
CFLAGS = $(CFLAGS) -Wall -g
# 上と下の束縛は等価です
CFLAGS += -Wall -g
配列は変数名の後ろに [] を追加し、初期値を改行を用いて要素を指定することで定義できます。各々の行でのスペースはOMakeにおいて重要な役割を担っています。例えば、以下のコードは文字列 c d e が出力されます。
X[] =
a b
c d e
f
println($(nth 2, $(X)))
文字 $():,=#\ はOMakeの特殊文字に指定されています。これらの文字を通常の文字としてOMakeで扱うためには、バックスラッシュ文字 \ でエスケープする必要があります。
DOLLAR = \$
文字列を連結させるために、改行もまたエスケープする必要があります。
FILES = a.c\
b.c\
c.c
バックスラッシュは他の文字でエスケープする必要が ない ことに注意してください。よって以下のような例は正常に動作します(これはつまり、文字列中のバックスラッシュが正常に保たれていることを表しています)。
DOSTARGET = C:\WINDOWS\control.ini
ある文章をクオーティングしたい場合は "#..." エスケープを使用します。ダブルクオーテーションの数は任意で、最も外側のクオーテーションは文字列に含まれません。
A = $""String containing "quoted text" ""
B = $"""Multi-line
text.
The # character is not special"""
関数は以下のような構文を用いて定義されます。
<name>(<params>) =
<indented-body>
パラメータは識別のためにカンマを用いて分割し、コードは関数定義からインデントした状態で、別の行に設置する必要があります。例えば、以下のコードは引数 a と b をコロンを用いて結びつける関数について定義しています。
ColonFun(a, b) =
return($(a):$(b))
return は関数から値を返す命令文です。 return 文は必須ではありません。この文が除外された場合、最後に関数が評価した命令文の返り値が返されます。
警告
バージョン0.9.6から return 文は関数を制御する命令文となりましたので、return文が呼ばれた場合、関数はただちに値を返して終了します。以下の例では引数 a が true であったのなら、関数 f はただちにprint文を評価することなく値1を返します。
f(a) =
if $(a)
return 1
println(The argument is false)
return 0
多くの場合、あなたは関数から直接値を返さずに、セクションやネストされたコードブロックから値を返したいと思うことがあるでしょう。このような場合に、あなたは value 演算子を使用できます。実際、 value 演算子は関数だけに限らず、値が必要となった場合はどこでも使用することができます。以下の定義では、変数 X は a の値に依存して1か2が束縛されて、結果を出力し、関数から値を返します。
f_value(a) =
X =
if $(a)
value 1
else
value 2
println(The value of X is $(X))
value $(X)
関数はGNU-makeの構文 $(<name> <args>) を用いて呼び出します。 <args> はカンマで分割された値のリストです。例えば、以下のプログラムでは、変数 X は値 foo:bar を含みます。
X = $(ColonFun foo, bar)
関数の戻り値を必要としない場合には、通常の関数表記を用いて関数を呼び出すこともできます。例えば、以下のプログラムでは文字列“She says: Hello world”を出力します。
Printer(name) =
println($(name) says: Hello world)
Printer(She)
この機能はバージョン0.9.8.6で導入されました。
関数はまたキーワードパラメータと引数をもつことができます。構文は [~|?]<id> [= <expression>] であり、キーワード名 <id> は必須となる引数である場合 ~ から始まる文字列で指定し、オプションとなる引数の場合 ? から始まる文字列で指定します。
キーワード引数と通常の匿名引数は完全に分離されます。また、キーワードパラメータを定義していない関数にキーワード引数を渡した場合、OMakeはエラーを送出します。
osh>f(x, ?y = 1, z) =
add($(mul $x, 100), $(mul $y, 10), $z)
- : <fun 0>
osh>f(1, ~y = 2, 3)
- : 123 : Int
osh>f(1, 3, ~y = 2)
- : 123 : Int
osh>f(1, 3)
- : 113 : Int
osh>f(1, 2, 3)
*** omake error:
File -: line 11, characters 0-10
arity mismatch: expected 2 args, got 3
osh>f(~z = 7)
*** omake error:
File -: line 12, characters 0-8
no such keyword: z
オプションとなるキーワード引数の値はデフォルトで空となります。
osh> g(?x) =
println($">>>$x<<<")
- : <fun 0>
osh> g()
>>><<<
osh> g(~x = xxx)
>>>xxx<<<
必須となるキーワード引数を指定しない場合はエラーとなります。
osh> h(~x, ~y) =
println(x = $x; y = $y)
- : <fun 0>
osh> h(~y = 2, ~x = 1)
x = 1; y = 2
osh> h(~y = 2)
*** omake error:
File -: line 11, characters 0-9
keyword argument is required: x
この機能はバージョン0.9.8.6で導入されました。
分類辞 curry を付与した関数は『通常よりも多い』引数で呼び出すことができます。カリー化関数は、残りの引数をとる関数を返す必要があります。また、残りの引数を含めたすべての引数は指定しなければなりません。
osh>curry.f(x, y) =
println($"Got two arguments: x = $x, y = $y")
g(z) =
add($x, $y, $z)
osh> f(1, 2, 3)
Got two arguments: x = 1, y = 2
- : 6 : Int
osh> f(1, 2)
Got two arguments: x = 1, y = 2
*** omake error:
File -: line 62, characters 0-7
arity mismatch: expected 1 args, got 0
また、関数はカリー化関数であるかどうかに関わらず、一部分のみを引数として渡すことができます。
osh> f1(a, ~b = 2, ~c = 3, d) =
println($"a = $a, b = $b, c = $c, d = $d")
- : <fun 0>
osh> f2 = $(apply $(f1), ~c = 13, 11)
- : <curry 0>
osh> f2(14, ~b = 12)
a = 11, b = 12, c = 13, d = 14
osh> f2(24)
a = 11, b = 2, c = 13, d = 24
コメントは # 文字から始まり、行の末尾まで続きます。
ファイルのインクルードには include か open 文を使います。インクルードされたファイルは OMakefile として、同じ構文で使用できます。
include $(Config_file)
open 文は include と似ていますが、一回しかインクルードされないのが特徴です。
open Config
# 2回目のopenは無視されますので、
# この行はなんの影響も与えません。
open Config
ファイル名が絶対パスで指定されていない場合、 include と open 文の両方は OMAKEPATH 変数上のパスを探します。 open 文の場合、この検索は パースする際に実行される ので、 open の引数に他の式を含める必要はありません。
omakeのスコープはインデントのレベルで定義されます。インデントレベルが上がると、omakeでは新しいスコープが導入されます。
section 文は新しいスコープを追加したい場合に有効です。例えば、以下のコードは X = 2 を出力した後で、 X = 1 を出力します。
X = 1
section
X = 2
println(X = $(X))
println(X = $(X))
この結果について驚くかもしれませんが─ section 内での変数の束縛は外部のスコープには影響を及ぼしていないのです。
“6.3 環境のエクスポート” で説明する export 文を使えば、内部スコープの変数をエクスポートすることでこの制限から抜け出すことができます。例えば、私たちが前回のサンプルに export 文を追加した場合、変数 X の新しい値が返されて、 X = 2 が2回出力されます。
X = 1
section
X = 2
println(X = $(X))
export
println(X = $(X))
分離されたスコープが非常に重要な結果を及ぼす場合があります。例えば、各々の OMakefile はそれ自身のスコープで評価されます。つまり各々のプロジェクトの一部は独立した設定となっているので、一つの OMakefile で変数を定義しても、これは他の OMakefile の定義に影響を及ぼしません。
別の例を見てみましょう。異なったビルドターゲットを指定するために、変数を分割するほうが便利である場合を考えます。この場合の頻繁に使う慣用句として、分割されたスコープを定義する section 文を使用することが挙げられます。
section
CFLAGS += -g
%.c: %.y
$(YACC) $<
.SUBDIRS: foo
.SUBDIRS: bar baz
この例では、 foo サブディレクトリには CFLAGS 変数に -g オプションが追加されていますが、 bar と baz ディレクトリには追加されていません。この例の場合ですとスコープのルールは非常によく働いており、 foo サブディレクトリには新しいyaccルールが追加されていますが、 bar と baz は追加されていません。さらにいうと、この追加されたルールは現在のディレクトリに影響を及ぼしていません。
トップレベルでの条件分岐は以下のような形となります。
if <test>
<true-clause>
elseif <text>
<elseif-clause>
else
<else-clause>
まず <test> が評価されて、もしそれが true の値(真偽値についての詳細は “9.2 論理式、真偽関数、コマンドのコントロール” を参照してください)であるならば <true-clause> のコードが評価されます。そうでなければ、残りの節が評価されます。また、 if 文は複数の elseif 宣言句を持たせることができます。 elseif と else 宣言句はなくても構いません。ただし、新しいスコープを導入するため、それぞれの宣言句はインデントされている必要があります。
if 文では、評価する文字列が空であったり、内容が false , no , nil , undefined , 0 であった場合、真偽値は false として評価されます。それ以外はすべて true になります。
以下の例では典型的な条件分岐の使い方を示しています。 OSTYPE 変数は現在使っているマシンのアーキテクチャを表しています。
# アーキテクチャ上での主要な拡張子
if $(equal $(OSTYPE), Win32)
EXT_LIB = .lib
EXT_OBJ = .obj
EXT_ASM = .asm
EXE = .exe
export
elseif $(mem $(OSTYPE), Unix Cygwin)
EXT_LIB = .a
EXT_OBJ = .o
EXT_ASM = .s
EXE =
export
else
# 他のアーキテクチャの場合は強制終了する
eprintln(OS type $(OSTYPE) is not recognized)
exit(1)
パターンマッチングは switch と match 文を使って実現できます。
switch <string>
case <pattern1>
<clause1>
case <pattern2>
<clause2>
...
default
<default-clause>
case の数は任意です。 default 宣言句はなくても構いませんが、使う場合は一番最後の宣言句で用いるべきです。
switch の場合、文字列は <patterni> と『文字通りに』比較されます。
switch $(HOST)
case mymachine
println(Building on mymachine)
default
println(Building on some other machine)
<patternN> は定数である必要はありません。以下の関数は pattern1 のマッチ、そして ## デリミタを用いた pattern2 のマッチを表しています。
Switch2(s, pattern1, pattern2) =
switch $(s)
case $(pattern1)
println(Pattern1)
case $"##$(pattern2)##"
println(Pattern2)
default
println(Neither pattern matched)
match の場合、パターンとしてegrep(1)─正規表現─が使用できます。数値変数 $1, $2, ... は \(...\) 表現を使って値を取得できます。
match $(NODENAME)@$(SYSNAME)@$(RELEASE)
case $"mymachine.*@\(.*\)@\(.*\)"
println(Compiling on mymachine; sysname $1 and release $2 are ignored)
case $".*@Linux@.*2\.4\.\(.*\)"
println(Compiling on a Linux 2.4 system; subrelease is $1)
default
eprintln(Machine configuration not implemented)
exit(1)
OMakeはオブジェクト指向言語です。一般的に、オブジェクトはフィールド(訳注: プロパティもしくはメンバ変数と置き換えても良いです)とメソッドを持っています。オブジェクトは変数名の最後に . を加えることで定義できます。例えば、以下のオブジェクトは2次元平面上での点(1, 5)を表しています。
Coord. =
x = 1
y = 5
print(message) =
println($"$(message): the point is ($(x), $(y)")
# Xに5を束縛
X = $(Coord.x)
# これは "Hi: the point is (1, 5)" と出力されます。
Coord.print(Hi)
オブジェクトのフィールド x と y は点の座標を表しています。 print メソッドは点の現在位置を出力します。
オブジェクトと同様にして クラス も定義できます。例えば、私たちは現在、オブジェクトを生成したり、移動したり、位置を出力するメソッドを持った Point クラスを作りたいものとしましょう。クラスはオブジェクトの作り方と似ていますが、 class 宣言句を用いて名前を定義付ける点が異なります。
Point. =
class Point
# フィールドの通常の値
x = 0
y = 0
# 座標から新しいクラスを生成する
new(x, y) =
this.x = $(x)
this.y = $(y)
return $(this)
# 点を右に移動する
move-right() =
x = $(add $(x), 1)
return $(this)
# 点を出力する
print() =
println($"The point is ($(x), $(y)")
p1 = $(Point.new 1, 5)
p2 = $(p1.move-right)
# "The point is (1, 5)" と出力
p1.print()
# "The point is (2, 5)" と出力
p2.print()
変数 $(this) は現在のオブジェクトを参照していることに注目してください。また、クラスとオブジェクトは新しいオブジェクトを返す new と move-right メソッドを持っています。これは、オブジェクト p2 とオブジェクト p1 が別物であり、 p1 はオリジナルの座標(1, 5)を保持していることを表しています。
クラスとオブジェクトは継承(多重継承を含む)を extends 文によってサポートしています。以下の Point3D では、 x , y , z フィールドを持ったクラスを定義しています。新しいオブジェクトは、親クラスやオブジェクトが持つすべてのフィールドやメソッドを継承します。
Z. =
z = 0
Point3D. =
extends $(Point)
extends $(Z)
class Point3D
print() =
println($"The 3D point is ($(x), $(y), $(z))")
# "new"メソッドはオーバーライドされていませんので、
# 下のメソッドは新しく点(1, 5, 0)を返します。
p = $(Point3D.new 1, 5)
static. オブジェクトはOMakeが動作している間、ずっと一定の値を保持していたい場合に使うオブジェクトです。このオブジェクトはプロジェクトを設定する際に頻繁に用いられます。プロジェクトを設定する変数が何回も書き換えられるのはリスクが高いので、 static. オブジェクトは設定がちょうど一回だけ行われることを保証してくれます。以下の(どこか冗長な)サンプルでは、 static. 節がLaTeXコマンドが使用可能であるかどうか調べるために使われています。 $(where latex) 関数は latex の絶対パスか、latexコマンドが存在しない場合は false を返します。
static. =
LATEX_ENABLED = false
print(--- Determining if LaTeX is installed )
if $(where latex)
LATEX_ENABLED = true
export
if $(LATEX_ENABLED)
println($'(enabled)')
else
println($'(disabled)')
OMakeの標準ライブラリを用いると第14章( 14. 自動設定用の変数と関数 )にあるような static. をプログラミングするための、多くの有用な関数を試すことができます。標準ライブラリを用いると、上のコードは以下のように書き直せます。
open configure/Configure
static. =
LATEX_ENABLED = $(CheckProg latex)
プロジェクトの設定として使われている static. 節は、 ConfMsgChecking や ConfMsgResult 関数( 14.1.1 ConfMsgChecking, ConfMsgResult )を使って、 static. 節でどういう動作をしているのかについて出力すべきです(もちろん、標準ライブラリにある多くの関数が、この作業を自動的に行ってくれます)。
この機能はバージョン 0.9.8.5 で搭載されました。
.STATIC 節の書き方は static. 節の書き方と似ています。構文は以下の3つのどれを選んでも書くことができます。
# bodyで定義されたすべての変数をエクスポート
.STATIC:
<body>
# 特にファイル依存を指定したい場合
.STATIC: <dependencies>
<body>
# ファイル依存と同様に、どの変数をエクスポートしたいのか指定する場合
.STATIC: <vars>: <dependencies>
<body>
<vars> は定義する変数名、 <dependencies> はファイル依存─依存先のファイルのある一つが変更された場合、対象のルールは再評価されます─を指定します。 <vars> と <dependencies> はもし必要ならば除外することができ、 <body> 中で定義されたすべての変数はエクスポートされます。
たとえば、前回のセクションで示した最後のサンプルは以下のように改良できます。
open configure/Configure
.STATIC:
LATEX_ENABLED = $(CheckProg latex)
効果は( .STATIC を使用する代わりに) static. を使用した場合とほとんど似ています。しかしながら、殆どの場合において .STATIC のほうが優位です。理由は2つあります。
まず、 .STATIC 節は遅延評価されます。これはつまり、 .STATIC 内の変数が一つでも解決されないのならば、評価されることはないということを意味しています。例えばもし $(LATEX_ENABLED) が決して評価されない変数だとすると、 .STATIC 節は決して評価されることはありません。これは少なくとも一回はいつでも評価される static. 節とは対照的です。
次に、 .STATIC 節はファイル依存を指定できます。これは、 .STATIC 節がメモ化として用いられる場合に有効です。例えば、キーと値のペアを持ったテーブルから辞書を作りたい場合を考えてみましょう。 .STATIC 節を使うことによって、omakeはこの計算を(omakeが毎回動くときに計算するのではなく)入力されたファイルが変更された場合のみ計算するようにふるまいます。以下の例では、 awk 関数がファイル table-file をパースするために用いられています。 awk 関数は key = value の形をした行を発見する度に、そのキーと値のペアを TABLE 変数に追加します。
.STATIC: table-file
TABLE = $(Map)
awk(table-file)
case $'^\([[:alnum:]]+\) *= *\(.*\)'
TABLE = $(TABLE.add $1, $2)
export
ルールの依存関係が変わった場合はいつでも .STATIC 節は再計算されます。このルール内での対象は、エクスポートする変数となります(この場合ですと TABLE 変数が相当します)。
.MEMO ルールは、その結果が独立して動いている omake インスタンス間で保存されない点を除いて、 .STATIC ルールと等価です。
.STATIC と .MEMO ルールはまた、計算された値とリンクしている『キー』を表す :key: を使うことができます。 .STATIC ルールを、キーと値がリンクした辞書として考えることは有用です。 .STATIC ルールが評価された場合、結果は指定されたルールによって定義された :key: がテーブル内に保存されます( :key: が指定されていない場合、デフォルトのキーが代わりに用いられます)。言い換えると、ルールは関数のようなものです。 :key: は関数の『引数』を表しており、ルール部分で結果を計算します。
これを確かめるために、 .MEMO ルールをフィボナッチ関数に改良してみましょう。
fib(i) =
i = $(int $i)
.MEMO: :key: $i
println($"Computing fib($i)...")
result =
if $(or $(eq $i, 0), $(eq $i, 1))
value $i
else
add($(fib $(sub $i, 1)), $(fib $(sub $i, 2)))
value $(result)
println($"fib(10) = $(fib 10)")
println($"fib(12) = $(fib 12)")
このスクリプトを実行した場合、以下のような結果となります。
Computing fib(10)...
Computing fib(9)...
Computing fib(8)...
Computing fib(7)...
Computing fib(6)...
Computing fib(5)...
Computing fib(4)...
Computing fib(3)...
Computing fib(2)...
Computing fib(1)...
Computing fib(0)...
fib(10) = 55
Computing fib(12)...
Computing fib(11)...
fib(12) = 144
フィボナッチ関数は各々の引数の場合において、一回だけしか計算されていないことに注目してください。これは普通にプログラムした場合ですと、指数関数的に計算時間が増えてしまいます。言い換えると、 .MEMO ルールは計算結果をメモ化(memoization)しているからこそ、この名前なのです。 .STATIC ルールを代わりに使った場合、すべての omake インスタンスにおいて値が保存されていることに注意してください。
一般的には、あなたは .STATIC か .MEMO ルールを関数内で用いる場合はいつでも、ふつう :key: を使いたくなるでしょう。しかしながら、これは必須ではありません。以下の例では、 .STATIC ルールが、何か計算時間のかかる作業を一回だけ行う場合を表しています。
f(x) =
.STATIC:
y = $(expensive-computation)
add($x, $y)
あなたがフィボナッチ関数のような再帰的な関数を定義する場合、さらに以下の点に注意すべきです。 :key: を除外してしまった場合、ルールは関数自体に対して定義されてしまい、循環された依存関係で評価されてしまいます。以下は :key: を除いたフィボナッチ関数の出力結果です。
Computing fib(10)...
Computing fib(8)...
Computing fib(6)...
Computing fib(4)...
Computing fib(2)...
Computing fib(0)...
fib(10) = 0
fib(12) = 0
この動作は i = 0 || i = 1 の場合に達するまで result の値が保存されていないので、 fib は自身を fib(0) に達するまで再帰的に呼び出し、そして result の値は0に修正されてしまうために生じます。
再帰的な定義が無難に動作する場合もありますが、あなたは普通 :key: 引数をつけることで、各々の再帰的な呼び出しが異なった :key: を持つようにするでしょう。これは多くの場合において、 :key: が関数の引数すべてに含めるべきであることを示しています。
OMakeではいろんな方法でそれぞれの値を表すことができます。私たちはこれを以下のリストにしました。
array - 配列
コンストラクタ: $(array <v1>, ..., <vn>) (9.3.1 array)
オブジェクト: Array (12.1.7 Array)
配列は有限の数の値をもったリストを表します。配列はまた以下のように定義することもできます。
X[] =
<v1>
...
<vn>
詳細は “9.3.5 nth”, “9.3.8 nth-tl”, “9.3.4 length” を参照してください。
string - 文字列
オブジェクト: String (12.1.8 String)
通常、すべての文字からなるシーケンスは配列として表現されるため、単純にソース中に書き表すことで初期化できます。内部で文字列はいくつかの断片としてパースされます。文字列はしばしば、ホワイトスペース(訳注: ホワイトスペースはスペース、タブを含んだ空白文字のことです)によって分割された値のシーケンスとして定義されます。
osh>S = This is a string
- : <sequence
"This" : Sequence
' ' : White
"is" : Sequence
' ' : White
"a" : Sequence
' ' : White
"string" : Sequence>
: Sequence
osh>length($S)
- : 4 : Int
データ 文字列は、ホワイトスペースが重要な意味を持つ場合に用いられます。これは単純な一つの値として定義されるので、配列にはなりません。コンストラクタはクオーテーション $"..." と $'...' で表現できます。
osh>S = $'''This is a string'''
- : <data "This is a string"> : String
詳細は “7.2 クオート文字列” を参照してください。
file - ファイル
コンストラクタ: $(file <names>) (10.1.1 file, dir)
オブジェクト: File (12.1.13 File)
ファイルオブジェクトはファイルの絶対パスを表すオブジェクトです。ファイルオブジェクトは絶対パスとして見ることができます。文字列への変換はカレントディレクトリに依存しています。
osh>name = $(file foo)
- : /Users/jyh/projects/omake/0.9.8.x/foo : File
osh>echo $(name)
foo
osh>cd ..
- : /Users/jyh/projects/omake : Dir
osh>echo $(name)
0.9.8.x/foo
詳細は “10.1.1 file, dir” を参照してください。
ノート
訳注: 原文では “10.6.1 vmount” となっていますが、これはおそらく “10.1.1 file, dir” の間違いであると思われますので、置き換えました。
directory - ディレクトリ
map (dictionary) - マップ (辞書)
オブジェクト: Map (12.1.2 Map)
マップ/辞書オブジェクトは値と値を結びつけるテーブルです。 Map オブジェクトは空のマップです。データ構造は永続的に保持されて、すべての演算は分かりやすく関数的です。特別な構文 $|key| によって文字列のキーを表現することができます。
osh>table = $(Map)
osh>table = $(table.add x, int)
osh>table. +=
$|y| = int
osh>table.find(y)
- : "int" : Sequence
function - 関数
コンストラクタ: $(fun <params>, <body>) (9.5.1 fun)
オブジェクト: Fun (12.1.9 Fun)
関数オブジェクトはいろんな方法で定義できます。
無名関数
$(fun i, j, $(add $i, $j))
名前をつけた関数
f(i, j) =
add($i, $j)
この機能はバージョン0.9.8.6で導入されました。 無名関数の引数
osh>foreach(i => $(add $i, 1), 1 2 3)
- : <array 2 3 4> : Array