9
7

More than 5 years have passed since last update.

OCamlでpecoっぽいのを作ってみる

Posted at

pecoと見ると、pacoを思い出しそうになる私です。(pacoは名前が変わってporgとなっていたようです。気づかなかった・・・)
pacoは今でも便利に使ってますが、まぁ普通は使わずにパッケージマネージャを使ったほうがいいと思います。emacsを手でビルドしていると手放せないんだ・・・

前書き

pecoって?という方はこちらを。 https://github.com/peco/peco

機能としては、標準入力から受け取ったものを任意に絞り込んで、標準出力に出力する、というかなりシンプルですが、emacsでhelm/anythingを、Vimならば・・・なんだっけ。そういった絞りこみインターフェースをシェル上で実現する、という感じのものです。

私も以前、zshで似たような(ものすごい機能劣化版)を、zshの機能だけを使って作ったりしたことがありますが、これはコマンドなのでシェルに関係なく使えます。

qiitaでも検索すれば色々利用法は出てきます。ものがシンプルなので、応用が非常に効く、というのも人気の一つのようです。

peco自体は、percolというpython製のツールにインスパイアされて作った(とpecoのreadmeに書いてます)、ということなので、そのさらに前のツールを作った方に先見の明があったということでしょう。知っている人は知っている、Keysnailを作った方です。

本題

さて、pecoはwindowsも含めた複数環境をサポートするため(なのかどうかはしりませんが)、golangで作成されています。最近気づきましたが、golangってインデントがタブのせいか、github上で非常にインデントが広くて読みづらいですね。

個人的にgolangは仕事で使うんならやらんでもないけど、個人的には積極的に利用したくないなー、という感じです。書き方が冗長すぎると感じたもんで。
(そもそもツールだからええやん、というのはそのとおり。私もpeco入れて便利に使ってます)

OCamlで何かしらターミナルを利用したツールを作ってみたかった、というのもあっていつものノリで、OCamlで似たものに作ってみました。

利用したライブラリ

ライブラリとしては、以下を利用しました。

  • lambda-term
    • Lwt
    • React
    • これらは、lambda-termがReactを利用したZedと、Lwtを盛大に利用しているため、必須です

lambda-termには、read_lineという入力インターフェースがすでに実装されているため、これを思いっきり利用して、面倒な部分を省くことにしました。

ドハマりしたところ

あまりこういったコマンドラインツールとか書いたこと無いので、ところどころでドハマりしてめちゃ時間取られたりしました。単純にLwtの使い方を調べるのにもすごい時間かかった・・・。

ttyのとき/パイプのときのハンドリング

単純に私の知識がなかったのが原因ですが、パイプで繋いだ時の標準入力、標準出力の使い方に非常に悩まされました。ttyかどうか、というところについても同様に、 何やっても動かねぇぇ みたいな感じにもなったりしました。

これについては、OCamlでのシステムプログラミングをまとめたGithub pagesがあったので、これを熟読することでなんとか解決できました。

非常に役立ちました。

複数のReact.Eのハンドリング

今回、絞り込んだものを選択するキーシーケンスと、編集された時のキーシーケンスとに別々にReact.Eを割り当てて、それらに対するハンドラを設定する、という形にしましたが、これがまたうまく行かない。

let e1, _ = React.E.create () 
and e2, _ = React.E.create () in
(* f1, f2はそれぞれ別の処理をtermにするものだと思ってください *)
React.E.map e1 f1 |> ignore;
React.E.map e2 f2 |> ignore;

みたいな感じにすると、f1が動いた後にf2が動かなく、f2が動いた後にはf1が動かない、みたいな形になります。解決としては、きれいじゃないかもしれないけれども、こんな形に落ち着きました。

type t = {mutable v: int}
let i = {v = 0} in
let e1, _ = React.E.create ()
and e2, _ = React.E.create () in
let e = React.E.select [React.E.stamp e1 (); React.E.stamp e2 ()] in
React.E.map e (fun () -> i.v <- succ i.v) |> ignore;

mutableを使うのはなんかシャクですが、無理して複雑怪奇にするよりはこっちのほうがすっきりしました。

ひとまず作ってみて

とりあえずどんなものか、感触を掴みたかったんで作ってみましたが、意外としんどいです。ただ、一回軌道に乗ってしまえば、リスト処理とかoptionの処理とか、若干冗長でもザクザク書いていくことはできました。

だいぶ行き当りばったりで作ったので、モジュール分けとかあまり考えてないし、何よりテストなんて書いてませんが、それでもほとんど実行時エラーには(IOに関するところを除いて)ぶち当たりませんでした。この辺はOCamlとかHaskellを使っていて気持ちがいいところですね。

まぁ、OCamlでわざわざツールを書くか?と言われればウーン・・・となりますが。速度を気にしないのならRuby/Pythonとかでトライ&エラーしたりしたほうが速いケースもあるかと思いますので、その辺は臨機応変で。
(相変わらずまとまりないな)

9
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
7