Migemoを使って絞り込みできるfzfっぽいものを作っています
残暑というかもう真夏じゃねーか、という気温でいやんな感じです。でも夕方の風邪は大分過ごしやすくなってきました。 そんな秋の声が聞こえはじめている季節とは関係なく、今作っているtoolについて書いてみます。 https://github.com/derui/oif ...
残暑というかもう真夏じゃねーか、という気温でいやんな感じです。でも夕方の風邪は大分過ごしやすくなってきました。 そんな秋の声が聞こえはじめている季節とは関係なく、今作っているtoolについて書いてみます。 https://github.com/derui/oif ...
気づいたら8月になっていました。今年の梅雨はかなり長かったですね・・・。過ごしやすいのは結構なんですが、野菜が高くなるのでこれも困ります。 今回は、OCamlでTUI(Terminal-based User Interface)を作る際の鉄板ライブラリである lambda-term を使ったときに、multi byteを表示出来なかったのを解消したので、備忘録として書いておきます。 <!–more–> やりたかったことと起こっていたこと やりたかったこととしては、 一文字ずつ表示したい 各文字にstyleを当てたい lambda-termでは、 LTerm_draw.draw_string と LTerm_draw.draw_char という2つの関数があります。こいつらは字面の通り、stringやcharをレンダリングします。 使い方はこんな感じです。実際にはcontextが必要なので、widgetの中とかで行う感じです。 let str = Zed_string.of_utf8 "foo" in let style = LTerm_style.none in LTerm_draw.draw_string ~style ctx 0 0 str; LTerm_draw.draw_char ~style ctx 0 1 @@ Zed_string.get 0 str これで表示自体は出来るんですが、 LTerm_draw.draw_char を使っていった時に、色々と気になる問題がありました。それは、multi byte(ここでは日本語)を表示しようとした時に、なぜか表示されない、ということでした。 解決 備忘録なのでさっさと行きますが、原因は LTerm_draw.draw_char のcolumn指定の誤りでした。 LTerm_draw.draw_char のシグネチャは、以下のようになっています。 val draw_char: ?style:LTerm_style.style -> LTerm_draw.context -> int -> int -> Zed_char.t -> unit さて、Zed_charですが、こいつは zed というライブラリが提供しているmoduleです。こいつはunicodeを保持していて、保持している文字の幅も持っています。 Zed_char.width で取得できます。 LTerm_draw.draw_char の挙動ですが、基本的にはterminalのascii 1文字を1columnとして描画します。ただ、 Zed_char.width が1より大きい場合は、1より大きい分だけSizeHolderというダミー文字で埋めるようになっています。 この挙動がわかっていなかったので、multi byteを1columnずつずらして表示しようとすると、一つ前に表示したmulti byteを消したのと同じ状態になってしまっていました。 実際、multi byteを考慮した上で LTerm_draw.draw_char を使う場合、以下のようにする必要があります。 let str = Zed_string.of_utf8 "テストfoo" in let style = LTerm_style.none in Zed_string.fold (fun ch index -> LTerm_draw.draw_char ~style ctx 0 index ch; index + Zed_char.width ch ) 0 |> ignore わかってしまえば納得ですが、中々ドキュメントだけでは分かりづらいことなので、誰か(主に自分)の役に立てばと思います。 ...
気づいたら来週で今年の業務も終わりということに気づきました。今年もいろいろ・・・あったか? 今回は、最近色々と辛くなってきたので、初めてProtocol Buffers・・・というかProtocol Buffers languageを利用して、サーバー側=OCamlとクライアント側=TypeScriptで型定義を共有していきたいと思います。 ...
この記事は株式会社オープンストリームアドベントカレンダーの3日目の記事です。 年の瀬ということで、いい感じに部屋が冷えてきてちょうどよいです。さて、 あまりネタがない 今回はOCamlの開発環境について書こうかと思います。 ...
ようやく涼しくなってきたと思った瞬間に晩秋になってしまい、秋がなかったなぁ、としみじみと感じてしまいました。 第三回は、Cross Compileできたものの、上手く動かない、というときに役立つデバッグについて書きます。 <!–more–> Cross Compileしたバイナリの難しさ Linux上でクロスコンパイルしたバイナリですが、実際にこのバイナリを動かしてみると、問題が発生(Segfaultとか)することがあります。 特に最初はWineで動かすと思いますが、エラーの内容がメモリアドレスくらいしか無く、結構色々と辛いです。Windows上で実行してみるのも中々にしんどいです。普通にそのまま実行時エラーで落ちるので。 Visual Studioとかで動かしてみる、というのも手段だと思いますが、ここではあくまでLinux上で解決してみます。 gdbserverとgdb gdbには、remoteのgdbと繋げてローカルで実行できる gdbserver というツールが存在しています。 Debianであれば、まず以下でmingw向けのgdbserverと、mingwでコンパイルされたtargetをデバッグできるgdbをインストールします。 apt install mingw32-w64-gdbserver mingw32-w64-gdb-target これを使うと、以下のようにしてdebugを行えます。 wine /usr/share/win64/gdbserver :3000 sample.exe x86_64-w64-mingw32-gdb sample.exe # ここからGDB内 > remote target localhost:3000 # つながると普通の(若干コマンドが成約されていますが)gdbとして使えます。 > continue Program received signal SIGSEGV, Segmentation fault. 0x0000000000a19d1c in lwt_unix_not_available (feature=<optimized out>) at lwt_unix_stubs.c:107 107 lwt_unix_stubs.c: No such file or directory. (gdb) bt #0 0x0000000000a19d1c in lwt_unix_not_available (feature=<optimized out>) at lwt_unix_stubs.c:107 #1 0x0000000000a1b400 in lwt_unix_iov_max (a1=<optimized out>) at windows_not_available.c:16 #2 0x00000000008611ed in camlLwt_unix__entry () #3 0x0000000000000001 in ?? () 上記のように、Windows向けにビルドしたバイナリを、Linux上でデバッグできます。OCamlでビルドしたものであれば、上記のようにcaml系統のデバッグシンボルも見えるので、デバッグがはかどります。 ...
歓迎会と送別会が連チャンになったらだいぶグロッキーになってしまい、睡眠を削って生活するのはもう無理だなぁと思いました。十分な睡眠によるパフォーマンス向上をなめてはいけない・・・。 さて、第二回は実際にCross Compileを行うための環境について書きたいと思います。 <!–more–> ビルド環境の選定 まずOCamlプログラムのクロスコンパイル・・・というかWindows向けのビルド環境をどうするか?です。Windows向けにビルドする場合、次のいずれかが主要な選択肢になります。 Linux上でMinGWをインストールし、Windows向けのバイナリをクロスコンパイルする Windows上でOCaml/OPAMをインストールし、Windows向けのバイナリをネイティブコンパイルする Windows上でコンパイルする場合、OCaml/OPAMそれぞれをビルドするか、もしくは配布されているバイナリを展開する方法があります。この内、自前でビルドする方法は後述するとおり結構厳しいです。一応チャレンジしてみましたが、敢え無く爆砕しました・・・。 と、いうことで、Linuxでのクロスコンパイルに一縷の望みを託してみます。 Windows上のOCamlについて OCaml本体は、実はWindows環境上でもちゃんとコンパイル・実行できるようになっています。(MSVC/Cygwinのいずれかが必要です)また、OPAMもWindows上でビルドできるようになっています・・・が。 OPAMをMinGWでビルドしてしまうと、 opam init が上手く動作しない、という問題が発生します・・・。これはOPAMでも認識されている問題です。このため、OPAMをWindows上で動かす場合、OCaml本体もCygwin向けにコンパイルする必要があります。 しかし、Cygwinでコンパイルしてしまうと、Cygwin1.dllというdllを同梱しないと動作しなくなります。Cygwin1.dllはライセンス的にも結構厳しいため、できれば付けたくありません・・・。 Linux上でのクロスコンパイル環境 OCamlプログラムを、Linux上でWindows向けにコンパイルする方法は、 MinGWでコンパイルされたOCamlコンパイラでコンパイル・リンクする という形になります。本来、Linux上でMinGWを利用してコンパイルされたバイナリは、Windows環境またはWineでしか動きません。 ここで前に紹介したocaml-cross-windowsが効いてきます。このリポジトリでは、Windowsバイナリを生成でき、Linux上で実行可能なOCamlコンパイラを提供してくれています。ただ、ocaml-cross-windowsでは、Debianとかを推奨?している雰囲気があります。しかし私の利用しているDistributionはGentoo・・・。 と、いうことで、こういうときはDockerに頼ります。Debianのベースイメージから、クロスコンパイル環境を整えます。 クロスコンパイル用のイメージを作成する FROM debian:bullseye RUN apt update \ && apt install -y --no-install-recommends opam gcc-mingw-w64-x86-64 gawk m4 git ca-certificates \ && rm -rf /var/cache/apt/archives \ && opam init -n --disable-sandboxing \ && opam switch install 4.08.0 \ && opam repository --all add windows git://github.com/derui/sxfiler-repo-windows \ && eval $(opam env) \ && opam install -y conf-flambda-windows \ && opam install -y ocaml-windows64 \ && opam install -y ocaml-windows COPY scripts/entrypoint.sh /entrypoint.sh RUN chmod a+x /entrypoint.sh ENTRYPOINT ["/entrypoint.sh"] entrypoint.sh の内容は、opamを利用できるようにしているだけです。ここでのポイントは、aptでopamを入れることで色々楽していることと、64bit向けのMinGW環境を導入していることです。gawkを入れているのは、地味にこれがないとOCaml自体のコンパイルに失敗するためです。 ...
今回から何回かに分けて、今取り組んでいるOCamlのcross compileについて書いていきたいと思います。おおよそ三回位を予定しています。 第一回は、OCamlのcross compile事情について書きたいと思います。 <!–more–> Cross compileとは Cross Compileの正式な定義はいまいち判然としませんが、ここでは あるプラットフォーム上で、別のプラットフォームの実行ファイルなどをコンパイルする という定義にします。つまり、Linux上でWindowsのバイナリをコンパイルする、という感じです。 OCamlのcross compile事情 2019/10の時点で、一定以上の普及率かつ、上記の定義におけるCross complieを最も簡単に行える言語は、間違いなく Golang であることは論を待たないと思います。これは、Golang自体がlibc aware(というか再実装していた気がする)、かつ、デフォルトで各architectureへのコンパイルが可能な環境が整っているためです。この点から、マルチプラットフォームなCLIとかを作る時にGolangがファーストチョイスになっているのでしょう。 同じような感じで、 Rust もCross compileは簡単な部類だと思われます。(ここでは、シングルバイナリかどうか?というのは主題ではないので無視しています) これらの言語の共通点は、どちらもシステムプログラミングを主眼に置いた言語、ということも設定に影響していると思います。 翻ってOCamlですが、これらと比較すると中々厳しいです。何故厳しいのか?を考えてみると、以下のような点が挙げられそうです。 OCaml自体、複数のプラットフォーム向けのバイナリを出力するようにできていない OCamlはすでにだいぶ歴史のある処理系であり、2010年代に生まれた言語と背景が異なるので如何ともしがたい点もあります OCamlのユーザー内でニーズがない 最近になってニーズが出てきた・・・というよりも、Golangのこのfeatureないの?みたいな声が上がって来た感じです ライブラリがWindows/Linux両対応していないケースが多い OCamlのユーザーベースはLinuxが多い(偏見)なので、Linuxのみ対応しているライブラリが多いです ただ、ネットワーク周りは、Mirageの成果もあって、Pure OCamlで大体なんとかなります OPAMをwindowsで動かすのが超難易度 ・・・というよりも、2.0.5現在対応していません このため、Windows上でネイティブコンパイルすること自体も難しいです(近い将来出来るようになりそうですが こういう実態から、 OCamlのみでCross compileは出来ない というのが結論です。では諦めるしか無いのか?OCamlでマルチプラットフォームなアプリケーションを作るというのは夢物語なのか? ocaml-cross-windows しかしここで救いの手が。OCamlが好きな人々や、OCamlをマルチプラットフォーム(ここではWindows)で使いたい人々によって、ocaml-cross-windows というリポジトリが運用されています。主にLinux上でMingwを利用して、Windows向けのバイナリをコンパイル出来るように工夫されています。 これが自動的にopam-repositoryからmirrorされて自動的にアップデートされていく・・・のであればいいんですが、そうは問屋がおろしません。 公式のopamから、ocaml-cross-windows向けにportingするのは、ドキュメントに従っていけば意外と簡単なんです。ただ、例えばppxを利用していたりすると、ocaml-cross-windows上のpackageとopam公式の両方を入れないと動かなかったり、その逆もあります。また、最近のstandardであるduneを利用していないpackageの場合、色々と対応しないといけなかったりと、中々自動では難しいです。 しかし、現時点では非常に有力な選択肢です。このシリーズでは、これを使っていくことにします。 アプリケーション用のrepositoryという選択肢 ocaml-cross-windowsは、コミュニティによって運営されているため、足りないpackageがあれば、Pull Requestを出すのが正道です。・・・ただ、今やりたいのは、自分のアプリケーションをコンパイルしたいのです。そのためには、dirty hackも辞さない感じです。 となると、アプリケーション用のopam repository、という選択肢も上がります。単純にocaml-cross-windowsをcloneしてremoteと名前を変えれば、一応形になります。 当然、本来であればコミュニティに還元するのが自分のためにもなります。私も後で行うつもりですが・・・。 実際にocaml-cross-windowsから作って、自分が必要なpackageを追加しているのが、下のリポジトリです。 https://github.com/derui/sxfiler-repo-windows packageは用意できた ので、次はOCamlとOPAMを使ってCross compileする環境について書きたいと思います。
WAVから十二音技法に基づく音程を、gnuplot形式で抽出するCLIをほそぼそと作っています。 wav-pitch-extractor まだreadmeもない+完全に自分用の実装なので、仕事であればやらないような、車輪の再実装もやっています。その中で、 Fast Fourier Transform 、通称FFTも実装してみたので、その感想を書こうかと思います。 <!–more–> FFTとは? まずFFTが何か、簡単に説明します。もっと正確でもっと詳しい説明は色んなサイトに載っているので、そちらを見ていただくとして。 なお、単純にFFTだけで検索すると、 Final Fantasy Tactics の方も引っかかってきて、一定以上の年齢層にとって懐かしい気持ちになること請け合いです。 閑話休題。 FFTは上でも書いた通り、 Fast Fourier Transform のアナグラムです。日本語だと 高速フーリエ変換 という名前です。 高速 という修飾があるので、 フーリエ変換 を高速にやるアルゴリズム、ということです。 フーリエ変換とは 実際にはフーリエ変換と離散フーリエ変換で別々のものとして扱われているんですが、プログラムでは基本離散フーリエ変換しか扱えないので、以下でも離散フーリエ変換をフーリエ変換として扱います。 フーリエ変換は、端的に言うと ある複素関数からある複素関数への変換 というものを指します。現実的には、デジタル信号の周波数解析などで使われるのが一般的、とのことです。以下の式で表されます。 \begin{equation} F(t)=\sum_{x=0}^N f(x) \mathrm{e}^{-i \frac{2 \pi t x}{N}} \end{equation} ・・・とだけ書かれても、高卒程度の知識しか無いと、ファッ?ってなってしまいます(なりました)。特に右辺のネイピア数の辺りが鬼門です・・・が、実はここはオイラーの公式というものを使うと、三角関数だけで書けます。オイラーの公式は次のように定義されています。 \begin{equation} \mathrm{e}^{i\theta} = \cos{\theta} + i \sin{\theta} \end{equation} これに関連する特に有名なものとして、オイラーの等式があります。と オイラーの公式を最初の式に適用すると、ある時間の振幅に、オイラー公式で表される周期関数がかけられたものを、全時間分足したものが、ある周波数のスペクトルになる、ということになります。 私の説明は単に現時点での私の理解ですので、より正確な定義などを知りたい場合はより信頼できるサイト・文献をあたったほうがよろしいかと・・・。 素朴なフーリエ変換と問題点 上記の定義に従うと、次のように書けます。 let dft (data : float array) = let size = Array.length data in let radian = 2. *. Float.pi /. float_of_int size in Array.mapi (fun index _ -> let start = ref Complex.zero in Array.iteri (fun index' v -> let v = {Complex.re = v; im = 0.} in let times = float_of_int index *. float_of_int index' in let next = {Complex.re = cos (radian *. times); im = sin (radian *. times)} in let next = Complex.mul v next in start := Complex.add next !start) data ; !start) data (* dft [|1.0;2.0;3.0|] *) 素朴なので、メモリ使用量がー、とかは気にしていません。なお、数式をプログラムに起こすとき、 \( \sum \) はfor loopでの足し算に相当します。 ...
ちょっとしたCLI(ある程度出来たら記事にします)をOCamlで作っています。その際、初めてppx_letを使ってみたんですが、なかなか良かったので紹介します。 <!–more–> ppx_letは、OCaml界隈では知らない人のいないJane Street社が公開しているPPX拡張です。 OCamlでmonadを扱う時の課題 大抵、resultとかoption(特にresult)をmonadicにつなげていく時、大抵は下のような書き方になります。 let bind v ~f = match v with | Ok v -> f v | Error _ as v -> v let (>>=) v f = bind v ~f let foo = function | v when v > 10 -> Ok v | _ -> Error "error" let bar v = Ok (v * 10) let () = let v = foo 12 >>= bar in match v with | Ok v -> Printf.printf "result %d\n" v | Error e -> Printf.printf "error %s\n" e >>= とかのoperatorを使うのが一般的かと思います。これは非常にシンプルな例なので、operatorでも特に困りません。 しかし、ある程度の規模になってくると、ある時点で取得したデータを、後に使いたい、ということが非常によくあります。 foo v >>= fun v1 -> bar v1 >>= fun v2 -> foobar v1 v2 これが続くと、特にreadabilityの点で問題になってきます。 ...
このところOCamlでアプリケーションをほそぼそと作っているのですが、その過程でClean Architectureっぽいものを採用してみました。 <!–more–> 作っているアプリケーション自体は、完全に趣味の領域のものなのでまだ公開していません。ただ、OCamlであってもなんであっても、ある程度の規模になったらなんらかの方法論は必要かな、と思い始めました。 packageそのものがいっぱいある moduleもいっぱいある どことどこが関連してるかよくわからなくなってくる これはもしかしたらmodule/packageの分割方法自体が問題か・・・? ある程度Clean Architectureっぽいことも出来るようになってきたので、自分の知識を整理する上でも書いてみます。 Clean Architectureとは? ググってもらうのが一番早いのですが、それは流石に不親切すぎるので・・・。 Clean Architectureは、 http://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html が原典とされ、 Robert C. Martin によって提唱されたアーキテクチャです。 Onion Architecture/Hexagonal Architectureなど、過去のアーキテクチャも参考にしながら作成されたものになり、それらの特徴も一部受け継いでいます。 Clean Architectureは、以下のような制約を導入します。 ある層は、それより外側の層に依存してはならない ある層は、それより内側の層にのみ依存する これがClean Architectureにおける唯一の制約です。層としては、基本形として以下を提唱しています。 Entities いわゆるドメインモデルです Use Cases アプリケーションロジックです。Domain Logicよりは特殊化されていますが、サブシステムとかがないアプリケーションだと、大体がここにロジックがある感じになるそうです Interface Adapter/Gateway/Presenter UIやData Storeなど、外部との連携を行うための層です。 APIをGatewayとして実装する、UIの情報をPresenterから返す、とかいろいろあります Frameworks and Drivers Framework specificな処理とかです APIをFrameworkの機能を利用して作成する、とかであればここになります ただ、これ以外にも 層を 追加することは可能です。唯一の制約である、層の間にある依存方向を守る限り、層の増減は自由です。 OCamlでの課題(自分的に) OCamlである程度の規模のアプリケーションを作成する場合、恐らくある程度の単位でパッケージを作成する場合が多いと思います。最近は dune を利用すると思いますが、使わない場合であったり、一パッケージに全ファイルを置くのは色々問題があります。 module名がめっちゃ長くなる 1ファイル1moduleとかやると、ファイル名もめちゃくちゃ長いですが、そのmoduleを利用するときにすごく面倒になります 一般的なモジュール名がかぶる 特に被るのが Types とかの名前です 1ファイルに複数のmoduleを書くと見通しが悪い 1データ型1module主義とかやると、単純にファイルの中身がかなり長くなります OCamlerはあまりその辺は恐れないようですが。 この辺はfoldとかをうまく使えばいいのかもしれません 特に、頻繁に使うmodule名が長いと色々だれますし、毎回aliasを書くのもしんどいです。以前よりもmoduleを明示しなければならないケースは少なくなりましたが、一級moduleを利用しているとやっぱりきついです。 パッケージを分ける上での基準も厄介です。大抵は機能とか役割別だと思いますが、結構分けづらい感じになっていったりで・・・。 そこで、パッケージを分ける基準として Clean Architecture を使ってみました。 OCaml on Clean Architecture とはいっても、基本的には各層ごとになるようにパッケージを分割するだけです。そうすると、dependenciesの方向制御も簡単になります。私は、 domain, usecase, gateway, binary と分けています。こうすると、OCamlの制約とduneの機能で、以下のような利点を自動的に得られます。 ...