131 votes

Quels sont certains cas utilisation convaincante pour les types de méthodes dépendant ?

Dépendant de la méthode des types, qui était une fonctionnalité expérimentale avant, est maintenant activé par défaut dans le coffre, et apparemment, cela semble avoir créé une certaine excitation dans la Scala de la communauté.

Après le premier coup d'oeil, il n'est pas immédiatement évident que cela pourrait être utile pour. Heiko Seeberger posté un exemple simple, dépendant de la méthode de types ici, comme peut être vu dans le commentaire, on peut facilement être reproduit avec des paramètres de type sur les méthodes. Donc ce n'était pas très convaincante exemple. (J'ai peut-être raté quelque chose d'évident. S'il vous plaît corrigez-moi si).

Quelles sont certaines pratiques et utiles, des exemples de cas d'utilisation pour l'dépendant de la méthode des types d'où ils sont clairement plus avantageux que les solutions de rechange? Quelles sont les choses intéressantes pouvons-nous faire avec eux ce qui n'était pas possible/facile avant? Que font-ils de nous acheter sur le type existant fonctionnalités du système?

Merci!


QUESTION BONUS: Sont dépendants les types de méthode analogue à / s'inspirer de toutes les fonctionnalités trouvées dans les systèmes de type de pointe tapé langages comme Haskell, OCaml?

115voto

Miles Sabin Points 13604

Plus ou moins toute utilisation de membre (ie. imbriquée) types peuvent donner lieu à un besoin de charge pour les types de méthode. En particulier, je maintiens que sans dépendant de la méthode des types de la classique le modèle de gâteau est plus près d'être un anti-modèle.

Quel est donc le problème? Les types imbriqués dans Scala sont dépendants de leur joignant instance. Par conséquent, en l'absence de dépendant de la méthode des types, des tentatives de les utiliser en dehors de cette instance peut être très difficile. Cela peut tourner à des conceptions qui, au départ, semblaient élégant et attrayant dans monstruosités qui sont nightmarishly rigide et difficile à refactoriser.

Je vais l'illustrer avec un exercice que je donne lors de mon Avancée Scala cours de formation,

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
}

C'est un exemple classique de modèle de gâteau: nous avons une famille d'abstractions qui sont progressivement affinées par une hiérarchie (ResourceManager/Resource sont définies en FileManager/File qui sont à leur tour raffiné en NetworkFileManager/RemoteFile). C'est un jouet, par exemple, mais le modèle est réel: il est utilisé tout au long de la Scala compilateur et a été largement utilisé dans la Scala plugin Eclipse.

Voici un exemple de l'abstraction dans l'utilisation,

val nfm = new NetworkFileManager
val rf : nfm.Resource = nfm.create
nfm.testHash(rf)
nfm.testDuplicates(rf)

Notez que le chemin de la dépendance signifie que le compilateur va garantir que l' testHash et testDuplicates méthodes NetworkFileManager ne peut être appelée avec des arguments qui lui correspondent, c'est à dire. c'est propre RemoteFiles, et rien d'autre.

C'est indéniablement une propriété souhaitable, mais supposons que nous voulions déplacer ce test de code dans un autre fichier source? Ayant à charge les types de méthode c'est trivial à redéfinir ces méthodes à l'extérieur de l' ResourceManager de la hiérarchie,

def testHash4(rm : ResourceManager)(r : rm.Resource) = 
  assert(r.hash == "9e47088d")

def testDuplicates4(rm : ResourceManager)(r : rm.Resource) = 
  assert(r.duplicates(r))

Remarque l'utilise charge les types de méthode ici: le type de la deuxième argument (rm.Resource) dépend de la valeur du premier argument (rm).

Il est possible de le faire sans dépendant de la méthode des types, mais il est extrêmement maladroit, et le mécanisme est assez peu intuitive: j'ai été l'enseignement de ce cours pendant près de deux ans maintenant, et depuis ce temps, personne n'est venu avec une solution de travail de manière spontanée.

Essayez par vous-même ...

// 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)

Après un court moment, en difficulté avec elle, vous découvrirez probablement pourquoi j'ai (ou c'était peut-être David MacIver, nous ne pouvons pas nous souvenir de qui nous a inventé le terme) appellent cela la Boulangerie de Doom.

Edit: le consensus est que la Boulangerie de Doom était David MacIver de la monnaie ...

