Scalaで構造型を使用する方法
1. 概要
Scalaは主に名目上型付けされた言語です。つまり、2つのオブジェクトの型は、同じ名前の場合にのみ等しくなります。
しかし、名前は異なるが共通点があるタイプについてはどうでしょうか。 タイプ階層を変更してタイプ間の関係を作成できない場合はどうなりますか?
このチュートリアルでは、 Scalaの構造型を使用して、継承またはを使用できない場合に、ランタイムチェック済みのポリモーフィックコードを作成する方法を学習します。 X188X]型クラス。
2. アヒルのようにしゃがむなら
Scalaでは、正確性を確保するために型を使用します。これは、型内のコード不変条件と型間の関係をエンコードすることによって行います。 コンパイラはこれらの不変条件を適用し、プログラムに特定のエラーカテゴリがないことを保証します。
これらの保証の代金を支払います。 コンパイラーはいくつかのプログラムを書くのを少し難しくします。
例えば; ダックタイピングを使用する次のPython3コードのようなプログラムを作成しようとすると、Scalaコンパイラはエラーをスローします。
class Duck:
def fly(self):
print("Ducks fly together")
class Eagle:
def fly(self):
print("Eagles fly better than MJ")
class Walrus:
def swim(self):
print("I am faster on the water than on the land")
def flyLikeAnEagle(animal):
animal.fly()
animals = [Duck(), Eagle(), Walrus()]
for animal in animals:
flyLikeAnEagle(animal)
明らかに、コンパイラがWalrusでメソッドflyを呼び出さないようにする必要があります。 ただし、DuckおよびEagleオブジェクトの呼び出しを拒否するのは不便です。
Pythonは実行時に型チェックを実行し、それを持っているすべてのオブジェクトでflyメソッドを呼び出すことができます。 プログラマーは、この機能を「アヒルのように鳴く場合はアヒルである」ため、ダックタイピングと呼びます。
2. アヒルを一列に並べる
Scalaリフレクションを使用して同様のコードを記述できますが、結果のコードを維持するのは困難です。
幸い、Scalaを使用すると、ダックタイピングと同じ結果を達成するための慣用的なコードを記述できます。
構造タイプを入力します。
type Flyer = { def fly(): Unit }
def callFly(thing: Flyer): Unit = thing.fly()
def callFly2(thing: { def fly(): Unit }): Unit = thing.fly()
def callFly3[T <: { def fly(): Unit }](thing: T): Unit = thing.fly()
上記の例でわかるように、型制約が予想されるほとんどの場所で構造型を宣言できます:型エイリアス、メソッドのパラメータ型、または下限タイプ。
Scalaは、適切なメソッドを呼び出すために下のリフレクションを使用するコードを生成しますが、コンパイル時の型の安全性の利点があります。 コンパイラは必要なメソッドが存在することを確認するため、実行時の例外の可能性を排除します。
"Ducks" should "fly together" in {
callFly(new Duck())
callFly2(new Duck())
callFly3(new Duck())
}
"Eagles" should "soar above all" in {
callFly(new Eagle())
callFly2(new Eagle())
callFly3(new Eagle())
}
"Walrus" should "not fly" in {
// The following code won't compile
// callFly(new Walrus())
// callFly2(new Walrus())
// callFly3(new Walrus())
}
3. より深刻な例
リソースが常に閉じていることを確認することにより、リソースのリークを防ぐための構造タイプの古典的で実用的な例。 従来のtry-catchブロックを使用することもできますが、規則に従うすべての人に依存します。
構造型を使用して柔軟な制御構造を記述できます:
type Closable = { def close(): Unit }
def using(resource: Closable)(fn: () => Unit) {
try {
fn()
} finally { resource.close() }
}
using(file) {
() =>
// Code using the file
}
すでに説明したように、 closeの呼び出しはリフレクションを使用しますが、渡す関数内のすべてのコードは静的にリンクされます。
単一の動的呼び出しのコストはごくわずかです。 それでも、他の場合には、オーバーロードを使用し、事前にわかっている型の制御構造のいくつかのバージョンを特殊化することの利点があるかもしれません。
この特定のケースでは、Sourceクラスの静的に型付けされたバージョンを作成できます。
def using(file: Source)(fn: () => Unit) {
try {
fn()
} finally {
file.close()
}
}
4. 結論
この記事では、他のポリモルフィックコードの記述が失敗する場合に構造型を使用する方法を検討しました。
結果のプログラムがリフレクションを使用していることも学びましたが、型の安全性が失われないことを証明しました。 唯一の欠点は、ランタイムリフレクションに関連するパフォーマンスのわずかな低下です。
いつものように、記事の完全なソースコードは、GitHubでから入手できます。