Lisp (Scheme) に入門していたつもりが、いつの間にか WebAssemblyをかじっていた件
何を言っているかわからねーと思うが (ry
TL; DR⌗
モチベと背景⌗
元はと言えば関数型プログラミングってなんぞや? ってなってたんですよね。
もっと言うと、流行りのプログラミング言語はよく 「マルチパラダイム」 だと言われていて、特に関数型プログラミングの機能を部分的に取り入れていて~ って話題になりがちなのですが (要出典)、それ以前の元々の関数型言語ってどんな姿で、 それらはどんなアイデアをもとに書かれていたのだろう? みたいな。
プログラミング言語 Lisp⌗
え? Haskell? すごいH本もいま読んでますんで……
文 (statement) を使わずとも式 (Expression) さえあればプログラミングはできてしまうらしい。ただしCとはちょっと違う姿に。 後述。
数ある Lisp方言の中でも Scheme は変数スコープが工夫されているそうで。
あと、データ型が緩くて可変長の配列とか構造とかを好き放題いじくれるのやべえなって。
Chicken Scheme⌗
Scheme → C言語 トランスパイラ兼コンパイラ。
先に述べた不思議な言語をCと組み合わせられると聞いて手を出した次第。
Brainf*ck⌗
プログラミング言語。読めない。 でも機能が少ない分コンパイラが比較的簡単に書けるらしい。 今回はインタプリタにしたけれど。
Lispはプログラミング言語 (のコンパイラとか実行環境) の開発にも適すると風の噂で……
中々自分で書けなくてひとのコードでデバッグせざるを得なかった
Emscripten⌗
C/C++コードを WebAssembly実行ファイルにコンパイルし、 さらにそれをブラウザで読み込ませるJSのコードまで生成してくれるSDK、なのかな? 後述。
やっていて感じたこと⌗
関数型プログラミング……?⌗
; 8bit符号なし整数の範囲からちょっとズレた値を調整する
(define overflow
(lambda (x)
(cond ((< 255 x) 0)
((< x 0) 255)
(else x))))
if文の()内によく x < 0
とかって書いたりしますが、
Lispでは<
とか+
も前置する関数、
ここでは手続き (procedure) と呼ばれるものになるんですね (注1)。
それと cond
は “condition"の略で、switch文みたいな分岐をしてくれる子です。
ただしこれも引数がいっぱいある「手続き」になっていて、
マッチした条件式のあとの式が評価されて返されます。
また、一度束縛した変数は基本的に不変 (immutable) で、
必要に応じて set!
のような !
マークの付いた手続きで上書きも一応は可能、
くらいのスタンスらしいです (注2)。
というのもループなどを含めそもそも破壊的変更が必要なシーンが無いんですね (それこそパフォーマンスの都合でメモリを上書きしたい、とかそんくらいかもしれない)。 例えば再帰呼び出しする関数も、 その関数単体は引数が同じなら同じものが返るやつ、みたいなイメージ?
他にも、遅延評価 や継続渡しスタイル といったコンセプトが有名らしいです。 前者はちょっとだけ今回も使っています。
言うまでもない話ではあるのですが、これらもいわゆる「銀の弾丸」ではなく、
ロジックがミスっていれば普通にコケます。上記のコードも、一度に ±1
より大きく増減した数値に対しては思ったように動かないはずです。
リスト操作は計算コストが大きめ⌗
- 再帰的に参照してるのだからそりゃあそう
- Brainf*ckインタプリタのメモリの実装をベクタに変えたら倍速くらいに
- これはこれで多分ですが頻繫にヒープメモリを触っていて、
C言語の固定長配列よろしくスタックにものを置く実装より重いのでは
- もちろん柔軟で便利ではある
- これはこれで多分ですが頻繫にヒープメモリを触っていて、
C言語の固定長配列よろしくスタックにものを置く実装より重いのでは
別の話: 特殊なことをしようとするとビルドがちょっと大変かも⌗
最初は手元で Schemeのコードを動かしてキャッキャしてたんですけれども、 WebAssemblyにしたらインストール不要な形で公開できんじゃね? と思い立ちあれこれ試していたら丸一日以上溶けました。
参考: SchemeのコードをCのプロジェクトとして扱うチュートリアル
Schemeのソースファイルを Cのソースファイルに変換するまではいいけれど、 それを Clangでコンパイルさせるには……?? と (まぁモジュールのソースコード読めばいいのですけれど、 最終的に今回は依存関係の解決を手動でやっちゃっていたという)。
Chickenで Emscriptenをやっているひとが中々見つけられず、 唯一見つけた例もモジュールがほぼ不要な Hello Worldコードだったのでその先は試行錯誤しました。
/bin/ld: /tmp/vectorbfi-b5a9f6.o: in function `f_332':
vectorbfi.c:(.text+0x926): undefined reference to `C_srfi_2d4_toplevel'
例えばこのエラーは srfi-4
というモジュールが不足している、という意味なのですが、
シンボルの名前が保持されていたりいなかったりで最初面食らっちゃいました。
最終的な手順はこちら: https://gist.github.com/renem2185/eb907c6b0f554f95de929420d7d20a77
ビルドとグルーコードの作成さえできちゃえば、 もう今回のように Hugoと GitHub Pagesでデプロイできちゃいますよと。最高ですね。
……多分、単に WebAssemblyをやりたいだけだったら Rustとか C++とか、 他に幾らでも選択肢はあるしその方が最適化できるんじゃないかな?
雑感⌗
- たのしい (小並感)
- 関数型プログラミングの心がちょっとだけわかった気がします
- いくらか心身の快復を感じました、療養中なんでこんなもんでしょう
- てか正直これ動くと思ってなかったので……
- レガシーっぽい技術も深掘りすると面白い、かも
残件⌗
Chickenのクロスコンパイル機能を試す⌗
- Chicken側でも Clangのオプションが使えるらしい
- つまり Scheme → LLVM IR → WASM みたいな手順で楽にコンパイルできるのでは
- Emscriptenに頼らない方が楽という説も
- ついでに WebAssemblyインスタンスのメモリが不足する? 問題も調べたい
ChickenのFFIを試す⌗
- Scheme視点からCのコードを includeする例は公式サイトにあった
- なんとかして Schemeの関数をWASM側に公開できないかな~?
Brainf*ckコンパイラは書かないの?⌗
- 気が向いたら
ここまで来たら他のプログラミング言語も習得できるはず!⌗
- そもそも Rustとかの予習を意図していた
- あとは C++のスマートポインタとかかな
脚注⌗
- ここでいう手続き (procedure) はSQLのそれとは別物。大抵何かしら返ってくる。
- 実装によるみたい? 参考: Gauche:immutableなデータの注意点