Movatterモバイル変換


[0]ホーム

URL:


Zenn
mizchimizchi
🤖

Unison 言語から、「次」の言語を考察したい

に公開

関数型まつり以降、Unison 言語が気になっています。

https://www.unison-lang.org/

最近、試しにAI用のLSPとか作ってたんですが、既存のプログラミング言語処理系にやや限界を感じています。既存言語の人間用のファイル参照と line:character の補完というインターフェースが、AIの操作単位と噛み合ってないじゃないか?という懸念です。

関数型まつりでも発表があった、関数型ドメインモデリングの Scott Wlaschin 氏いわく「副作用のない関数だけでプロジェクトを構成すれば、関数名はただの名前空間のルックアップテーブルに過ぎなくなり、その合成だけでドメインを表現できる」とし、関数を組み合わせるスタイルの Railway Oriented Programming を提唱していました。

https://fsharpforfunandprofit.com/rop/

関数型まつりで Unison 関連の発表があったわけではないんですが、関数型言語のうち自分がほしい特性をAIに相談したら、それって Unison じゃない? と提案されたことがきっかけです。

(二次会で話してたhttps://x.com/yukikurage_2019 にも、それって Unison では?と言われたのがあります)

Unison は既存のエディタやテキスト表現を前提としない、まったく新しい体系のプログラミング言語です。実際に Unison が使い物になるかと言うかどうかというのは一旦置いといて(マイナー言語なのは間違いないです)、新しい概念を掴むために Unison 言語を少し勉強することにしました。

このブログはその学習ログです。

最初に: 頭をリセットする

Unison 言語は一般的なプログラミング言語と全く違うワークフローを要求します。

まず純粋関数型+エフェクトシステムのある言語ということで、一般的なプログラマの発想から遠いのとは別に、さらに独自のコード管理とバージョン管理を持ちます。

前提が大きく違うので、頭の体操が必要です。

  • すべての関数は内部的に自己参照ハッシュで表現されます
    • nix のように環境上の参照を固定できます
  • 言語自体に Git 相当の機能が組み込まれています
    • コードの変更には Unison 言語専用の UCM (Unison Code Manager) を経由します
    • すべての関数は自己参照ハッシュで保存されて UCM が追跡します
  • ファイルという実体がないです
    • scratch.u は、編集するものを一時的に展開するバッファに過ぎません
  • Git に詳しい人向けの説明
    • AST から.git/objects/ 相当の blob object を保存/解決します
    • git のベアリポジトリ相当から始まり、プロジェクト上の名前空間は、単に関数ハッシュへの参照になります
    • その実体は~/.unison/v2/unison.sqlite3 です
  • 関数型言語に詳しい人向けの説明
    • エフェクトシステムが組み込まれた純粋関数型の言語です
    • 基本的に Haskell の構文やオフサイドルールに従います。 do 構文もあります
    • 純粋関数によって書かれたテストは、その環境では不変なはずなのでキャッシュされます
    • 関数型言語としての説明はこの記事ではしません

UCMの何が嬉しいのか

  • コードをその時記述して動いたときの状態で、その環境における一意な参照を解決する
    • 「あのとき動いていたコード」を探すのが簡単
    • 変更時には新しい実装同士で型の整合性をとってコミットする(必要がある)
  • 型チェックとバージョン管理が統合されているので、純粋関数である限り不整合が起きない
  • 差分検知によるインクリメンタルな型チェックとビルドがある

インストール

いちばん簡単なのは、 homebrew / linuxbrew でインストールすることです。

https://www.unison-lang.org/docs/install-instructions/

brew tap unisonweb/unisonbrew install unison-language

ついでに vscode 拡張をいれます

https://marketplace.visualstudio.com/items?itemName=unison-lang.unison

Unison 最初の一歩

vscode で適当なプロジェクトを開き、ターミナルでucm を起動します。
実際はファイルという実体がないので、どこで ucm を起動しても構いません。

ここではmyplg というプロジェクト(名前空間)を作成しました。

$ ucm  Now starting the Unison Codebase Manager(UCM)...scratch/main> project.create myplg1. Open scratch.u.2. Write some Unison code and save the file.3. In UCM,type`update` to save it to your new project.

ヒントにでているように、scratch.u というファイルを作成して、動作確認で次のように入力して保存(Ctrl-S) します。

> 1 + 1

すると、ucm 側で、次のように表示されるはずです

myplg/main>    1 | > 1 + 1          ⧩          2

> が REPL として評価された値が出力されるはずです。

Hello World

scratch.u に、さらに次のようなコードを保存してみましょう。

helloWorld: '{IO,Exception}()helloWorld_=printLine"Hello World"

'{IO, Expeption} の型表現は Ability といって、その関数の要求する副作用・エフェクトを示しています。printLine にホバーすると、次のような型シグネチャとその詳細が表示させるはずです。

printLine : Text ->{IO, Exception} ()printLine "myText" prints the given Text value to the console.

これで Ctrl-S を押すと、 ucm 側が scratch.u の変更に反応するはずです。

  Loading changes detected in ...scratch.u.  I found and typechecked these definitions in  ...scratch.u. If you do an `update`, here's how  your codebase would change:      ⍟ These new definitions are ok to `update`:          helloWorld : '{IO, Exception} ()

この時点で、 ucm 側でrun helloWrold で実行することができます。

myplg/main> run helloWorldHello World  ()

とはいえ、まだこのコードはプロジェクトに取り込まれていません。 ucm 側で、update して、ls してみます。

myplg/main> update  Done.myplg/main> ls  1. helloWorld ('{IO, Exception} ())  2. lib/       (8013 terms, 184 types)myplg/main> view helloWorld   helloWorld : '{IO, Exception} ()  helloWorld _ = printLine "Hello World"

(lib は標準ライブラリの名前空間です)

これで scratch.u のコードが、myplg/mainhelloWorld が取り込まれていることが確認できました。

テストを書く

https://www.unison-lang.org/docs/usage-topics/testing/

scratch.u でtest> でテストコードを追加できます。

square:Nat->Natsquarex=x*xtest>square.tests.ex1=check(square4==16)

面白いのは、Unisonは純粋関数型言語なので、純粋関数のテスト結果はキャッシュされています。

ucm から変更を取り込んで、このテストを実行してみます。

myplg/main> updatemyplg/main> test square.tests  Cached test results (`help testcache` to learn more)      1. square.tests.ex1   ◉ Passed    ✅ 1 test(s) passing    Tip: Use view 1 to view the source of a test.myplg/main> ls square.tests  1. ex1 ([Result])

新しいコードを追加する branch.create ~ merge

一度 add/update したら scratch.u から削除しても、その関数は残っています。

別ブランチに移動して、そこでコードを書いて、編集してみます

myplg/main> branches                           Branch   Remote branch  1.   main  myplg/main> branch.create distancemyplg/distance>

次のようなコードを書きます

-- https://www.unison-lang.org/docs/language-reference/record-type/typePoint={x:Float,y:Float}distance:Point->Point->Floatdistancep1p2=dx=abs(Point.xp1-Point.xp2)dy=abs(Point.yp1-Point.yp2)sqrt(powdx2.0+powdy2.0)test>distance.tests.ex1=check(distance(Point3.04.0)(Point0.00.0)==5.0)

これをコミットして merge

myplg/distance> updatemyplg/distance> switch /mainmyplg/main> merge /distance  I fast-forward merged myplg/distance into myplg/main.myplg/main> find tests  1. distance.tests.ex1 : [Result]  2. square.tests.ex1 : [Result]myplg/main> test distance.tests  Cached test results (`help testcache` to learn more)      1. distance.tests.ex1   ◉ Passed    ✅ 1 test(s) passing    Tip: Use view 1 to view the source of a test.

square を再編集

scratch.u をまっさらにして、次のコマンドを打ちます。

myplg/main> edit square  ☝️    I added 1 definitions to the top of  /home/mizchi/mizchi/zenn/slides/aoai/unison-plg/scratch.u    You can edit them there, then run `update` to replace the  definitions currently in this namespace.

edit square によって、scratch.u は次のようにコードが書き込まれます

square:Nat->Natsquarex=useNat*x*x

元のコードとは違う点に注意してください。
use Nat * は標準ライブラリを暗に省略していただけで、実際には関数単位の依存として内部的に展開されていました。

ucm ではテキストコードを直接保存しているのではなく、関数単位で他の関数への依存があり、それを edit では展開しているわけです。

Unison の面白さ

人間が書くコードは表象に過ぎず、Git で AST の自己参照ハッシュがある、というのは AI 向けに向いてる抽象だと自分は感じました。テキスト表現なんて人間用のインターフェースで、AI は直接構造化データを参照する方がいいですからね。

他にも、紹介してない機能がたくさんあります。

  • Unison Share によるコード共有(GitHub 相当)
  • Unison Cloud による分散実行
  • Ability によるエフェクトシステム

ただし、Unison には明確な欠点があります。それは Git/GitHub とはまったく異なるワークフローになってしまったことで GitHub にホストされず、それによって AI の学習となっておらず 「なんか Haskell 風の言語」以上の精度がでません。

とはいえ、コンパイラが賢いのと、要はHaskellなので、意外となんとかなってる気がします。最初に既存の言語との差分の、簡単なチートシートを作っておきましょう。

おまけ Unison UCM MCP を作ってみた

面白半分で、 Haskell で UCM を操作する MCP を書かせてみました。

https://github.com/mizchi/unison-mcp

Claude Code に UCM を操作する MCP を提供して、それによってコードを書かせる実験です。

ダイクストラによる経路探索ぐらいだったら、これで実装ができましたが。...まだそれ以上の評価はしていません。一応動いたという感じ。

Haskell で JSONRPCの MCP を喋るサンプルぐらいにはなってると思います。

考察

  • テキスト表現は人間用インターフェースに過ぎない
    • バイナリで格納して、閲覧時にユーザーの好きなフォーマットで展開すればいいい
    • コード検索は、grep ではなく ASTと型システムと合わせてクエリできればいい
    • 言語自体がバージョニング管理できれば、AIの書き散らすコードを管理できる
    • しかし、そうすると GitHub のインターフェースに乗らなくなる
  • 既存の言語に似ていて、コンパイラの警告がある程度賢ければ、学習量の不利は踏み倒せる
  • AI用の言語に必要なのは、たぶん専用のシェル統合環境なんじゃないか
    • MCPとエフェクトシステムを統合すれば、明示的な副作用の制御ができそう
    • ランタイム系は Wasm で作って、 WASI Sandboxで権限制御すればいいじゃないか?
  • Unison はまだ人間向きなんじゃないか?
    • ucm のシェル相当で長大なワンライナー(人間用ではないので型含めた大量の記述を要求していい)でその時書いたコードをハッシュで保存して、コード片を組み合わせて、最後に可読性のあるコード表現に落とせばいい

というのを余裕があったり作りたく、じつはちょっとだけ試作したんですが、それは後で書きます

mizchi

Programmer in the Loop

バッジを贈って著者を応援しよう

バッジを受け取った著者にはZennから現金やAmazonギフトカードが還元されます。


[8]ページ先頭

©2009-2025 Movatter.jp