210
105

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2025-05-16

単位ミスが招く重大事故

マーズ・クライメイト・オービター」をご存知でしょうか。火星の気象調査をミッションとして、NASAが1998年に打ち上げた探査機です。1億2500万ドルを費やして作られたこの探査機は、本務に就く前に火星周回軌道内で炎上、そのまま消息を絶ちました。原因はヤード・ポンド法単位とメートル法単位の取り違え。

かの有名な「もう助からないゾ♡」の事故もやはり単位変換ミスが発端でした。人類史上において、不適切な単位の取り扱いが悲惨な事故に繋がる例は枚挙にいとまがありません。

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

......そんな感じの問題意識から生まれたプログラミング言語、それが「Numbat」です。

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

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

基礎文法

まず特徴的なのが代入操作です。数値型の場合、let [変数名]: [型名] = [数値] [単位]の形で変数に値を代入します。型名はLength, Time, Mass等の物理次元の名前になります(無次元量はScalar)。

let w1: Mass = 0.5 kg
# let [変数名]: [型名(物理次元)] = [数値] [単位]

ただし、型推論が働くので、型名は省略しても構いません。

let w2 = 2 lb
# w2に2ポンドを代入

変数同士は、単位変換を行うことなく演算が可能です。printで出力する際、-> [単位]とすることで、出力単位を指定できます。

print(w1 + w2 -> oz)
# -> 49.637 oz | オンスとしてprint

つまり、値を与える時値を取り出す時にさえ単位を指定すれば、その間の変換について一切考えることなく演算結果を得ることが出来ます。

使用例

オンラインPlaygroundで、インタラクティブにNumbatの挙動を試すことが出来ます。
以下に公式のサンプルを示します。

numbat-interactive.png

次元型による柔軟さと安全性

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

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

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

print(cos(ω))
# -> 0.5

print(cos(δ))
# -> 0.5

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

逆に、次元が合っていなければ、きちんとエラーが返ってきます。例えば、下記のように余弦関数cos()に対してLengthの次元を持つαを引数として与えると、

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

異なる次元を持つ数値同士の加算・減算も当然認められていません。

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とか、JuliaのUnitful.jlあたりが有名でしょうか。それらと比べて Numbat の際立つ点は、言語標準の静的な型システムで物理次元と単位を扱うことにより、言語全体で物理次元の整合性を保証できる点です。言語自体に組み込まれている分、構文も簡潔です。

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

その他の文法的特徴

演算子記号

プログラミング言語としては珍しく、×(乗算記号)や²(累乗記号)といった、人間にとって自然な演算子記号を使うことが出来ます(一覧)。プログラマは今更何とも思わないかもしれませんが、書き上がったコードを見るとやはり見やすいです。もちろん、***といった一般的なプログラミング言語表現も使えます。

let Eₚ: Energy = 80 kg × 9.8 m/s² × 5 m

関数定義

F#Rustを混ぜたようなシンタックスで関数を定義します。

fn max_distance(v: Velocity, θ: Angle) -> Length = v² · sin(2 θ) / g0

ジェネリクス

ジェネリクスを使えば、様々な型に対して適用可能な関数を定義できます。さらに、総称型引数(下の例ではD)同士の演算によって、引数や返り値の関係性を縛ることも出来ます。

fn sqrt<D>(x: D^2) -> D
# 平方根関数。引数は返り値の2乗の次元を持つ

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

定数

様々な物理・数学定数が標準で使えます。

print(π)
# -> 3.14159
print(pi)
# -> 3.14159
print(avogadro_constant)
# -> 6.02214e+23 mol⁻¹

数字以外の型

汎用プログラミング言語なので、StringBoolといった基礎的な型はもちろん一通り扱えます。

let msg: String = "Hello, World!"
print(msg)
# -> Hello, World!

メソッドチェーン

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

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

if式

if式が使えます。

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

カスタム単位

自前で次元や単位を定義できます。

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

  1. なおOpenCVの三角関数はdegree

210
105
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
210
105

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?