Mojo触れるようになった
はじめに
MojoはModulerが開発したプログラミング言語. Pythonのパッケージを読み込むことができ、Pythonよりパフォーマンスがいいらしい。
ドキュメント
ひとまず、申請していたwaitlistが許可されPlayGroundが触れるようになったので触ったことを整理していきたい。
githubにもリポジトリはあるけど、コードは何もない
とりあえずHelloMojo.ipynbをやっていく
変数の定義
Pythonと違い、imutableな変数宣言できる。
-
let
: イミュータブル -
var
: ミュータブル
メリットとしては以下の2点
- システムプログラマが変更不可であることを担保するため
- 変数名のタイポによる代入ミスを防ぐ
2は雑にnotebookとか使っているとめっちゃあるので確かにわかる。
試したところ、宣言がなくても一応動いた。
def declare():
let a = 1
var b = 2
c = 3
print(a, b, c)
# これはletに代入しているのでエラー
# a = 3
declare() # -> 1 2 3
変数宣言と同時に型の指定や後から変数を初期化することもできる。
def declare_with_type():
let d: Int = 10
let e: F32
e = 0.5
# 初期値を宣言した後なのでエラー
# e = 0.1
# 文字列型 -> String.Stringがある
# 組み込みで以下の二つもあるけど違いが理解できず
# StringLiteral: https://docs.modular.com/mojo/MojoBuiltin/StringLiteral.html
# StringRef: https://docs.modular.com/mojo/MojoBuiltin/StringRef.html
let f: String = "Hello"
let g: String = "World"
print(d, e, f, g)
declare_with_type() # -> 10 0.500000 Hello World
String型にバグっぽいのがあるらしい。
let
で宣言しても代入することができる。
from String import String
let a: String = "This is original text." # -> This is original text.
print(a)
a = "asdf" # 変更できないはず
print(a) # -> asdf
struct (構造体)
クラスとは違い、明示的にvar
かlet
でインスタンスの変数を宣言する必要がある。
struct MyStruct:
var a: Int
var b: F32
var c: Int
# let c: F32 = 2 # 今の所、letは使用できない
fn __init__(self&, a: Int, b: F32, c: Int):
self.a = a
self.b = b
self.c = c
fn print_vars(self&):
print("a: ", self.a)
print("b: ", self.b)
print("c: ", self.c)
var my_struct = MyStruct(1, 2, 3)
my_struct.print_vars()
# a: 1
# b: 2.000000
# c: 3
# 変数の変更
print("Change b 2 -> -2")
my_struct.b = -2
my_struct.print_vars()
# Change b 2 -> -2
# a: 1
# b: -2.000000
# c: 3
書いてある通り、未定義の変数を宣言しようとするとエラーのなる。
var my_struct.d = 2
letで宣言
struct TestStruct:
let c: F32
fn __init__(self&, c: F32):
self.c = c
The biggest difference compared to a class is that all instance properties in a struct must be explicitly declared with a var or let declaration.
var or letで宣言すると書いているけど、let
はまだ利用できないみたい?やり方が違うのかもしれない
self&
について
The problem here is that iadd needs to mutate the internal state of the integer. The solution in Mojo is to declare that the argument is passed “by reference” by using the & marker on the argument name (self in this case)
ref.: https://docs.modular.com/mojo/programming-manual.html#by-reference-arguments
selfの内部情報を変更する必要がある場合に、&
を付与することで参照
を渡していることを明示的にする
型チェック
Pythonと違い、結構型チェックがしっかりしてる。
動的型付けの便利さはあれど、システムかするときによくバグの元になるのでcompile時にやってくれるのは良いなと思う
# OK
def return_int() -> Int:
return 1
# NG
# error: Expression [8]:6:12: 'Bool' value cannot be converted to 'Int' in return value
def return_int() -> Int:
return True
# OK
# 書かなければ大丈夫
# 後述
def return_int():
return True
# NG
# 型に実装されていない演算
# error: Expression [25]:6:14: 'String' does not implement the '__lt__' method
def is_negative(a: String) -> Bool:
return a < 0
関数の定義をdef
ではなくfn
ですると明示的に変数や戻り値の型を記載する必要がある。
# NG
# error: Expression [26]:5:14: 'fn' parameter type must be specified
fn sample_fn(a):
return a
# 記載しない場合、暗黙的に戻り値なしとなるみたい
# error: Expression [27]:6:12: 'Int' value cannot be converted to 'None' in return value
fn sample_fn(a: Int):
return a
オーバーロード
Pythonだとtyping.overload
を使う.
ただ、型ヒントのためだけの実装で、処理をかき分けることはできない。
Mojoだと引数の異なる関数を複数定義すれば良いみたい。
struct MyStruct2:
var name: String
def __init__(self&, name: String):
self.name = name
def hello(self&):
print("Hello", self.name)
def hello(self&, greeting: String):
print(greeting, self.name)
var my_struct2 = MyStruct2("Bob")
my_struct2.hello() # -> Hello Bob
my_struct2.hello("Good morning") # -> Good morning Bob
Python書いてて、結構コードがややこしくなるなと思うこともあるので便利な気がする。
pandasのDataFrameの初期化関数のようにいろんな引数を受け取って処理したいときはPythonよりスッキリ描けるかもしれない
fnについて
関数定義の方法としてdef
とfn
の二つある。
-
def
: Pythonのような柔軟な関数 -
fn
: 型などに厳密な関数
fn
ではいくつか制限がある.
とりあえず、次の二つは便利な気がする。
- 引数と型ヒントが必須
- 引数が不変であること
# OK
def add_def(a, b):
return a + b
print(add_def(1, 2)) # -> 3
# OK
fn add_fn(a: Int, b: Int) -> Int:
return a + b
print(add_fn(1, 2)) # -> 3
# NG
# error: Expression [17]:9:11: 'fn' parameter type must be specified
fn add_fn(a, b):
return a + b
# NG
# error: Expression [15]:10:5: expression must be mutable in assignment
fn add_fn_ng(a: Int, b: Int) -> Int:
a = a + 1
return a + b
__copyinit__
, __moveinit__
インスタンスを別の変数に渡す時の挙動を制御できる。
例えばnumpyのarrayは参照を渡すので以下のようなことになる
import numpy as np
a = np.array([1, 2, 3])
b = a
print(a, b) # -> [1 2 3] [1 2 3]
b[2] = -3
print(a, b) # -> [ 1 2 -3] [ 1 2 -3]
mojoではそもそも__copyinit__
や__moveinit__
を実装しないと代入できない
from String import String
struct CopyMoveTest:
var name: String
var value: Int
fn __init__(self&, name: String, value: Int):
self.name = name
self.value = value
fn dump(self):
print("Name:", self.name)
print("value:", self.value)
var copymove_test = CopyMoveTest("temp", 1)
copymove_test.dump()
# これはNG
# error: Expression [12]:32:15: value of type 'CopyMoveTest' cannot be copied into its destination
# var xxx = copymove_test
CopyMoveTest
に再代入の関数を実装していないため
さては関数にも渡せない?かと思ったら、一部そんなことあった.
def call_dump(obj: CopyMoveTest):
obj.dump()
&をつけると参照が渡されるので問題ないみたい
# OK
def call_dump(obj&: CopyMoveTest):
obj.dump()
# -> Name: changed
# -> value: 1
fnで定義する場合はどちらもできた。
- &なし: インスタンス変数変更不可
- &あり: インスタンス変数変更可能
# OK
fn call_dump(obj&: CopyMoveTest):
obj.name = 3
obj.dump()
fn call_dump(obj: CopyMoveTest):
# obj.name = 3 #NG
obj.dump()
他の言語をあまりやらず、普段書くのもPythonが基本的なPython畑の人なのでここら辺になってくると少し理解が追いつかなくなってくる。