Pour les bonus: Scala, à la forme de types de charge en général (et dépendant de la méthode des types comme une partie de celui-ci) a été inspiré par le langage de programmation Beta ... ils se posent tout naturellement dans la Bêta constante de la nidification de la sémantique. Je ne connais pas d'autre, même faiblement, intégrer langage de programmation qui a des types dépendants dans ce formulaire. Les langues, comme le Coq, le poivre de Cayenne, de l'Épigramme et Agda ont une forme différente de l'dépendante de frappe qui est d'une certaine façon plus générale, mais qui diffère de manière significative, en faisant partie de systèmes de type qui, contrairement à la Scala, de ne pas avoir de sous-typage.

53voto

Alexey Romanov Points 39124
trait Graph {
  type Node
  type Edge
  def end1(e: Edge): Node
  def end2(e: Edge): Node
  def nodes: Set[Node]
  def edges: Set[Edges]
}

Ailleurs, nous pouvons de manière statique garantie que nous ne sommes pas mélanger les nœuds de deux graphiques différents, par exemple:

def shortestPath(g: Graph)(n1: g.Node, n2: g.Node) = ... 

Bien sûr, cela a déjà travaillé si définie à l'intérieur d' Graph, mais disons que nous ne pouvons pas modifier Graph et sont la rédaction d'un "pimp my library" de l'extension.

Sur la deuxième question: les types de permis par cette fonction sont beaucoup plus faibles que complète des types dépendants (Voir Dépendante de Programmation Typé dans Agda pour une saveur.) Je ne pense pas que je l'ai vu une analogie avant.

6voto

Shelby Moore III Points 2088

Cette nouvelle fonctionnalité est nécessaire lorsqu’un béton type abstrait membres sont utilisés au lieu des paramètres de type. Lorsque des paramètres de type sont utilisés, la dépendance de type familial polymorphisme peut être exprimée dans les versions plus récentes et des plus âgées de Scala, comme dans l’exemple simplifié ci-dessous.

3voto

Shelby Moore III Points 2088

Je suis le développement d'un modèle pour la interoption d'une forme de programmation déclarative avec l'état de l'environnement. Les détails ne sont pas pertinentes en l'espèce (par exemple, renseignements à propos des rappels et de la similitude conceptuelle de l'Acteur modèle combiné avec un Sérialiseur).

La question pertinente est de l'état, les valeurs sont stockées dans une table de hachage de la carte et référencée par une clé de hachage de la valeur. Fonctions d'entrée immuable des arguments qui sont des valeurs de l'environnement, peuvent appeler d'autres fonctions, et l'écriture de l'état de l'environnement. Mais les fonctions sont pas autorisés à lire les valeurs de l'environnement, le code interne de la fonction ne dépend pas de l'ordre des changements d'état et demeure ainsi déclarative dans ce sens). Comment tapez ceci dans Scala?

L'environnement de la classe doit avoir une méthode surchargée qui intrants, notamment une fonction à appeler, et des intrants, les clés de hachage des arguments de la fonction. Ainsi, cette méthode peut appeler la fonction avec les valeurs nécessaires à partir de la valeur de hachage de la carte, sans fournir l'accès public en lecture pour les valeurs (donc comme nécessaire, en niant les fonctions de la capacité de lire des valeurs de l'environnement).

Mais si ces clés de hachage sont des chaînes de caractères ou un nombre entier de valeurs de hachage, le typage statique de la table de hachage type d'élément de carte subsume à l'un ou à AnyRef (hash code de mappage pas indiqué ci-dessous), et donc une incompatibilité pourrait se produire, c'est à dire qu'il serait possible de mettre n'importe quel type de valeur dans une table de hachage de la carte pour une clé de hachage.

trait Env {
...
  def callit[A](func: Env => Any => A, arg1key: String): A
  def callit[A](func: Env => Any => Any => A, arg1key: String, arg2key: String): A
}

Bien que je n'ai pas fait de test suivantes, en théorie je peux obtenir les clés de hachage à partir des noms de classe au moment de l'exécution employant classOf, donc une clé de hachage est un nom de classe au lieu d'une chaîne de caractères (en utilisant Scala de backticks pour intégrer une chaîne de caractères dans un nom de classe).

trait DependentHashKey {
  type ValueType
}
trait `the hash key string` extends DependentHashKey {
  type ValueType <: SomeType
}

Si statique type de sécurité est atteint.

def callit[A](arg1key: DependentHashKey)(func: Env => arg1key.ValueType => A): A

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X