Muga という新しいプログラミング言語 作っています。まだ初期の実験段階ですが、仕様と実装を GitHub に公開しました。
https://github.com/lef237/muga
Rust製です。
cargoが使えれば、READMEのQuickstartを読んですぐにプログラムを実行できると思います。
動画
百聞は一見に如かず!ということで動画を用意しました。実際に動いているところが分かります。
動画内では正常系と異常系の両方を試しています。テキストで貼り付けるとこんな感じです。
$ cargo run -- samples/inferred_types.muga Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.06s Running `target/debug/muga samples/inferred_types.muga` 10 10
正常系の場合はちゃんと出力されています。
うまくいかない場合はtype mismatchのエラーもちゃんと出力されます。
$ cargo run -- samples/inferred_types.muga Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.06s Running `target/debug/muga samples/inferred_types.muga` 10:12: T002 type mismatch: expected Int, found String
設計判断
Muga の設計にあたっていくつか決めたことがあります。今のところは次の2つ。
基本的に immutable
すべての束縛はデフォルトで immutable。変更したいときだけ mut を明示的に書きます。
x = 1 # immutable mut total = 0 # mutable total = total + x
letにするか迷ったのですが、letは言語ごとに意味的なばらつきがあるので、mutableを推測しやすいmutにしました。
shadowing を禁止
同じスコープで同じ名前を再束縛することはできません。これはElmの影響を受けています。
すごく悩んだのですが、決め手となったのはRedditのこのコメントかも。
引用は長いのでdetailsに囲みます。
A lot of people are talking about why they think shadowing is fine, but in order to attempt to answer the question:
People don't like shadowing because, in the enterprise context, you often deal with code that is 1) not written by you and 2) a little messier (often longer) than you'd personally like. These two things combine to make shadowing a source of confusion and mental overhead.
Let's take the example you used from another comment:
for rr in sol.sln.rr.iter() {
for rr in rr.iter() {
if !rr.is_finite() {
println!("ERROR: Solution diverged at timestep: {}:{}", iter, m);
break 'outer;
}
}
}
That's all well and good --- it's fairly easy to see that the rr in the if statement is not the same rr from the initial for loop. However, if this function evolves from what it is now to something like:
for rr in sol.sln.rr.iter() {
rr.doStuff();
some_other_function();
maybe_this_one_takes_rr();
// ... 10 more lines
for rr in rr.iter() {
some_other_calls_unrelated_to_rr();
// ... 10 more lines, maybe a loop or conditional
if !rr.is_finite() { // Now --- what is rr and where did it come from? What's its type?
println!("ERROR: Solution diverged at timestep: {}:{}", iter, m);
break 'outer;
}
}
}
Now, by the time you get to the if statement inside the nested loop, in order to properly understand which rr you're talking about, you have to carefully read (and keep in your working memory) the entire function context from the start; you can't just skim from the top for the declaration of the variable. Heck, if you do skim from the top, you might end up thinking rr is a totally different type than it actually is! This is particularly awful for immutable values, since you might see an immutable declaration at the start of a function and assume you know to what data that variable name refers, but then it changes over the course of the function (bc now it's shadowed).
You might counter --- "but this is because of bad coding practice; if you kept your functions small and only carefully used shadowing, you wouldn't have this issue!" You're right. But, the same is true about memory errors in unsafe languages, or type errors in languages like Python or Javascript.
Even though careful programmer discipline can prevent many problems, in the long-term, multiple-maintainer context, relying on careful programmer discipline just isn't sound. Instead, people like language features to forcibly reduce mental overhead and the required program context you have to keep "in [human] memory."
コード例
https://github.com/lef237/muga/tree/main/samples
このリンクに色々置いていますが、一番分かりやすいものを貼ってみます。*1
record Counter {
value: Int
}
fn start(n: Int): Counter {
Counter {
value: n
}
}
fn inc(counter: Counter): Counter {
counter.with(value: counter.value + 1)
}
fn double(x: Int): Int {
x * 2
}
fn main(): Int {
# This chain is equivalent to:
# double(inc(inc(start(10))).value)
10.start().inc().inc().value.double()
}
関数をネストすることもできるし、ドットを使ってチェーンもできます。好きな方を自由に選べるようにしています。
recordはC#から影響を受けました。書き換え不可を基本にしたかったので、StructではなくてRecordにしています。
レコード - C# reference | Microsoft Learn
ファイルは.mugaという拡張子にしました。
名前の由来
"muga" は日本語の「無我」から取りました。無我夢中でプログラミングに取り組めるような言語を目指しています。
キャッチフレーズは A quiet programming language.
簡単な現状紹介
だいたいこんな感じです。
- Rust 実装 (parse / name resolution / type check / HIR / bytecode VM) が動作
- 18 件のテストをpass
- ライセンスは MIT
- 未実装: receiver-style 関数の解決規則、関数型の型注釈
A -> B、など
仕様ドキュメントの方が実装より少し先行していて、spec/ ディレクトリに章ごとの草案があります。
なんでつくりはじめたの?
好きな言語をいいどこ取りした最強のプログラミング言語を作りたくてェ…
これから
まだシンプルな機能しか実装されていませんが……動きます!
(あとでmuga foo.mugaで実行できるようにする予定です)
もしかしたらフィードバックしてくれる方がいらっしゃるかもしれないので早い段階で公開してみました。
設計も実装も徐々に育てていこうと思っています。
気になった点、疑問、設計へのツッコミ、Issue でも pull-req でもなんでも歓迎します。
リポジトリはこちら
https://github.com/lef237/muga
*1:シンタックスハイライトはいったんRustのものを使っています

















