owk は JSON 等それなりの構造を持ったデータのストリームに対して、 awk 的に使えることを目指して作られている DSL/interpreter です。
$ owk -e '"hello world"' "hello world" $ seq 5 | owk -i line 'map { _ * 2 }' 2 4 6 8 10 $ seq 5 | owk -i line 'map { _ * 2 } >> filter { _ < 10 }' 2 4 6 8 $ echo '{"name":"roi"}{"name":"zak"}' | owk 'map { _.name }' "roi" "zak"
実行ファイルであるところの owk
コマンドについて説明します。
owk
は引数で渡された owk script をまず一度評価し、
最後の式を main
関数として扱います。
その後、標準入力を Stream として main
関数に渡します。
$ seq 10 | owk -io word 'print "init"; map input -> { "[" + input.0 + "]" }' init [1] [2] [3] [4] [5] [6] [7] [8] [9] [10]
-e
オプションを指定することで、
入力を読み込まず渡された script を実行し、最後の式の値を表示する eval モードになります。
$ seq 10 | owk -e '"hi"' "hi"
入力をどのようにパースするかは -i
オプションで指定できます。
デフォルトでは JSON のストリームとしてパースされます。
$ echo '{ "value": 1 } { "value": 2 } { "value": 3 }' | owk -i json 'map { _.value }' 1 2 3
例えば -i word
と指定することで、行ごとにスペースで分割し、数値もしくは文字列として、
数値をキーにした辞書に格納され、渡るようになります。
$ echo '1 2 three' | owk -io word 'map { _ }' {0: "1 2 three", 1: 1, 2: 2, 3: "three"}
また、出力をどのように変換するかは -o
オプションで指定できます。
$ owk -o ltsv -e '{ key: "value", key2: 100 }' key:value key2:100
map
関数を使用することで、入力 Stream からのデータそれぞれに対して関数を適用することが出来ます。
$ seq 10 | owk -i line 'map { _ * 2 }' 2 4 6 8 10 12 14 16 18 20
filter
関数で、条件を満たす入力のみを通すことが出来ます。
$ seq 10 | owk -i line 'filter i -> { i > 5 }' 6 7 8 9 10
fold
関数で、入力を畳み込むことが出来ます。
$ seq 10 | owk -i line 'fold (acc -> i -> { acc + i }) 0' 55
また、 >>
演算子により、それぞれの関数を連結した関数とすることが出来ます。
$ seq 10 | owk -i line 'map { _ + 1 } >> map { _ * 2 } >> fold (acc -> i -> acc + i) 0' 130
$>
演算子は左辺の値を右辺の関数に適用します。
$ owk -e '[1, 2, 3, 4] $> filter { _ % 2 == 0 } >> map { _ * 2 }' [ 4, 8 ]
- 動的型付。
- lexical scope 。
- 全て式です。何かしらの値を返します。
- データ型は基本的に全て immutable です。
- 変数の再定義は、同一スコープ中ではできません。親スコープの shadowing は可能です。
文字列です。
s = "hello, owk" print "\u3042" # => あ
数値です。
i = 2 j = 10.1 print (i * j) # => 20.2
真偽値です。
owk において 偽 と判定されるのは、 false
と下で解説する Undef
のみです。
t = true f = false
ハッシュとか連想配列とも言われるアレです。
キーの文字列に対して、任意のデータを保持できます。
d = { key: "value", key2: 100 } print d.key # => value print (d ["key"]) # => value
辞書の更新 (merge) は以下のように行います。
d2 = d { key2: 200, key3: "spam" } d3 = d2 ["key4", "egg"]
リストです。任意のデータを格納できます。
L = [1, 2, 3, "4"] print (L[0]) # => 1
List[start, count]
で slice を取得できます。
L2 = L[1, 2] print L2 # => [2, 3]
タプルです。任意のデータを格納できます。
リストとの使い分けですが、こちらは主に関数へ複数の値を渡すのに利用されます。
t = (1, 2)
関数です。
pattern (guard) -> { block }
の形で定義します。
block
には複数の式を記述できます。
pattern (guard) ->
部分は省略可能で、その場合 _ ->
が補われます。
(guard)
部分のみを省略することもできます。
また、 block
が単一の式のみからなる場合は { }
を省略することができます。
|
で区切ることにより、複数のパターン・ブロックが記述できます。
関数の戻り値は最後の式の値になります。
関数呼び出しは func arg
の形になります。
複数の値を渡したい場合は Tuple を渡すか、カリー化された関数を定義することで代用します。
なお、 owk においては0引数の関数呼び出しはできません。 不要だとしても最低1つは引数を渡してください。
f = { print "hi" } f () # => hi f2 = _ -> print "hi" f2 () # => hi f3 = name -> { print("hi, " + name) } f3 "nakamuray" # => hi, nakamuray f4 = (x, y) -> { x * y } print (f4(2, 3)) f5 = x -> y -> { x * y } print (f5(2, 3)) f6 = i -> { i * 2 } print (f6 10) # => 20 f7 = { _ * 2 } print (f7 10) # => 20 f8 = 0 -> { "zero" } | n -> { n } print (f8 0) # => zero print (f8 100) # => 100 f9 = n (n > 5) -> "greater than five" | n -> "less than equal five" print (f9 5) # => less than equal five print (f9 6) # => greater than five
データのストリームです。入力データはこの Stream として処理系より渡されます。
これを操作する関数を組み上げるのが、 owk の基本的な使い方となります。
参照です。 owk の他のデータ型は全て immutable なので、破壊的変更を行いたい時はこれを使います。
Ref を参照したい時は、関数のように呼び出します。
変更には :=
演算子を利用します。
r = ref 0 print (r ()) # => 0 r := 1 print (r ()) # => 1
未定義値です。
u = undef
代入および関数適用の際に、パターンマッチが行われます。
パターンには String, Number, Dict, List, Tuple のリテラルと変数が記述できます。
(a, b) = (1, 2) [c, [d, e]] = [3, [4, 5]] f = (("6", 7) = ("6", 7)) { key1: g, key2: h } = { key1: 8, key2: 9 } ((i, j) -> { print (i, j) }) (10, 11)
Dict のパターンマッチでは、チェックされる値の側に余分なキーがあっても無視されます。
{ key1: k } = { key1: 12, key3: 13 }
変数名@パターン の形で記述することで、パターン全体を変数に入れることができます。
l@{ key: m } = { key: "value", key2: "value!" } print l # => { key: "value", key2: "value!" } print m # => value
マッチに失敗した場合、代入なら Undef が返ります。
n = (0 = 1)
関数適用の場合は次のパターン・ブロックを試みます。全てのパターンにマッチしなかった場合は Undef が返ります。
func = 0 -> { 0 } | 1 -> { 1 } print (func 1) # => 1 print (func 2) # =>
以下の演算子が利用できます。大体見たままです。
-
, +
, *
, /
,
>
, <
, >=
, <=
,
==
, !=
, =~
, !~
,
!
, &&
, ||
, :
, ?
, :=
見たままでないいくつかを説明します。
$
Haskell の
$
です。左辺に関数、右辺に引数をとります。print $ 1 + 1 # => 2 print (1 + 1) # => 2
?
左辺に Bool 、右辺に関数を取り、 Bool が 真 のとき右辺を実行します。
true ? { print "hi" } # => hi false ? { print "hi?" }
:=
上記 Ref の説明参照。
>>
関数を連結します。 受け取った引数を左辺に渡してその戻り値を右辺に渡すような、新たな関数を作ります。
f = { _ * 2 } g = { _ + 1 } h = f >> g print $ h 10 # => 21
バッククオートで囲むことで、演算子を変数名として参照・代入がきます。
print $ `+` 1 2 # => 3 `+:` = x -> y -> print (x, "plus", y) 1 +: 2 # => 1 plus 2
owk スクリプトは、上記のデータ型と演算子を組み合わせた式の羅列になります。 式同士はセミコロン、もしくは改行で区切られます。 式中に改行を書きたい場合はバックスラッシュで改行をエスケープできます。
なお、以下の箇所では改行は無視されます。
- Dict, List and Tuple 中の , の左右
- 関数定義の区切りの | の左右
- 2項演算子の右側
コメントは #
から改行までです。
TODO: いつか書く。