メンバ(ネストされた)型を多かれ少なかれ使用すると、依存するメソッド型が必要になる場合があります。特に、依存するメソッドタイプがないと、古典的なケーキパターンはアンチパターンに近いと私は主張します。
だから問題は何ですか?Scalaのネストされた型は、それらを囲むインスタンスに依存します。その結果、依存するメソッドタイプがない場合、そのインスタンスの外部でそれらを使用しようとすると、イライラするほど難しくなる可能性があります。これにより、最初はエレガントで魅力的であるように見えるデザインを、悪夢のように厳格でリファクタリングが困難な怪物に変えることができます。
Advanced Scalaトレーニングコースで行うエクササイズで、
trait ResourceManager {
type Resource <: BasicResource
trait BasicResource {
def hash : String
def duplicates(r : Resource) : Boolean
}
def create : Resource
// Test methods: exercise is to move them outside ResourceManager
def testHash(r : Resource) = assert(r.hash == "9e47088d")
def testDuplicates(r : Resource) = assert(r.duplicates(r))
}
trait FileManager extends ResourceManager {
type Resource <: File
trait File extends BasicResource {
def local : Boolean
}
override def create : Resource
}
class NetworkFileManager extends FileManager {
type Resource = RemoteFile
class RemoteFile extends File {
def local = false
def hash = "9e47088d"
def duplicates(r : Resource) = (local == r.local) && (hash == r.hash)
}
override def create : Resource = new RemoteFile
}
これは、古典的なケーキのパターンの例です:私たちは徐々に階層構造を通じて洗練されている抽象化の家族を持っている(ResourceManager
/ Resource
で洗練されているFileManager
/ File
で洗練された順番にですNetworkFileManager
/ RemoteFile
)。これはおもちゃの例ですが、パターンは本物です。Scalaコンパイラ全体で使用され、Scala Eclipseプラグインで広く使用されていました。
以下は、使用中の抽象化の例です。
val nfm = new NetworkFileManager
val rf : nfm.Resource = nfm.create
nfm.testHash(rf)
nfm.testDuplicates(rf)
パスの依存関係は、コンパイラがtestHash
とtestDuplicates
メソッドNetworkFileManager
がそれに対応する引数でのみ呼び出されることを保証することを意味することに注意してください。それは独自のものRemoteFiles
であり、他には何もない。
これは紛れもなく望ましい特性ですが、このテストコードを別のソースファイルに移動したいとします。依存するメソッドタイプを使用すると、ResourceManager
階層外のメソッドを簡単に再定義できます。
def testHash4(rm : ResourceManager)(r : rm.Resource) =
assert(r.hash == "9e47088d")
def testDuplicates4(rm : ResourceManager)(r : rm.Resource) =
assert(r.duplicates(r))
ここでは、依存するメソッドタイプの使用に注意してください。2番目の引数(rm.Resource
)のタイプは、最初の引数(rm
)の値に依存します。
依存するメソッドの種類なしでこれを行うことは可能ですが、それは非常に厄介であり、メカニズムは非常に直感的ではありません。私はこのコースをほぼ2年間教えてきましたが、その時点では誰もプロンプトなしで実用的なソリューションを考え出していません。
自分で試してみてください...
// Reimplement the testHash and testDuplicates methods outside
// the ResourceManager hierarchy without using dependent method types
def testHash // TODO ...
def testDuplicates // TODO ...
testHash(rf)
testDuplicates(rf)
少し苦労してみると、なぜ私(または、David MacIverだったか、誰がこの用語を作ったか思い出せない)が、これをDoomのベーカリーと呼んでいるのがわかるでしょう。
編集:コンセンサスは、Doom Baker of DoomがDavid MacIverの造語であったということです...
おまけに、一般的なScalaの依存型の形式(およびその一部としての依存メソッド型)は、プログラミング言語Betaから発想を得たものです。これらは、Betaの一貫した入れ子のセマンティクスから自然に生まれました。この形の依存型を持つ他のほんのわずかに主流のプログラミング言語さえ知りません。Coq、Cayenne、Epigram、Agdaなどの言語には、いくつかの点でより一般的な依存型の異なる形式がありますが、Scalaとは異なり、サブ型がない型システムの一部であることによって大幅に異なります。