2017-05-13 4 views
3

Je voulais approfondir la programmation de niveau type dans Scala et j'ai commencé à faire de petits exercices. J'ai commencé avec une implémentation des nombres de Peano au niveau du type. Voici le code ci-dessous!Programmation de niveau type dans Scala

sealed trait PeanoNumType { // Type at the end indicates to the reader that we are dealing with types 
    type plus[That <: PeanoNumType] <: PeanoNumType 
} 

sealed trait ZeroType extends PeanoNumType { 
    type plus[That <: PeanoNumType] = That 
} 

sealed trait NextType[This <: PeanoNumType] extends PeanoNumType { 
    type plus[That <: PeanoNumType] = NextType[This#plus[That]] 
} 

Maintenant, la question est, que l'implémentation ci-dessus m'achèterait-elle? Comment puis-je m'en servir?

Répondre

6

Tant que vous avez besoin de créer ces types vous-même, cela ne vous donne pas grand-chose. Mais, dès que vous faites du compilateur faire les choses pour vous, ce sera beaucoup plus utile.

Avant le montrer, nous allons changer une façon de représenter Peano aritmetic en quelque chose de plus court:

sealed trait Num 
case object Zero extends Num 
case class Succ[N <: Num](num: N) extends Num 

Vous pouvez ensuite créer une liste avec une taille connue au moment de la compilation:

sealed abstract class List[+H, N <: Num](val size: N) { 
    def ::[T >: H](value: T): List[T, Succ[N]] = Cons(value, this) 
} 
case object Nil extends List[Nothing, Zero.type](Zero) 
case class Cons[+H, N <: Num](head: H, tail: List[H, N]) extends List[H, Succ[N]](Succ(tail.size)) 
type ::[+H, N <: Num] = Cons[H, N] 

Si vous vérifier le type de sth créé avec la liste de sych il aura la taille encodée dans son type:

val list = 1 :: 2 :: 3 :: 4 :: Nil // List[Int, Succ[Succ[Succ[Succ[Zero.type]]]]] = Cons(1,Cons(2,Cons(3,Cons(4,Nil)))) 

La prochaine chose que vous pourriez faire serait d'utiliser des implicits pour vérifier quelque chose, par ex.

trait EvenNum[N <: Num] 
implicit val zeroIsEven = new EvenNum[Zero.type] {} 
implicit def evenNPlusTwo[N <: Num](implicit evenN: EvenNum[N]) = new EvenNum[Succ[Succ[N]]] {} 

Avec cela, vous pouvez appliquer que certaines opérations ne pouvait se faire lorsque la preuve implicite pourrait fournir:

def operationForEvenSizeList[T, N <: Num](list: List[T, N])(implicit ev: EvenNum[N]) = { 
    // do something with list of even length 
} 

operationForEvenSizeList(1 :: 2 :: Nil) // ok 
operationForEvenSizeList(1 :: 2 :: 3 :: Nil) // compiler error 

Pour autant que je peux dire vrai pouvoir de programmation au niveau du type à Scala apparaît lorsque vous commencez à utiliser des implicits pour créer de nouveaux types: ceux que vous pourriez utiliser pour la preuve implicite, la dérivation de classe de type et la suppression de certains éléments structurels.

Une bibliothèque qui aide beaucoup avec la programmation générique est Shapeless. Je crois que ce sera une chose amusante pour vous de travailler avec, une fois que vous ferez un exercice ou deux avec une dérivation de type classe avec implicits. Retour à votre code: vous pourriez fournir quelques implicits qui généreraient et fourniraient des instances de votre classe pour vous. De plus, en plus de créer une nouvelle classe, ce code ferait aussi quelque chose d'autre, par ex. combinez des listes d'éléments que vous ajouteriez dans ces classes, ou fournissez la conversion de PeanoNumType en Int, ou ajoutez quelques prédicats fonctionnant dans la compilation, etc. Sky est la limite.