物理次元型を持つ静的型付け言語「Numbat」の紹介

# 単位ミスが招く重大事故

```plantuml
@startuml
<style>
root {
  Padding 0
  Margin 0
}
footer {
  FontColor gray
  FontSize 12
}
</style>
footer Illustration by NASA (www.nasa.gov)
title <img:https://natgeo.nikkeibp.co.jp/nng/images/news/bigphotos/images/the-mars-curse-mars-climate-orbiter_44154_big.jpg>
@enduml
```

「[マーズ・クライメイト・オービター](https://ja.wikipedia.org/wiki/%E3%83%9E%E3%83%BC%E3%82%BA%E3%83%BB%E3%82%AF%E3%83%A9%E3%82%A4%E3%83%A1%E3%82%A4%E3%83%88%E3%83%BB%E3%82%AA%E3%83%BC%E3%83%93%E3%82%BF%E3%83%BC)」をご存知でしょうか。火星の気象調査をミッションとして、NASAが1998年に打ち上げた探査機です。1億2500万ドルを費やして作られたこの探査機は、本務に就く前に火星周回軌道内で炎上、そのまま消息を絶ちました。原因はヤード・ポンド法単位とメートル法単位の取り違え。

かの有名な[「もう助からないゾ♡」](https://ja.wikipedia.org/wiki/%E3%82%AE%E3%83%A0%E3%83%AA%E3%83%BC%E3%83%BB%E3%82%B0%E3%83%A9%E3%82%A4%E3%83%80%E3%83%BC)の事故もやはり単位変換ミスが発端でした。人類史上において、不適切な単位の取り扱いが悲惨な事故に繋がる例は枚挙にいとまがありません。

プログラミングにおいても同じです。接頭辞の変換を忘れたり、[numpyの三角関数](https://numpy.org/doc/2.1/reference/generated/numpy.sin.html)にradianではなくdegreeで角度を与えて時間を溶かした人は私だけではないでしょう[^2]。もっと安全・便利に数値を扱えるように人類をサポートする仕組みが必要です。


......そんな感じの問題意識から生まれたプログラミング言語、それが「[**Numbat**](https://github.com/sharkdp/numbat)」です。

# 静的な物理次元型付け言語「Numbat」

https://github.com/sharkdp/numbat

**Numbat**は、科学技術計算を安全かつ効率的に行うことを目的する静的型付け言語です。この言語は、**物理次元（長さ、時間、質量など）をファーストクラスの型として扱う**ことで、単位の不適切な取扱いを型エラーとして検出できます。**単位変換はすべて自動**で行われ、書き手が自分で行う必要はありません。

# 基礎文法
まず特徴的なのが代入操作です。数値型の場合、`let [変数名]: [型名] = [数値] [単位]`の形で変数に値を代入します。型名は`Length`, `Time`, `Mass`等の物理次元の名前になります（無次元量は`Scalar`）。
``` rust
let w1: Mass = 0.5 kg
# let [変数名]: [型名（物理次元）] = [数値] [単位]
```
ただし、型推論が働くので、型名は省略しても構いません。
```rust
let w2 = 2 lb
# w2に2ポンドを代入
```
変数同士は、**単位変換を行うことなく演算が可能**です。`print`で出力する際、`-> [単位]`とすることで、**出力単位を指定**できます。
```rust
print(w1 + w2 -> oz)
# -> 49.637 oz | オンスとしてprint
```
つまり、**値を与える時**と**値を取り出す時**にさえ単位を指定すれば、**その間の変換について一切考えることなく**演算結果を得ることが出来ます。

# 使用例
オンライン[Playground](https://numbat.dev)で、インタラクティブにNumbatの挙動を試すことが出来ます。
以下に公式のサンプルを示します。

![numbat-interactive.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/160540/24b15651-15fe-439c-b1ee-7b6198fac8ee.png)

# 次元型による柔軟さと安全性
Numbatの関数は引数や返値に次元型が指定されているため、次元が合っていれば、どのような単位で値を入力しても正しい答えを返すことができます。例えば、下記のように余弦関数`cos()`に対して**radianで指定した角度（π/3）を入れても、degreeで指定した角度（60°）を入れても、正しい答え**（0.5）が得られます。どちらも`Angle`の次元を持つためです。

```rust
let ω: Angle = π/3 rad
# 角度をラジアンで指定

let δ: Angle = 60°
# 角度を度数で指定

print(cos(ω))
# -> 0.5

print(cos(δ))
# -> 0.5

print(cos(ω + δ))
# -> -0.5
```

逆に、次元が合っていなければ、きちんとエラーが返ってきます。例えば、下記のように余弦関数`cos()`に対して`Length`の次元を持つ`α`を引数として与えると、
``` rust
let α = 3 nm
print(cos(α))
```
エラーが発生し、「次元が合っていませんよ」と教えてくれます。そのまま演算を実行して無意味な答えを返すことはありません。
```
error: while type checking
  ┌─ Module 'math::trigonometry', File /usr/share/numbat/modules/math/trigonometry.nbt:9:8
  │
9 │ fn cos(x: Scalar) -> Scalar
  │        ^ Angle or Scalar or SolidAngle
  │
  ┌─ File calc.nbt:2:11
  │
2 │ print(cos(α))
  │       --- ^ Length
  │       │    
  │       incompatible dimensions in argument 1 of function call to 'cos'
  │
  = parameter type: Scalar    [= Angle, Scalar, SolidAngle]
     argument type: Length
    
    Suggested fix: divide the function argument by a `Length` factor

Interpreter stopped
```
異なる次元を持つ数値同士の加算・減算も当然認められていません。
```rust
let memory_size = 2 MB
let cache_size = 1_048_576 bit

print(memory_size + cache_size -> kB)
# -> 2131.07 kB | 単位は異なるが次元は同じ（DigitalInformation） なので加算OK

let heart_rate = 90 bpm
print(memory_size + heart_rate)
# エラー: DigitalInformation + Frame/Time は次元が異なるので出来ない
```
このように、数値に対して常に次元型を用いたチェックを行うことで、**柔軟かつ安全**な計算プログラムを書くことが可能になります。

# 「それって○○と同じでは？」

物理単位の換算を行ってくれるようなライブラリ自体は多数存在します。[PythonのPint](https://pint.readthedocs.io)とか、[JuliaのUnitful.jl](https://painterqubits.github.io/Unitful.jl)あたりが有名でしょうか。それらと比べて Numbat の際立つ点は、言語標準の静的な型システムで物理次元と単位を扱うことにより、**言語全体で物理次元の整合性を保証**できる点です。言語自体に組み込まれている分、**構文も簡潔**です。

言語標準の型システムで物理単位を扱う先例としては、F#の[Units of Measure](https://learn.microsoft.com/ja-jp/dotnet/fsharp/language-reference/units-of-measure)が知られています。開発者も「F# は単位に基づいた表現力豊かな型システムを持つ唯一の言語」と[述べています](https://numbat.dev/doc/comparison.html)。Numbat が異なるのは、**「単位」ではなく「次元」そのものを型とする**点です。たとえば「km/h」ではなく「長さ/時間」として型を定義することで、より汎用的かつ柔軟な設計になっています。また、SI単位系はもちろん、通貨・画素・人数といった[多様な次元や単位]((https://numbat.dev/doc/list-units.html))が標準で準備されているのも特徴です。

# その他の文法的特徴

### 演算子記号
プログラミング言語としては珍しく、`×`（乗算記号）や`²`（累乗記号）といった、**人間にとって自然な演算子記号**を使うことが出来ます([一覧](https://numbat.dev/doc/operations.html))。プログラマは今更何とも思わないかもしれませんが、書き上がったコードを見るとやはり見やすいです。もちろん、`*`や`**`といった一般的なプログラミング言語表現も使えます。
```rust
let Eₚ: Energy = 80 kg × 9.8 m/s² × 5 m
```

### 関数定義
`F#`と`Rust`を混ぜたようなシンタックスで関数を定義します。
```rust
fn max_distance(v: Velocity, θ: Angle) -> Length = v² · sin(2 θ) / g0
```

### ジェネリクス
ジェネリクスを使えば、様々な型に対して適用可能な関数を定義できます。さらに、総称型引数（下の例では`D`）同士の演算によって、引数や返り値の関係性を縛ることも出来ます。
``` rust
fn sqrt<D>(x: D^2) -> D
# 平方根関数。引数は返り値の2乗の次元を持つ

fn sqrt<D>(x: D) -> D^(1/2)
# 同上
```

### 定数
様々な[物理・数学定数](https://numbat.dev/doc/list-constants.html)が標準で使えます。
```rust
print(π)
# -> 3.14159
print(pi)
# -> 3.14159
print(avogadro_constant)
# -> 6.02214e+23 mol⁻¹
```

### 数字以外の型
汎用プログラミング言語なので、`String`や`Bool`といった基礎的な型はもちろん一通り扱えます。
```rust
let msg: String = "Hello, World!"
print(msg)
# -> Hello, World!
```

### メソッドチェーン
パイプライン演算子（`|>`）を用いたイテレータメソッドチェーンが使えます。関数型言語というか`F#`の影響を感じます。

```python
[1 m, 1e4 mm, NaN, -inf] 
                |> filter(is_finite) 
                |> sum()
                
# -> 11 m    [Length]
# リストの中から有限の値を持つ（is_finite）要素のみfilterし、それらの合計をsumで取得
```

### if式
if式が使えます。
```rust
fn step(x) = if x < 0 then 0 else 1
# xが0以上の時に1、そうでない時に0を与えるステップ関数
```

### カスタム単位
自前で次元や単位を定義できます。
```rust
unit TokyoDome: Volume = 1_240_000 m³
# Volume次元を持つ新単位「東京ドーム」を定義
```

### エラーメッセージ
エラーメッセージが親切なのもNumbatの特徴です。処理系の実装言語である`Rust`の影響を強く感じます。
```
>>> print(planck_leng)
error: while type checking
  ┌─ <input:35>:1:7
  │
1 │ print(planck_leng)
  │       ^^^^^^^^^^^ unknown identifier
  │
  = Did you mean 'planck_length'?>>> print(planck_leng)
```

# 今後の展望
現状、Numbatは発展途上（初版が2023年）かつ超マイナー言語ではありますが、やはり「物理次元型を持つ静的型付け言語」という尖ったコンセプトは魅力的です。他言語との連携等によって今後発展することを期待しています。

# 追記
2025/05/20:
この記事を読んだ方が、言語仕様に関する100倍詳細な解説記事を書いてくれました。感謝。
https://qiita.com/alRex/items/de03e1348ce8c9238fa5

# Reference
- リポジトリ: https://github.com/sharkdp/numbat
- 公式ドキュメント: https://numbat.dev/doc

[^2]: なおOpenCVの三角関数はdegree