見出し画像

Mojo(サイトの和訳)変数

2024/03/02時点でのMojoのサイトの導入が終わった後のテキストの和訳です。

変数

変数とは、値またはオブジェクトを保持する名前です。
Mojoの変数はすべて変更可能です。(実行時に変更できない定数値を定義したい場合は、エイリアスキーワードを参照してください)。
メモ

Mojo は以前、不変変数を宣言する let キーワードをサポートしていました。これは、言語を簡素化するためと、
別の場所で説明するその他の理由から削除されました。
古いコードの移行を簡単にするために、let宣言は現在サポートされていますが、機能はvar宣言と同じです。
宣言されていない変数

def関数やREPL環境内では、名前と値だけで変数を作成することができます。例えば

name = "Sam"

varなしで宣言された変数はsに続く。
ノート

宣言されていない変数は、fn関数や構造体フィールドでは使用できません。
宣言された変数

varキーワードで変数を宣言することができます。例えば

var name = "Sam"
var user_id:Int

name変数は文字列 "Sam "に初期化されている。user_id変数は未初期化ですが、整数値を表すInt型が宣言されています。
宣言された値はすべて型付けされ、型アノテーションで明示的に型付けされるか、値で初期化されたときに暗黙的に型付けされます。

宣言された変数は強く型付けされているので、それらの型が暗黙的に変換されない限り、異なる型の値を変数に代入することはできません。
たとえば、次のコードはコンパイルできません:

var user_id:Int = "Sam"

型付けに加え、宣言された変数は、宣言されていない変数とは異なり、レキシカル・スコープに従う。

最後に、varを使うことで、タイプミスによる実行時エラーを防ぐことができる。例えば、宣言されていない変数の名前のスペルを間違えた場合、
Mojoは単純にスペルを間違えた名前を使用して新しい変数をインスタンス化します。
しかし、すべての変更可能な変数を最初に var で宣言する必要がある場合 (fn 関数の内部がそうです)、
次のようなスペルミスはコンパイラによって検出されます:

var name = "Sam"

どこか後で...

nane = "Sammy" # これは `fn` 関数の中では許されない。

def関数内でvarを使用することはできますが、この利点はfn関数内で使用された場合にのみ実現されます。
この場合、Mojoコンパイラは宣言されていない変数(上記のnaneなど)を未知の宣言としてフラグを立てます。
メモ

REPL 環境で Mojo を使用する場合、トップレベル変数 (関数または構造体の外側の変数) は var 宣言を必要としません。
型の注釈

Mojo は動的な変数型 (実行時に値の型を推測できる) をサポートしていますが、変数の静的な型アノテーションもサポートしています。
これにより、変数のコンパイル時に強力な型チェックを行うことができ、コードの予測性、管理性、安全性が向上します
(特に、fn 関数の型チェックと組み合わせた場合)。

変数の型を指定するには、コロンの後に型名を続けます:

var name: String = "Sam"

こうすることで、nameに文字列でない(あるいは暗黙的に文字列に変換できない)値が代入されることはない。
メモ

型アノテーションを使用するには、varで変数を宣言する必要があります。

型に引数が1つだけのコンストラクタがある場合、2つの方法で初期化することができます:

var name1:文字列 = "Sam"
var name2 = String("Sam")

これらの行はどちらも同じコンストラクタを呼び出して、StringLiteral から String を生成しています。
遅い初期化

型アノテーションを使用すると、後期初期化が可能になります。例えば、ここではz変数が最初に型だけで宣言され、
値が後で代入されていることに注目してください:

fn my_function(x: Int):
var z:Float32
if x != 0:
z = 1.0
else:
z = foo()
print(z)

fn foo() -> Float32:
return 3.14

ノート

後期初期化は、変数が型付きで宣言されている場合にのみ機能する。
暗黙の型変換

いくつかの型には、ある型からその型への組み込み型変換(型キャスト)があります。例えば、Stringに数値を代入すると、
コンパイラー・エラーではなく、"1 "という文字列が作成されます:

var number:文字列 = 1

上で示したように、値の代入は、代入される値と一致する単一の引数を取るコンストラクタがターゲット・タイプにあれば、
コンストラクタ呼び出しに変換できる。
そこで、このコードでは整数を受け取るStringコンストラクタを使用する:init(inout self, num: Int).

暗黙的変換はオーバーロードされた関数のロジックに従ったもので、ここで起こっているのはまさにこれだからだ:

var number = String(1)

このように、特定の型(Stringなど)の引数を必要とする関数を呼び出す場合、
その型が(その型のオーバーロードされたコンストラクタのいずれかを使用して)暗黙的に必要な型に変換できる限り、任意の値を渡すことができます。

例えば、Stringを期待する関数にIntを渡すことができます。StringにはIntを受け取るコンストラクタがあるからです:

fn take_string(version: String):
print(version)

fn pass_integer():
バージョン:Int = 1
take_string(version)

暗黙変換の詳細については、コンストラクタと暗黙変換を参照してください。
変数のスコープ

var で宣言された変数は、レキシカル・スコープによって束縛されます。つまり、入れ子になったコードブロックは、
外側のスコープで定義された変数を読んだり変更したりすることができます。
しかし、外側のスコープは内側のスコープで定義された変数を読むことはできません。

例えば、ここに示したifコード・ブロックは、外側の変数が読み書きできる内側のスコープを作りますが、
新しい変数はifブロックのスコープを越えて生きません:

def lexical_scopes():
var num = 10
var dig = 1
if True:
print("num:", num) # 外側スコープ "num" を読み込む
var num = 20 # 新しい内部スコープ "num" を作成する
print("num:", num) # 内部スコープ "num" を読み込む
dig = 2 # 外側スコープ "dig" を編集する
print("num:", num) # 外側スコープ "num" を読み込む
print("dig:", dig) # 外側スコープ "dig "を読む

レキシカルスコープ()

num: 10 num: 20 num: 10 dig:2

ifの中のvar文は、外側の変数と同じ名前の新しい変数を作成することに注意してください。
これにより、内側のループが外側のnum変数にアクセスするのを防ぐことができる。
(これは「変数シャドーイング」と呼ばれるもので、内側のスコープの変数が外側のスコープの変数を隠す、あるいは「シャドーイング」するものである)

内側のnum変数の寿命は、ifコードブロックが終わったところでちょうど終わる。

これは宣言されていない変数(varキーワードのない変数)とは対照的で、関数レベルのスコープを使用します
(Pythonの変数の動作と同じです)。つまり、ifブロックの中で宣言されていない変数の値を変更すると、関数全体の値が変更されます。

例えば、以下は同じコードですが、var宣言がありません:

def function_scopes():
num = 1
if num == 1:
print(num) # 関数スコープ "num" を読み込む
num = 2 # 関数スコープの変数を更新
print(num) # 関数スコープ "num" を読み込む
print(num) # 関数スコープ "num" を読み込む

関数スコープ()

1 2 2

宣言されていない変数(Pythonスタイルの変数)は、(レキシカルスコープではなく)関数レベルのスコープを使うからです。

この記事が気に入ったらサポートをしてみませんか?