2013-01-27 3 views
4

J'ai posé quelques questions à ce sujet mais cette fois je veux en faire une discussion plus générale, car il me semble que Scala manque de blocs très importants.Scala: immutabilité et compatibilité de type dépendant du chemin

Consultez le code suivant (qui est simplifiée de mon vrai projet),

trait World { 
    type State <: StateIntf 
    def evolve(s: State): State 
    def initialState: State 
} 

class Algorithm(world: World) { 
    def process(s: world.State) { 
    val s1 = world.evolve(s) 
    // ... do something with s and s1 
    } 
} 

Tout semble si beau et mathématique, mais

object SomeWorld extends World {...} 
new Algorithm(SomeWorld).process(SomeWorld.initialState) // incompatible type 

Certes, vous pouvez le faire de la façon suivante

trait World { 
    type State <: StateIntf 
    var s: State 
    def evolve: Unit  // s = next state 
    def initialize: Unit // s = initial state 
    def getState: StateIntf = s 
} 

Mais nous sommes juste de retour au monde mutable.

On me dit que c'est parce que Scala n'a pas d'analyse de flux. Si tel est le problème, Scala ne devrait-il pas avoir cette pièce? J'ai seulement besoin que le compilateur puisse être conscient que les valeurs passées de val à val sont les mêmes de sorte que leurs types internes doivent être d'accord. Cela me semble si naturel, comme:

  1. val est le concept le plus foundamental qui implique immuabilité dans scala
  2. type dépendant chemin compatibilité est nécessaire pour modéliser des choses comme World avec immuabilité complète (qui est fortement souhaitée d'un perspective mathématique)
  3. d'analyse de flux de passage val s résoudre le problème

Suis-je trop demander? Ou y a-t-il déjà une bonne façon de le résoudre?

Répondre

5

Le compilateur a parfois besoin d'un peu d'aide pour prouver que ce que vous faites est légal lors de l'utilisation des types dépendants de chemin. C'est-à-dire, comme vous l'avez dit, le compilateur manque d'analyse de flux, nous devons donc lui dire explicitement que nous n'utilisons pas seulement World, nous utilisons exactement SomeWorld afin que nous puissions utiliser SomeWorld.initialState.

Dans votre cas, si vous changez Algorithm comme ceci:

class Algorithm[W <: World](world: W) { 
    def process(s: world.State) { 
    val s1 = world.evolve(s) 
    // ... do something with s and s1 
    } 
} 

Puis les compiles suivantes:

object SomeWorld extends World {...} 
new Algorithm[SomeWorld.type](SomeWorld).process(SomeWorld.initialState) 
6

Je pense que les médicaments génériques offre une solution simple à ce problème:

trait World[S <: StateInf] { 
    def evolve(s: S): S 
    def initialState: S 
} 

class Algorithm[S <: StateInf](world: World[S]) { 
    def process(s: S) { 
    val s1 = world.evolve(s) 
    // ... do something with s and s1 
    } 
} 
+1

Je pense que le type supérieur lié comme [S <: StateInf] devrait être ajouté au type paramètres. – Odomontois

+0

@Odomontois Merci. J'ai oublié le lien. – paradigmatic

+0

Après 2+ ans ... finalement réalisé pourquoi les génériques ne fonctionnent pas pour moi (mais dans la plupart des cas, je dirais que votre solution est géniale). Dans ma situation, j'ai deux mondes avec le même type d'état, et je veux appliquer la fonction World 1 à un état généré dans World 2, mais l'état doit être passé par un convertisseur avant d'être utilisé. Ce comportement est mieux modélisé par type interne, puisque les génériques permettent d'appliquer la fonction sans conversion, ce qui est dangereux car elle compile et s'exécute mais produit un non-sens. Je dois dire que j'ai failli commettre plusieurs fois cette erreur et que j'ai été averti à chaque fois pendant la compilation. – Kane

Questions connexes