関数パターンマッチングのまとめ

オブジェクトリテラルも使えるようになって、もう大きな変化も無さそうなのでまとめます。

基本

シンボルはそのままローカル変数になります。

`x[
  x
].(1) //1

引数の数が合わなければエラーになります。

`x[
  x
].() //error

仮引数を書かなければ、引数ゼロを指定したことになります。

`[
  arg.size.== 0
].() //true

Method.| でオーバーロードします。引数エラー時に次のMethodを試します。

f: `x[
  1
].| `x y[
  2
]
f(0) //1
f(0 0) //2

無視

アスタリスクで引数が無視されます。

`*[
  arg.[0]
].(1) //1

残りの引数を無視する、というのもアスタリスクでできます。

`x *[
  x
].(1 2 3) //1

展開

.@でRange化します。

`x xs.@[
  xs.to_Array
].(1 2 3) //{2 3}

実引数側の.@で引数展開になります。

`x xs.@[
  xs.to_Array
].(1..3.@) //{2 3}

型指定

シンボルに続けて()を書くことで型指定ができます。そのオブジェクトが自身か親で無い場合にエラーになります。

f: `x(String)[
  x
].| `x[
  x.@name
]
f('str') //str
f(1) //Int

複数指定すると、どちらでもいい、という意味になります。

f: `x(Int Double)[
  x
].| `x[
  x.@name
]
f(1) //1
f(1.0) //1.0
f(1u) //Uint

メソッドコール

シンボルがメソッドコールを持つ場合、シンボルをまず定義し、その式全体が評価され、その結果がfalseの場合エラーになります。

f: `x.% 2[
  'odd'
].| `x.% 2.== 0[
  'even'
].| `*[
  'else'
]
f(1) //odd
f(2) //even
f(3.0) //else //Double.% は未定義でエラー

デフォルト引数

.=で実引数が足りない場合のデフォルト値を与えられます。

f: `x.= 0 y.= 1[
  {x y}
]
f()  //{0 1}
f(1) //{1 1}
f(Null 2) //{0 2} //実引数がNullの場合もデフォルト値

リテラル

リテラルを置くとそうでない場合にエラーになります。

f: `1[
  1
].| `x[
  0
]
f(1) //1
f(2) //0
f(1.0) //1 //単純に、その値の.==でtrueになるかどうかなので、型が一致しなくてもエラーにならない。
f('1') //0 //.==自体がエラー

分解(デストラクチャ)

配列リテラルで配列を分解します。

`{x y z}[
  y
].({1 2 3}) //2

分解式内でのパターンマッチングも可能です。

f: `{0 y(Int) z.= 1 *}[
  z
].| `*[
  'error'
]
f({0 0}) //1
f({1 0}) //error
f({0 2.0 3}) //error
f({0 0 0 0}) //0

Pairを作る.=>でPair(Mapのvalue_type)が分解されます。

Map.new(:a.=> 1 :b.=> 2).collect& `x.=> y[
  y
] //{1 2}

オブジェクトリテラルでslotの分解ができます。key: valuevalue部分がパターンマッチング式として処理されます。

`{a: x  b: y}[
  {x y}
].({a: 1 b: 2}) //{1 2}

指定されたkeyが無ければエラーになりますが、指定されてないものがあってもエラーにはなりません。

f: `{a: x  b: y}[
  {x y}
].| `{a: x}[
  {x}
]
f({a: 1}) //{1}
f({a: 1 c: 2}) //{1}

def_safe(:*)により、そのkeyが無くてもよいという指定になり、その値はNullになります。

`{a:* x  b: y}[
  {x y}
].({b: 2 c: 3}) //{Null 2}

Nullなので、valueにデフォルト引数を与えることで、デフォルト値が指定できます。

`{a:* x.= 0  b: y}[
  {x y}
].({b: 2 c: 3}) //{0 2}

その引数自体のデフォルト値を指定することも可能です。*1

f: `{a: x}.= {a: 1}[
  x
]
f() //1
f(1) //aが未定義でerror

親のslotは参照されます。

`{@name: name}[
  name
].(1) //Int

キーワード引数 (非推奨)

他の引数との兼ね合いが難しいので、オブジェクトリテラルのデストラクチャを使うほうがその部分に限定できていいと思います。
Symbol_idリテラルにデフォルト引数を指定した場合にキーワード引数として扱われます。

f: `:key.= 'val'[
  key
]
f(:key 1) //1
f(1) //'val' //キーワード引数に関係ない実引数は無視され、エラーにならない。
f(1 :key 2) //2
f(:key 3 1) //3

f2: `x :key.= 'val'[
  {x key}
]
f2(:key 1) //{:key 1}
f2(1) //{1 'val'}
f2(1 :key 2) //{1 2}
f2(:key 3 1) //{:key 3}

関数型言語によくある例

len: `{x xs.@}[1.+ recur(xs)].|`{}[0]
len({1 2 3}) //3

quicksort: `{}[{}].|`{x xs.@}[
  recur(xs.remove&`y[y.>= x]).+ {x}.+ recur(xs.remove&`y[y.< x])
]
quicksort({2 3 1}) //{1 2 3}

fib: `n.<= 2[
  1
].| `n[
  recur(n.dec).+ recur(n.dec(2))
]
fib(10) //55

