Más o menos cualquier uso de tipos de miembros (es decir, anidados) puede dar lugar a la necesidad de tipos de métodos dependientes. En particular, mantengo que sin tipos de métodos dependientes, el patrón de pastel clásico está más cerca de ser un antipatrón.
¿Entonces, cuál es el problema? Los tipos anidados en Scala dependen de su instancia de cierre. En consecuencia, en ausencia de tipos de métodos dependientes, los intentos de usarlos fuera de esa instancia pueden ser frustrantemente difíciles. Esto puede convertir diseños que inicialmente parecen elegantes y atractivos en monstruosidades que son terriblemente rígidas y difíciles de refactorizar.
Ilustraré eso con un ejercicio que doy durante mi curso de entrenamiento 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
}
Es un ejemplo del patrón de pastel clásico: tenemos una familia de abstracciones que se refinan gradualmente a través de una jerarquía ( ResourceManager
/ Resource
se refinan por FileManager
/ File
que a su vez se refinan por NetworkFileManager
/RemoteFile
). Es un ejemplo de juguete, pero el patrón es real: se usa en todo el compilador Scala y se usó ampliamente en el complemento Scala Eclipse.
Aquí hay un ejemplo de la abstracción en uso,
val nfm = new NetworkFileManager
val rf : nfm.Resource = nfm.create
nfm.testHash(rf)
nfm.testDuplicates(rf)
Tenga en cuenta que la dependencia de la ruta significa que el compilador garantizará que los métodos testHash
y solo se puedan invocar con argumentos que le correspondan, es decir. es propio y nada más.testDuplicates
NetworkFileManager
RemoteFiles
Es indudablemente una propiedad deseable, pero ¿y si quisiéramos mover este código de prueba a un archivo fuente diferente? Con los tipos de métodos dependientes es trivialmente fácil redefinir esos métodos fuera de la ResourceManager
jerarquía,
def testHash4(rm : ResourceManager)(r : rm.Resource) =
assert(r.hash == "9e47088d")
def testDuplicates4(rm : ResourceManager)(r : rm.Resource) =
assert(r.duplicates(r))
Tenga en cuenta los usos de los tipos de métodos dependientes aquí: el tipo del segundo argumento ( rm.Resource
) depende del valor del primer argumento ( rm
).
Es posible hacer esto sin tipos de métodos dependientes, pero es extremadamente incómodo y el mecanismo es bastante poco intuitivo: he estado enseñando este curso durante casi dos años, y en ese momento, a nadie se le ha ocurrido una solución de trabajo inesperada.
Pruébalo por ti mismo ...
// 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)
Después de un corto tiempo luchando con él, probablemente descubrirá por qué yo (o tal vez fue David MacIver, no podemos recordar cuál de nosotros acuñó el término) lo llamo la panadería de la fatalidad.
Editar: el consenso es que Bakery of Doom fue la moneda de David MacIver ...
Para el extra: la forma de Scala de tipos dependientes en general (y los tipos de métodos dependientes como parte de ella) se inspiró en el lenguaje de programación Beta ... surgen naturalmente de la semántica de anidamiento consistente de Beta. No conozco ningún otro lenguaje de programación incluso débilmente convencional que tenga tipos dependientes en esta forma. Los idiomas como Coq, Cayenne, Epigram y Agda tienen una forma diferente de tipeo dependiente, que de alguna manera es más general, pero que difiere significativamente al ser parte de sistemas de tipos que, a diferencia de Scala, no tienen subtipos.