Type Promotionの訳
It’s a Dart language feature that in certain circumstances allows you to treat variables as if they had a more specific type than their original declaration.
これはDart言語の機能で、特定の状況下で、変数を、元の宣言よりも特定の型を持っているかのように扱うことができます。
Here, for example, I’ve got a variable text of type Object.
ここでサンプルとして、Object型の変数textがあるとします。
Object text = "Hello World"; if(text is String){ print(text.length); }else{ print("it wasn't a string!"); }
But Dart will allow me at compile time to treat it as if it’s a string and access its length property even though I haven’t done any casting.
しかし、Dartでは、コンパイル時に、型キャスティングを行っていないにもかかわらず、これをString型として扱い、そのlengthプロパティにアクセスすることができるのです。
If you’re like me, you may have seen Type Promotion and started using it to simplify your code until one day you ran into a situation like this.
私のように、Type Promotionを見て、コードを簡略化するために使い始め、ある日このような状況に遭遇したことがあるかもしれません。
class UnreliableProvider{ String? get maybeString => //Implementation of maybeString getter void someMethod(){ if(maybeString is String){ print(maybeString.length); //←error } else { print("it wasn't a string!"); } } } //The property 'length' can't be unconditionally accessed because the receiver can be 'null'. //receiver(maybeString)がnullになる可能性があるので、プロパティ'length'は無条件にアクセスすることはできません。
maybeString.lengthの箇所で上記のコンパイル時エラー発生。
It looks really similar.
非常に前のサンプルと似た状況です。
There’s the same check and everything. But Dart won’t let you treat the reference the same way.
チェックも何もかも同じです。でもDartはリファレンスを同じように扱わせない。
if文でmaybeStringをString型とタイプチェックしているのに、そのブロック内でゲッターlengthにアクセスできない。つまりString型として扱えていない。
Error– ‘maybeString’ refers to a property. so it couldn’t be promoted. So what’s going on here?
エラー– ‘maybeString’はプロパティを参照しているため、型プロモーションできませんでした。
どういうことでしょう?
Let’s take a second and talk about how Type Promotion works.
少し時間をとってどのようにタイププロモーションが行われるのかを話していきます。
Type Promotion comes into play in two ways–
タイププロモーションには二つの方法があります。
Promoting from a general type to a more specific subtype and promoting from a nullable type to the non-nullable underlying type.
「汎用的な型からより具体的なサブタイプへのプロモーション」と「nullable型からnon-nullable型への型プロモーション」の二つです。
First, promotion from a general type to a specific subtype.
まず最初に、汎用的な型から具体的なサブタイプへのプロモーションです。
Object text = "Hello World";
Take this variable text. It’s a variable of type object, the base class for all Dart objects except for null.
変数textですが、これはObject型の変数であり、Objectクラスは、nullを除くDartのすべてのオブジェクトの基本クラスです。
Object text = "Hello World"; print(text.length); // error
If we try to print text.length like this, Dart won’t let us do it because the base type object doesn’t have a length getter defined.
text.lengthをprintしようとすると、Object型にはlengthゲッターが定義されていないので、Dartはprintを許可しません。
However, we know that text is a string, a specific subtype of object, which does have length defined.
しかし、私たちはtextの値がString型であることを知っています。Object型の具体的なサブタイプであるString型です。Stringクラスにはlengthが定義されています。
Object text = "Hello World"; if(text is String){ print(text.length); }
We add a type check and make sure that text is, in fact, a string.
私たちはタイプチェックを行い、textの値がString型であることを確認します。
With that type check in place, Type Promotion takes place.
このタイプチェックにより、タイププロモーションが行われます。
Dart knows that within the scope of that if statement, text is a string.
このif文のブロック内において、textの値がString型であると、Dartは認識します。
Text gets promoted from an object to a string.
textはObject型からString型へとタイププロモーションされました。
So we’re able to access text length and print it without issue.
ですから私たちはtext.lengthにアクセスでき、問題なくprint文を実行することができました。
Second, there’s nullable to non-nullable Type Promotion which is exactly what it sounds like, for example, when a nullable variable gets promoted to a non-nullable type, like string.
二つ目の、nullable型からnon-nullable型へのタイププロモーションです。これはまさにその通り、例えばnullable変数がstringのようなnon-nullable型にプロモーションする場合です。
String? maybeString = Random().nextBool() ? "This is a string!" : null; if(maybeString is String){ print(maybeString.length); } else { print("it wasn't a string!"); }
The Dart type system is intelligent enough to track how variables are accessed in the branches of a function.
Dartの型システムは、関数の分岐で変数がどのようにアクセスされるかを追跡するのに十分なインテリジェンスを持っています。
Here, the code branches in one of two ways.
ここで条件分岐には二つの道(選択肢)があります。
The variable maybeString is a string, or it’s null.
maybeString変数はString型か、あるいはnullです。
There’s also a type check to see if maybeString is of type string.
maybeStringがString型かどうかのタイプチェックも行われました。
If the type check returns true, then maybeString gets promoted to a non-nullable string in that branch, and we can print its length.
タイプチェックでtrueが返されると、maybeStringはnon-nullable型へプロモーションされて、私たちはlengthをprintすることができます。
If it’s not a string, we print out a statement that screams, “it was null!”.
もしString型でないなら、”it was null”と表示されます。
Objection, Your Honor. But how do we know that Type Promotion took place?
しかし、私たちはどのようにしてタイププロモーションが発生したかを知るのでしょう?
Well, nullable types are a union type of null and its underlying type, which in this case is string.
nullable型は、nullとその基になる型(この場合はString型)の共用体型です。
This means that for nullable types, you can only access methods and properties that are defined by both string and null until type promotion takes place.
つまり、nullable型の場合、型の昇格が行われるまで、String型とnullの両方で定義されているメソッドとプロパティにのみアクセスできます。
Even though length is defined for string, it’s not defined for null, meaning that length would not have been a valid method if it were still a nullable string.
lengthはString型に対して定義されていても、nullに対しては定義されていないので、String?型のままでは、lengthは有効なメソッドではない(アクセスできない)ということです。
Now, back to our example.
If maybeString is null and fails the type check, Dart takes the second branch and we end up at the print statement, which prints, “it was null!”.
さて、今回の例に戻ります。
もしmaybeStringの値がNULLで、型チェックに失敗すると、Dartは2番目のブランチを取り、print文にたどり着き、「it was null!」とプリントされます。
Then there’s the promotion of a general type to a specific subtype.
それから汎用的な型から具体的なサブタイプへのプロモーションに関してですが。
Let’s revisit our earlier example, which had the variable test declared as an Object.
Object型として宣言された変数testのある、最初のサンプルに戻りましょう。
//再掲 Object text = "Hello World"; if(text is String){ print(text.length); }else{ print("it wasn't a string!"); }
Stepping through the code, Dart can tell that the code branches, again, in one of two ways.
コードをステップスルーすると、Dartは、コードが2つの方法のいずれかで分岐していることを確認できます。
First branch, the variable text is, in fact, a string, in which case, print the string’s length.
最初の分岐、つまり変数textがString型の場合、textのlengthを表示します。
Or the second branch, text is not a string, in which case, print some output saying that it’s not a string.
二つ目の分岐、つまりtextの値がString型ではない場合、”it wasn’t a string!”と表示されます。
Dart knows that, once you’re within the first branch, the text variable is a string.
Dartは、一度一つ目の分岐に入れば、textの値がString型であるということを認識します。
Why? Because we had the type check, which passed, and so the object got promoted to a string within the body of the if statement.
なぜ?
それは私たちがタイプチェックをして、一つ目の条件をパスしたので、if文のブロック内でtextがString型にプロモーションされたからです。
Within that branch, we can keep accessing text as a string, thanks to Type Promotion.
そのブランチの中(ブロックの中)では、Type Promotionによって、textをString型としてアクセスし続けることができます。
On the flip side, if we land on the second branch, Dart knows that the example variable is not a string because it failed the type check, and so it’s not promoted.
一方、2番目のブランチに着地した場合、Dartはtextが型チェックに失敗してString型でないことを知っているので、昇格させません。
Well then, why didn’t it work in our unreliable provider example?
ではなぜUnreliableProviderのサンプルではこれが機能しなかったのでしょうか?
Let’s take a look at it again.
もう一度見てみましょう。
class UnreliableProvider{ String? get maybeString {} //Implementation of maybeString getter void someMethod(){ if(maybeString is String){ //←一回目の呼び出し、ここでString型と確認されても print(maybeString.length); //←二回目の呼び出し、ここではnullである可能性がある。 } else { print("it wasn't a string!"); } } }
We’ve swapped out the text variable for the maybeString property.
text変数をmaybeStringプロパティに交換しました。
But nothing else has really changed.
しかしそれ以外の違いはありません。
Why does Type Promotion work with variables but not object properties?
なぜタイププロモーションは変数には行われるのに、オブジェクトのプロパティには行われないのでしょう?
Taking a deeper look at our seemingly suspicious UnreliableProvider, we see that the getter for the maybeString property is truly living up to its name.
一見怪しげなUnreliableProviderをさらに詳しく見てみると、maybeStringプロパティのゲッターは、まさにその名の通り生きていることがわかります。
maybeString is sometimes a string. Sometimes it’s just null.
maybeStringはString型の値の場合もあり、ときにはnullの場合もあります。
We’ve seen this before. Well, at least whoever named this variable was descriptive. Knowing what we know now, our failing example makes more sense.
これは前にも見たことがある。少なくとも、この変数を命名した人は、説明的でしたね。今までの失敗例を知れば、より納得がいくはずです。
Dart knows you could possibly write code like this, so it has to handle it.
Dartは、あなたがこのようなコードを書く可能性があることを知っているので、それを処理する必要があるのです。
Otherwise, Dart couldn’t guarantee sound null safety.
そうしないと、Dartが健全なnull安全性を保証できません。
As a result, Type Promotion doesn’t work for object properties because an object property has the potential to return a different type on a second call.
オブジェクトのプロパティは、2回目の呼び出しで異なる型を返す可能性があるため、結果として、タイププロモーションはオブジェクトのプロパティには機能しません。
class UnreliableProvider{ String? get maybeString => Random().nextBool() ? "This is a string!" : null; void someMethod(){ if(maybeString is String){ //←一回目の呼び出し、ここでString型と確認されても print(maybeString.length); //←二回目の呼び出し、ここではnullである可能性がある。 //↑ error : "The property 'length' can't be unconditionally accessed because the receiver can be 'null'." } else { print("it wasn't a string!"); } } }
オーバーライドの結果maybeStringが上記のようにオーバーライドされたら、一回目のゲッターmaybeStringの呼び出しでString型と判定されても、二回目のゲッター呼び出しではnull(NULL型)と判定される可能性がある、ということ。
結局サブクラスや実装先での実装次第、ということになるのでnull安全性を確保できない(タイププロモーションはできない)、ということ。
So even though the first call to get maybeString could have returned a string, a second call could have potentially returned a different type– null.
仮に一回目の呼び出しでmaybeStringがString型の値を返したとしても、二回目の呼び出しでは違う型、つまりnullを返す可能性があります。
You may be wondering, well, what if I’ve made sure that my object property returns a value that isn’t null but it still doesn’t work?
オブジェクトのプロパティがnullでない値を返すことを確認したが、それでもうまくいかない? なぜなの?、とあなたは思うかもしれません。
Dart doesn’t have sealed classes. That means every class can be extended or even implemented.
Dartにはsealed classがありません。つまり、全てのクラスを継承することができるし、全てのクラスを実装(implements)することも可能です。
For example, imply senders and getters can be overridden and the extended class may choose to return null for that same object property like so.
例えば、セッターやゲッターを堂々とオーバーライドして、継承先クラスがその同じオブジェクトのプロパティに対して、次のようにnullを返すことを選択することができます。
So how do you get around this problem with Type Promotion and object properties?
タイププロモーションとオブジェクトのプロパティに関するこの問題にどう対処するのが良いでしょうか?
Hint– only local variables can be promoted, so assign the object property value to a local variable, then reference the variable instead of the property.
ヒント:昇格できるのはローカル変数だけなので、オブジェクトのプロパティ値をローカル変数に代入し、プロパティの代わりにその変数を参照します。
Voila! The type track is successful, and maybeString local gets promoted to a string.
ほらね。型追跡は成功し、maybeStringLocalはString型に昇格しました。
class UnreliableProvider{ String? get maybeString {} //Implementation of maybeString getter void someMethod(){ String? maybeStringLocal = maybeString; if(maybeStringLocal is String){ print(maybeStringLocal.length); } else { print("it wasn't a string!"); } } }
One thing that can be easy to forget, when you’re using a local variable for Type Promotion, if you want to write a value back to the object property, make sure you update the property itself and not just the local variable.
ひとつ忘れがちなのは、Type Promotionでローカル変数を使用している場合、オブジェクトのプロパティに値を書き戻す場合は、ローカル変数だけでなくプロパティ自体も更新するようにすることです。
class NoteBook{ int? currentPage = 5; turnPage(){ int? currentPageLocal = currentPage; if(currentPageLocal != null){ //Type Promotion print(currentPageLocal.toDouble()); //Write to the promoted local variable currentPageLocal += 1; //currentPageLocal = 6, currentPage = 5 //Remember to write back to the original property! currentPage = currentPageLocal; //currentPageLocal = 6, currentPage = 6 } } }
https://dartpad.dev/?id=3f01234c7f71359527bfe6ffceab7f61
参考