14. Pythonista 向けの読み替えとコード例
14.1. この章で学ぶこと
Mojo の位置づけの再確認
Pythonista がつまずきやすい点
ownership・compile-time・trait の読み替え
この章は、Python 経験者向けの補足です。 コードを見ながら、Mojo が Python とどこまで似ていて、どこから違うのか を整理します。
まずは次の3つを押さえると読みやすいです。
最初の見た目はかなり Python に近い
でも中身はコンパイル言語寄り
ownership と compile-time がわかれ目になる
14.2. Python との違いの地図
この章では、Python 経験者がつまずきやすいポイントを、コード例を通して確認します。
Python の感覚 |
Mojo での読み替え |
|---|---|
名前がオブジェクトを指す |
誰がその値を所有しているか |
関数は何でも受け取れる |
|
例外は実行時に伝わる |
|
型ヒントは補助的 |
|
duck typing |
trait で必要な能力を宣言する |
以降は、この読み替え表の各行をコードで確認していきます。
14.3. ownership と value semantics
Mojo を学ぶときの大きなわかれ目がここです。
14.3.1. 基本
値には owner がある
owner の寿命が終わると値は破棄される
参照はそのあいだだけ安全に使える
GC なしで安全性を目指す
ref value_ref = list[0]
Python では、変数は「オブジェクトへの名前」と考えることが多いです。 一方で Mojo では、誰がその値を持っているか をより強く意識します。
つまり、
「名前がオブジェクトを指す」というより、 「誰がその値を持っているか」 を意識する世界
と考えると入りやすいです。
出典: Ownership
補足: 最初は少し重い考え方ですが、安全性と性能を両立するための土台です。
14.4. read / mut: 関数シグネチャが意味を持つ
def add(mut x: Int, read y: Int):
x += y
def main():
var a = 1
var b = 2
add(a, b)
print(a) # 3
この例では、引数の役割がシグネチャに出ています。
readは読み取り用mutは書き換え用
ここで大事なのは、その関数が何をするかを、引数宣言の時点でかなり読める ことです。
Python では、破壊的かどうかを名前や説明に頼ることが多いです。 Mojo では、それをシグネチャに出しやすいです。
出典: Ownership
補足: 「Python よりシグネチャが多くを語る言語」と理解するとよいでしょう。
14.5. mut の実務的な意味: コピーしない更新
def mutate(mut l: List[Int]) raises:
l.append(5)
def main() raises:
var values = [1, 2, 3, 4]
mutate(values)
print(values) # [1, 2, 3, 4, 5]
この例では、mut によって 破壊的に更新する ことが明示されています。
ポイントは次の通りです。
更新することがわかりやすい
不要なコピーを避けやすい
意図が API に出る
これは地味ですが、実務ではかなり重要です。
出典: Ownership
補足: 性能が大事なコードほど、コピー回数や aliasing の制御が効いてきます。
14.6. var と ^: ownership transfer
def take_text(var text: String):
text += "!"
print(text)
def main():
var message = "Hello"
take_text(message^)
# print(message) # ここでは使えない
ここでは、所有権の移動がはっきり出ています。
varは受け取る側が ownership を持つ^は呼び出し側から ownership を渡す渡したあとの元の変数は使えない
これは Python にはあまりない感覚です。
ここで大事なのは、この値はもう自分では使わない、と明示できる ことです。 そのぶん、コンパイラは危険な使い方を防ぎやすくなります。
出典: Ownership
補足: システムプログラミング言語に求められる安全性の中核にある考え方です。
14.7. compile-time parameters
def repeat[count: Int](msg: String):
comptime for i in range(count):
print(msg)
def main():
repeat[3]("Hello")
出力:
Hello
Hello
Hello
この例で大事なのは、[] が実行時引数ではないことです。
[]は compile-time input()は runtime inputcomptime forでコンパイル時に展開できる
つまり、見た目は関数呼び出しでも、コード生成に直接効く情報を渡している ということです。
出典: Parameters
補足: Python の型ヒントよりも、性能戦略に近い仕組みです。
14.8. type generic + value generic
def repeat[
MsgType: Writable,
count: Int
](msg: MsgType):
comptime for _ in range(count):
print(msg)
def main() raises:
repeat[2](42)
repeat[3]("mojo")
この例では、2種類の parameter が出ています。
MsgTypeは型 parametercountは値 parameter
どちらも compile-time specialization に効きます。
ここで大事なのは、型だけでなく値までコンパイル時に持ち込める ことです。 これが、固定長配列や SIMD、GPU 寄りの処理と相性がよい理由のひとつです。
出典: Parameters / Generics
補足: ここでも、Mojo が typed Python ではなく compile-time 指向の言語だと見えてきます。
14.9. traits / generics
def all_equal[
T: Equatable & Copyable
](ref lhs: List[T], ref rhs: List[T]) -> Bool:
if len(lhs) != len(rhs):
return False
for left, right in zip(lhs, rhs):
if left != right:
return False
return True
print(all_equal([1, 2, 3], [1, 2, 3]))
print(all_equal(["hello", "world"], ["hello", "world"]))
この例では、trait 制約が その型に必要な能力 を表しています。
Equatableは比較できることCopyableはコピーできること
つまり、T は何でもよいわけではなく、必要な能力を持っていなければならない ということです。
ここで大事なのは、generic が型安全のためだけでなく、 具体的なコード生成にも関わる ことです。
出典: Generics
補足: Python の duck typing よりも、必要な能力を先に言語側で宣言する感覚に近いです。
14.10. この章を一文で言うと
Mojo は最初は Python に近く見えるが、本質は ownership・compile-time・trait を土台にした別の言語です。
14.11. まとめ:読み替えのポイント
仕組み |
対応する Python の感覚 |
Mojo での違い |
|---|---|---|
|
引数の in/out 区別 |
シグネチャで明示 |
|
代入・コピー |
所有権の移動 |
|
ジェネリクス・型ヒント |
コンパイル時入力・特化 |
|
Protocol / ABC |
コード生成にも効く |
|
例外のある関数 |
シグネチャで型として明示 |
「似ているのは文法であって、値の考え方やコンパイル時の仕組みはかなり違う」 ― この一点を押さえると、以降のコードが読みやすくなります。