関数型まつり以降、Unison 言語が気になっています。
最近、試しに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 言語は一般的なプログラミング言語と全く違うワークフローを要求します。
まず純粋関数型+エフェクトシステムのある言語ということで、一般的なプログラマの発想から遠いのとは別に、さらに独自のコード管理とバージョン管理を持ちます。
前提が大きく違うので、頭の体操が必要です。
scratch.u
は、編集するものを一時的に展開するバッファに過ぎません.git/objects/
相当の blob object を保存/解決します~/.unison/v2/unison.sqlite3
ですいちばん簡単なのは、 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
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 として評価された値が出力されるはずです。
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/main
にhelloWorld
が取り込まれていることが確認できました。
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])
一度 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.
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 では展開しているわけです。
人間が書くコードは表象に過ぎず、Git で AST の自己参照ハッシュがある、というのは AI 向けに向いてる抽象だと自分は感じました。テキスト表現なんて人間用のインターフェースで、AI は直接構造化データを参照する方がいいですからね。
他にも、紹介してない機能がたくさんあります。
ただし、Unison には明確な欠点があります。それは Git/GitHub とはまったく異なるワークフローになってしまったことで GitHub にホストされず、それによって AI の学習となっておらず 「なんか Haskell 風の言語」以上の精度がでません。
とはいえ、コンパイラが賢いのと、要はHaskellなので、意外となんとかなってる気がします。最初に既存の言語との差分の、簡単なチートシートを作っておきましょう。
面白半分で、 Haskell で UCM を操作する MCP を書かせてみました。
https://github.com/mizchi/unison-mcp
Claude Code に UCM を操作する MCP を提供して、それによってコードを書かせる実験です。
ダイクストラによる経路探索ぐらいだったら、これで実装ができましたが。...まだそれ以上の評価はしていません。一応動いたという感じ。
Haskell で JSONRPCの MCP を喋るサンプルぐらいにはなってると思います。
というのを余裕があったり作りたく、じつはちょっとだけ試作したんですが、それは後で書きます
バッジを受け取った著者にはZennから現金やAmazonギフトカードが還元されます。