近況 2021-11-30

今月の活動 (ミローネ言語、Next.js、興味深かった記事など)

ミローネ言語

https://github.com/vain0x/milone-lang/compare/8b0cbdfbca6387ed1777fe8aaf3199a7cd897653..a858f5a1884e332b9fde90327d90b1eb405b40c6

いくつかの機能を追加した。

  • 多相な判別共用体型の単相化を実装した
    • 関数の単相化と同様
    • 型の使用箇所を探し、適用された型引数を記録する
    • 適用された型引数ごとに定義を複製する
    • 型の使用箇所を複製してできた型 (単相な判別共用体型) に差し替える
  • Option型の実装をライブラリ側に移動した
  • 一部の型に対する = の実装を導出する機能を実装した
    • = の定義箇所を探し、適用された引数の型を記録する
    • 適用された引数の型に対する = の実装を生成する
    • 使用箇所を導出された実装 (関数の呼び出し) に差し替える

LSPサーバーの改善をしている

  • 実装の方針を変えて、可変なコレクションを使わない、静的変数を使わない、ファイルシステムに直接アクセスしない、といった感じにした
    • テストコードを書くため
    • ミローネ言語版での実装に流用しやすくするため
    • もともとは雑かつすみやかに作るという方針だったから、そういう実装方針にしていた
    • 可変なコレクションは、実際にイミュータブルに書き換えてみると、書きやすさにたいした違いはない気がした
      • foldに慣れたからかもしれない
    • 起動時に初期化される値や、ファイルシステムにアクセスする関数などをパラメータで渡すのはめんどくさい
      • これはdependency injection (DI)としてプロダクションのコードではよくやりがち
  • textDocument/documentSymbol を実装した
    • VSCodeだと「アウトライン」にシンボルの一覧を出してくれたり、コマンドパレットで @シンボル名 と書いてシンボルの位置に移動できたりする
  • 型やモジュールのシンボル参照を雑に解決するようにした
    • 型検査の後の中間表現では、構文木に出現する型やモジュールが何に解決されたのか分かるようになっていない
    • 型名に対して定義の参照などは行えなかった
    • 型やモジュールは単純に名前で探すような実装をした
    • 名前のかぶりがあると正確に動作しないが、実際に名前がかぶることは少ない
    • 正確にできないからといって実装しないより、不正確でもあったほうが便利だった
  • 入力補完を実装しようとした
    • 指定された位置に . があるかどうかはトークン列から判断できる
    • その . の手前にある名前 (パス) や式を取り出すといったことが、いまのデータ構造ではうまくできない
    • トークン列と構文木の構造と位置情報から逆算して、untypedな構文木を構築できないか試した
      • わりとうまくいきそうにみえたが、時間を使いすぎているので後回しにした

ミローネ言語版のLSPサーバーを作りはじめた

  • ミローネ言語のもともとの目的がセルフホスティングなので、LSPサーバー自体もミローネ言語自身で書くのがよい気がする
  • LSPサーバーは、コンパイラと違ってlong-runningで、構造はそれほど複雑でなく、実用性が高いので、アプリ実装の好例になる
  • 入出力は普通にファイルの読み書きと同様だから簡単にできる
  • JSONのシリアライズとデシリアライズは、CのライブラリをリンクしてFFIで関数を呼ぶことで実装した
    • C側でミローネ言語のオブジェクトを作るときに、オブジェクトのメモリレイアウトを決め打ちしている (コンパイラの実装に依存している)
    • ふつうのミローネ言語の利用者はこれをできない (やってはいけない)
    • FFIの生焼け感が出てる
  • 標準入力の待機と、受け取ったリクエストの処理はマルチスレッドで並列に行う
    • 書いてから気づいたが、標準入力をノンブロッキングで読めばよかったかもしれない
  • アプリの状態はSQLiteで持つ
    • SQLiteでインメモリデータベースを作るということ
    • ミローネ言語のメモリ管理の方針はいわゆるアリーナアロケーション
      • ある処理の内部では、すべてのオブジェクトをメモリプールに確保して、最後にメモリプールを解放する
      • オブジェクトを持ち出すことはできない。可変なオブジェクトがなくて、処理の返り値はint固定なため
      • (__inRegion プリミティブを参照)
    • 例えばリクエストが来るたび、それに関して何か処理して、レスポンスを書き込んで、一括解放、という流れ
    • 欠点は、リクエストの処理を超えてデータを持つことができないこと
    • 対策として、リクエストの処理中に、必要なデータをデータベースに書き込み、後で読み出す、ということにする
    • 同様に、標準入力から読んだリクエストも、データベースに書き込む
      • SQLiteはスレッドセーフなのでOK
    • データベースを可変な変数と同期機構として使っている
  • 適当なタイミングでdiagnosticsを送るという部分だけ書いた
    • コンパイラに依存するとコンパイルが遅くなるから milone check コマンドを呼んで標準出力をパースするという実装にしている
    • プロセス生成のコストが高いことと、保存されていないファイルの内容は参照されないなどの問題がある
    • プロジェクト単位で、コンパイル結果をキャッシュする仕組みがあったほうがいいかもしれない

Next.js

  • 新しくウェブアプリを作りだしている
  • Node.js、TypeScript、express.js (Webサーバーライブラリ)、Reactという構成は引き継ぐ
  • いままではフレームワークを使っていなかった
  • 実装の一部に自作フレームワーク感が出てきて、よくなさそうな気がする
  • Next.jsを試すことにした
  • Webpackの設定をしなくてもホットリロードなどがついてきたり、ルーティングの仕組みが標準であったりして、うれしい

TypeScriptのプロジェクト間参照を試した

  • いままではアプリ側のコードを1個のプロジェクト (package.json/tsconfigという意味) でやっていた
  • 課題感があった
    • コードが大きくなるにつれてTypeScriptコンパイラの応答 (入力補完など) に機敏さがなくなってくる
    • フロントエンド側のコードを書いているときに、サーバーの一部だけで使っているライブラリのシンボルなどが入力補完にたくさん出てくる
  • プロジェクトを分けることにした
  • Web版、CLI版をそれぞれ別のプロジェクトにして、それらから共通で参照されるcoreプロジェクトを作った
  • わりとうまくいっている
    • デフォルトの設定からはずれるので、追加の設定が必要だったりハック的なことが必要だったりする
    • Next.jsの外側(cli版とか)は自分でやらないといけないので、webpackとかから完全に解放されるというわけでもなかった

F# の入力補完がなぜか速くなった

一瞬で出るようになった。これかな?

記事

記事を書いた:

読んだ記事で興味深かったもの:

  • IDEs and Macros (2021-11-21)
    • IDEとマクロの相性が悪いという記事
    • 感想: IDEとバッチコンパイラが必要とする言語の性質の違いが出てておもしろい
  • なぜElmは0.19のままか、変化すること/しないこと - Runner in the High (2021-11-13)
    • Elm (言語) は変化し続けることや機能を追加し続けること自体をよしとはしないという記事
    • 感想:
      • 機能を最小限に保つことの重要性はよくわかる
      • 一方、現代社会において、変化しつづけて注目を受けることは重要である
      • 言語の良さはエコシステムの強さやユーザー数の影響も大きい
      • 新しい機能を貪欲に試していく言語があるからこそ、言語機能の良し悪しを評価できるという面もある
      • 小さくて安定した言語は、プログラミング言語の long-term support (LTS) 版とか、言語機能のベストアルバムみたいな感じで、定期的に作って基準にするとよいかもしれない

関連記事