*1:Giraffe+ 0.6.29より

Giraffe+ 0.6.28.1343

Downloadページへ

  • ADD: istreamに定数を渡すことでscanfのフォーマット文字列のように
  • ADD: 引数pattern matching (destructuring) でObjectリテラルを使えるように
  • ADD: tail_recur (名前callのみサポート(しかも遅い(stack overflowしないだけ)))
  • FIX: GetIconはコンマ区切りpathをそのpathが存在する場合にコンマ区切りとして扱うべきでない
  • FIX: Giraffe.Edit.GetDrawRect がicon rectのleftを変更しない

Giraffe+ 0.6.26.1304

Downloadページへ

  • FIX: マルチスレッド描画でスクロールとマークをするとマークアイテムが非マークに表示されることがある
  • FIX: スレッド描画中にリストアイテムを変更すると回復不能のエラーになる
  • MOD: Config.*.Thread.Enable にマイナス値で描画のsingle/multi-threadを切り替えできる

スレッド描画のまとめ

アイコン取得やネットワークパスのタイムアウト待ちなどで入力できない状態になるのを回避するために、描画をスレッド化できるようにしています。ネットワークパス等を使ってないなら、そんなに深く考える意義のあるものではありません。
描画される部位は、Edit/ListのIcon/Textの4つ。どうスレッド化するかをConfigのDraw.(Edit|List).(Icon|Text).Thread.Enableで設定可能にしてあります。それらをデフォルト値とし、Event.Get(Edit|List)ThreadEnableで、描画毎に指定が可能です。
スレッドには、MultiとSingleがあり、メインスレッドとは別の描画スレッドを、1つだけにするか描画毎にするかが選べます。デフォルトはMultiであり、Giraffe.EnableSingleThreadDrawing(true)でSingleになります*1。Thread.Enableの値をマイナスにすることで設定と逆になります。
Multiの場合、同時に多くのリソースにアクセスするため、多少動作に不安定な部分が生じ、描画が前後したり、リソースの取得に失敗したりしますが、全体が一斉に描画されるので、感覚的には高速です。
Singleなら、描画は順番にされ、それが前後したりすることはありませんが、時間のかかる処理に引っかかった場合、その後の描画はそれが終わるまでされません。
Thread.Enableの値から1を引いたミリ秒数待って並列化します。描画の別スレッド化自体は描画開始時にされており、ただメインスレッドは待つだけであり、メインスレッドで描画がされるわけではありません。
SingleでThread.Enableが2以上の場合、Singleの描画スレッドからさらに描画スレッドを描画毎に生成し、終了を待ち、指定時間内に終了しなかった場合に並列化します。Multiの待ち処理と違い、メインスレッドは待ちませんので、入力不能にはなりません。

問題点

  • Edit-Textの描画をメインスレッドかメインスレッドが待つ間にやらないと、キャレットの軌跡が一つだけ残ります。キャレット移動時にキャレットを隠すとか再描画とかあれこれ試しましたが、連続移動時の軌跡も消えてしまうなど、いい結果にはなりませんでした。
  • List-Textの描画をメインスレッドが待つようにすると、なぜかList-Iconも待つようになります。アイコン取得APIがメインスレッドと通信してるようです。
  • devmgmt.mscにEdit-IconとList-Iconが同時アクセスするとエラーに。Shell Icon Hookの問題のようです。Shell関係のフックは環境にもよりますし、もう描画中のエラーは全て無視してその描画を中止するだけにしています。

描画、スレッド、シェルアイコン取得等、Windowsの仕様の部分が大きいので、あまり深入りする気はありません。

*1:settings\setup\(DRAW)SingleThread

Giraffe+ 0.6.25.1232

Downloadページへ

  • ADD: xyzzygiraffe-mode.lのF4でメソッド定義ファイルを開く
  • ADD: Giraffe.Edit.OnSetText, SetTextのText変更後の処理
  • ADD: Object.not!
  • ADD: Window.FindRegex
  • ADD: Window.FindWild2, FindRegexをワイルドカード->正規表現変換して呼ぶ
  • ADD: settings/key/SelectListItemDownAndOpenListbox
  • ADD: settings/setup/(AC)AddHistory, オートコンプリート結果に履歴を追加
  • FIX: "func('test' 1) test" がaccess violation. testは1を返すべき。
  • FIX: GC errorが再初期化時のスレッド中断により起きる。(スレッドはもう中断されないようになった)
  • MOD: (get-)func(-g) で、 get-func("Object.slot") のようにオブジェクトスロットを指定できるように
  • MOD: Giraffe.Edit.Text は書き換え可能であるべき。ユーザーがSetTextを実装できるように
  • MOD: find_wnd_strを速く。親ウインドウに0が渡されたときでも。
  • MOD: Null.msg は Nil.msg として扱うべき。
  • MOD: Object.notはselfを&&や||のように扱うべき。(block時にevalされる)

Giraffe+ 0.6.24.1222

Downloadページへ

  • ADD: Array.to_Block
  • ADD: Block, Method.resolve3, 全ての名前解決をする。デバッグ用。
  • ADD: Block.to_Array
  • FIX: ShowMenu でdynamic sub menuの戻り値のArrayが解放されることがある
  • MOD: Block リテラルは expr_block でなくてもいい