2016-01-20 14 views
5

Questo è il mio secondo tentativo per definire il problema, non riesco a capirlo.Typeclasses ed ereditarietà in scalaz

Voglio essere in grado di definire un tipo algebrico e definire su di esso un semplice typeclass, diciamo Show. In Haskell faccio:

data Tree a = EmptyTree | Node a deriving (Show) 

Ora, se di tipo I EmptyTree - Haskell può mostrare esso, quindi appartiene alla Show.

Ora sto cercando di fare lo stesso a Scala:

sealed abstract class Tree[+T] 
case object EmptyTree extends Tree[Nothing] 
case class Node[T](value: T) extends Tree[T] 

Poi mi definiscono Show intorno ad esso:

implicit def show[T] = Show.showA[Tree[T]] 

posso fare println((EmptyTree : Tree[Int]).show). Ma non posso fare println(EmptyTree.show) (risposta è value show is not a member of object EmptyTree)

devo scrivere supplementari:

implicit class MyShowOps[A, +T <: Tree[A]](t: T) { 
    def showMy(implicit ev: Show[Tree[A]]): String = ev.shows(t) 
} 

E solo allora posso fare println(EmptyTree.showMy)

E ancora non suona corretto, credo o sto cercando di fare una cosa sbagliata e non dovrei applicare Show in questo modo e dovrei usare la mia costruzione solo come Tree[T] o mi manca una costruzione corretta da Scalaz.

risposta

4

La rappresentazione di Scala delle ADT differisce da quella di Haskell in quanto i suoi costruttori hanno i loro tipi. Ciò riguarda in parte l'interoperabilità pratica: l'uso della sottotipizzazione è naturale nella JVM e ha lo both advantages and disadvantages.

Si sta verificando uno degli svantaggi, ovvero che i valori che sono tipizzati staticamente come un tipo di costruttore spesso complica l'inferenza di tipo e la risoluzione implicita.

istanze della classe Type sono staticamente risolti, e nel tuo caso specifico Show non è controvariante, quindi un'istanza per Tree[T] non è un'istanza per EmptyTree.type. La soluzione più idiomatica dal punto di vista Scalaz è quello di fornire costruttori intelligenti che restituiscono il tipo ADT:

import scalaz.Show, scalaz.syntax.show._ 

sealed abstract class Tree[+T] 

object Tree { 
    private[this] case object EmptyTree extends Tree[Nothing] 
    private[this] case class Node[T](value: T) extends Tree[T] 

    val emptyTree: Tree[Nothing] = EmptyTree 
    def node[T](value: T): Tree[T] = Node(value) 

    implicit def show[T]: Show[Tree[T]] = Show.showA[Tree[T]] 
} 

Ora si può scrivere Tree.emptyTree.show.

Si noti che questo problema si verifica anche in contesti ancora più semplici. Ad esempio, supponiamo di voler piegare un elenco con una Option come l'accumulatore:

scala> List(1, 2, 3).foldLeft(Some(0))((acc, i) => acc.map(_ + i)) 
<console>:11: error: type mismatch; 
found : Option[Int] 
required: Some[Int] 
     List(1, 2, 3).foldLeft(Some(0))((acc, i) => acc.map(_ + i)) 
                 ^

Poiché il tipo derivato per Some(0) è Some[Int], non Option[Int], il parametro di tipo che è dedotto per il metodo foldLeft è troppo restrittiva per il risultato dello map.

Sarebbe bello se la libreria standard fornito Option.none e Option.some "costruttori" per casi come questo, ma non è così, in modo che sia necessario mettere un tipo di annotazione sul primo argomento o usare qualcosa come Scalaz di none e some:

scala> import scalaz._, Scalaz._ 
import scalaz._ 
import Scalaz._ 

scala> List(1, 2, 3).foldLeft(some(0))((acc, i) => acc.map(_ + i)) 
res0: Option[Int] = Some(6) 

Nel tuo caso è possibile controllare presumibilmente la definizione ADT, in modo da poter fornire costruttori intelligenti come questo da soli.

+0

Ho visto modelli come 'val emptyTree: Tree [Nothing] ..' in fonti scalaz, ma non riuscivo a capire perché fosse fatto in quel modo. Ora è chiaro per me. C'è ancora qualcosa che non capisco, c'è qualche ragione particolare per cui in "ShowOps" di scalaz non è stato definito come ho fatto io? Sembra più semplice che dover definire i metodi di wrapper come 'emptyTree' e' node [T] ' – Archeg

+0

Il modello di costruzione up-casting risolve molti di questi problemi su tutta la linea in un unico posto, mentre fornire le istanze per i sottotipi avrebbe un impatto su tonnellate di codice. In generale, Scalaz non si occupa del sottotipo (ad esempio la maggior parte delle classi di tipi sono invarianti e le istanze non sono fornite per i sottotipi). –