NeovimでOCaml
OCaml書きたくなったので触ってみる。
環境
- Manjaro
- Neovim v0.10.0-dev
- fish 3.6.1
paru -S ocaml
v5.0.0を入れる。READMEに結構色々変更入ってるとか書いてあったけどこれが始めてなので気にしない。
MasonでLSPをインストールしようと思ったけど、opamとかいうOCamlのパッケージマネージャ?が無いと怒られたのでインストール
paru -S opam
それでも無いと怒られた。調べたところopam init
すれば良いらしい。あとなんかおかしくなったら~/.opam
を削除すれば良いらしい。こういうゴリ押し出来る仕様はとても好き。
Do you want opam to modify ~/.config/fish/config.fish? [N/y/f]
(default is 'no', use 'f' to choose a different file)
と聞かれたのでYesと答える。...と思ってEnterを押したらデフォルトでNoだったらしく、手動でsource /home/coma/.opam/opam-init/init.fish > /dev/null 2> /dev/null; or true
を実行する。
シェルを再起動して再びMasonを実行。今度は良い感じ。
LSPをインストールしている間に基本的な文法を調べてみる。(事前情報がCoqと関数型くらしかない)
print_string "Hello World";;
で行けるらしい。セミコロン2つなのはなんか新鮮。
処理系の話とか気になる(主に実行速度の面で)けどあんまり書いてない...セルフホスティングしてるっぽいし、多分直にバイナリになる感じなのかな。
OCAML入門にduneっていうビルドツールを使うのがオススメと書いてあったのでインストール。
dune init project NAME
でプロジェクトを新規作成出来るらしい。便利。
とりあえず実行する前にdune build @install
で依存関係をインストールしてみる。
完了したらdune exec
とかいうもっともらしいコマンドを実行してみる...けどエラーになった。
ファイル名とか指定してみたけど何も出力されないのでプロジェクト名を入れてみた。
無事実行された。
せっかくなのでなんか作ってみる。
シャワー浴びてる間に定期的にHTTPリクエストを送信するコマンドがあるとAPI開発が捗りそうなのでそれを作ってみたいと思う。
OCamlでHTTPする際にはcohttp
というライブラリが良いらしい。
opamでインストール
opam install cohttp-lwt-unix cohttp-async
パッケージをインストールしていて気が付いたんだけど、OCamlのビルドは結構遅い。Rustと良い勝負じゃないかと思う。(多分Rustの方が速い)
これはHaskellとかもそうだったので関数型言語の宿命みたいなものなのかもしれない。
opamでインストールしてもLSPでエラーが出てしまうので色々調べた。
その結果、なんとduneでは使用するライブラリをdune
でS式を使って指定する必要があるらしい。(Lispやっといて良かった~)
とりあえずCoHttpのサンプルコードを実行するために書いたduneファイル
(executable
(public_name ocaml)
(name main)
(libraries ocaml cohttp-lwt-unix cohttp-async lwt_ssl))
(opensslをインストールしてからじゃないとエラーが出るそうなので要注意。参考)
これはopam install
を実行してから書く必要がある。
ちなみに、duneでプロジェクトを作った際にopamのインストール先がローカルになるようなので、グローバルインストールしてバージョンが~みたいな事は心配しなくて良さそう。とても良き。
dune exec ocaml
で無事実行された。RedditのHTMLとレスポンスボディを取ってくるコードらしい。
環境も整った事なので早速先述したプログラムを解読していく。
プログラム全文をここにも貼ってみる。原文はCohttpのREADME
open Lwt
open Cohttp
open Cohttp_lwt_unix
let body =
Client.get (Uri.of_string "https://www.reddit.com/") >>= fun (resp, body) ->
let code = resp |> Response.status |> Code.code_of_status in
Printf.printf "Response code: %d\n" code;
Printf.printf "Headers: %s\n" (resp |> Response.headers |> Header.to_string);
body |> Cohttp_lwt.Body.to_string >|= fun body ->
Printf.printf "Body of length: %d\n" (String.length body);
body
let () =
let body = Lwt_main.run body in
print_endline ("Received body\n" ^ body)
let body =
以下の処理の内容をbody
に束縛している。
https://www.reddit.com/")
Client.get (Uri.of_string "これ単体で「文字列をURL形式に変換してGETリクエスト」を行っているっぽい。
>>= fun (resp, body) ->
急に見慣れない記号が出てきたけど、関数型言語あるあるのパイプ亜種みたいなものなのかな。
これで先述のClient.get()
の戻り値が(resp, body)であるという事が分かる。ノリとしてはJSのArrow Functionに近い...?
let code = resp |> Response.status |> Code.code_of_status in
code
は多分HTTPステータスコード。fun() ->
で末尾が->
なことから多分この行から関数のブロックに入ってるっぽい...と思ったけど戻り値の定義な可能性が捨てきれないからChatGPTに聞いてみたら戻り値の型らしい。なんでletが使えるんだ...?と思ったのでそれも聞いてみたところ、一時的に結果を格納する為にletを使うことが出来るらしい。
つまり、
-
->
は関数の戻り値を値を表していて、 - 明示的に型を表記せず、
- 一時的に値を代入している
と上のような書き方になるっぽい。
in ~
let in~
と書くことで複数行の処理の結果を変数に束縛する事が出来る。JSで言う即時関数に近いものを感じる。
let code = resp |> Response.status |> Code.code_of_status in
Printf.printf "Response code: %d\n" code;
Printf.printf "Headers: %s\n" (resp |> Response.headers |> Header.to_string);
body |> Cohttp_lwt.Body.to_string >|= fun body ->
Printf.printf "Body of length: %d\n" (String.length body);
body
|>
は関数型言語おなじみのパイプ処理。わざわざこんなスクラップを見る人は既に知ってると思うのであんまり解説しない。
Printf.printf()
はおなじみ標準出力をする関数。OCamlは副作用を許容する仕様なのでこういう副作用が発生する関数もカジュアルに使える。
最後のbody
でreturn相当の処理をしている。つまりこのbody
の結果が最初のlet body =...
の変数に束縛される。
let () =
ChatGPT曰くユニット型(unit type)を持つ式を実行するために使われる特殊な構文で、副作用を持つ関数を実行するために必要らしい。ChatGPT曰く処理が順番に実行されるらしいので非同期云々をしたい時はまた別のアプローチがありそう。