2016-02-29 16 views
5

Un typeclass esempio tratto dalla programmazione Scala libro:Diversi modi di costruire typeclass in Scala?

case class Address(street: String, city: String) 
case class Person(name: String, address: Address) 

trait ToJSON { 
    def toJSON(level: Int = 0): String 

    val INDENTATION = " " 
    def indentation(level: Int = 0): (String,String) = 
    (INDENTATION * level, INDENTATION * (level+1)) 
} 

implicit class AddressToJSON(address: Address) extends ToJSON { 
    def toJSON(level: Int = 0): String = { 
    val (outdent, indent) = indentation(level) 
    s"""{ 
     |${indent}"street": "${address.street}", 
     |${indent}"city": "${address.city}" 
     |$outdent}""".stripMargin 
    } 
} 

implicit class PersonToJSON(person: Person) extends ToJSON { 
    def toJSON(level: Int = 0): String = { 
    val (outdent, indent) = indentation(level) 
    s"""{ 
     |${indent}"name": "${person.name}", 
     |${indent}"address": ${person.address.toJSON(level + 1)} 
     |$outdent}""".stripMargin 
    } 
} 

val a = Address("1 Scala Lane", "Anytown") 
val p = Person("Buck Trends", a) 

println(a.toJSON()) 
println() 
println(p.toJSON()) 

Il codice funziona bene, ma io sono sotto l'impressione (da alcuni post del blog) che typeclasses sono in genere fatto in questo modo a Scala:

// src/main/scala/progscala2/implicits/toJSON-type-class.sc 

case class Address(street: String, city: String) 
case class Person(name: String, address: Address) 

trait ToJSON[A] { 
    def toJSON(a: A, level: Int = 0): String 

    val INDENTATION = " " 
    def indentation(level: Int = 0): (String,String) = 
    (INDENTATION * level, INDENTATION * (level+1)) 
} 

object ToJSON { 
    implicit def addressToJson: ToJSON[Address] = new ToJSON[Address] { 
    override def toJSON(address: Address, level: Int = 0) : String = { 
      val (outdent, indent) = indentation(level) 
      s"""{ 
       |${indent}"street": "${address.street}", 
       |${indent}"city": "${address.city}" 
       |$outdent}""".stripMargin 
    } 
    } 
    implicit def personToJson: ToJSON[Person] = new ToJSON[Person] { 
    override def toJSON(a: Person, level: Int): String = { 
      val (outdent, indent) = indentation(level) 
      s"""{ 
       |${indent}"name": "${a.name}", 
       |${indent}"address": ${implicitly[ToJSON[Address]].toJSON(a.address, level + 1)} 
       |$outdent}""".stripMargin 
    } 
    } 
    def toJSON[A](a: A, level: Int = 0)(implicit ev: ToJSON[A]) = { 
    ev.toJSON(a, level) 
    } 
} 


val a = Address("1 Scala Lane", "Anytown") 
val p = Person("Buck Trends", a) 


import ToJSON.toJSON 
println(toJSON(a)) 
println(toJSON(p)) 

Quale modo è migliore o più corretto? Qualsiasi intuizione è gradita.

+0

Si potrebbe chiedere, forse questa domanda nella comunità dei programmatori: http://programmers.stackexchange.com/ – ManoDestra

+1

@ManoDestra - Questo sarebbe probabilmente chiuso programmatori principalmente come base di opinione. Molte (la maggior parte) domande di questo tipo si riducono a una risposta di "scegliere un approccio ed essere coerenti". – GlenH7

+0

Vero. Sembra più una domanda specifica per la programmazione piuttosto che una solida implementazione. – ManoDestra

risposta

18

È un tratto chiamare la prima "classe tipo" a tutti (anche se non è come questi termini sono standardizzati, e anche la tua seconda versione di Scala più idiomatica differisce in molti modi importanti, ad esempio dalle classi di tipi in Haskell).

Una delle proprietà delle classi tipo che vorrei prendere in considerazione di definizione è che ti permettono di vincolare i tipi generici. Scala fornisce una sintassi speciale per supportarlo sotto forma di limiti di contesto, quindi posso scrivere ad es. il seguente:

import io.circe.Encoder 

def foo[A: Numeric: Encoder](a: A) = ... 

Questo vincola il tipo A di avere sia Numeric e Encoder istanze.

Questa sintassi non è disponibile per il primo ToJSON e dovresti utilizzare invece parametri di conversione della vista (ora deprecati) o impliciti impliciti.

Esistono anche molti tipi di operazioni che non possono essere fornite dal primo stile ToJSON. Ad esempio, supponiamo che abbiamo un Monoid che utilizza la codifica standard di Scala di classi di tipo:

trait Monoid[A] { 
    def empty: A 
    def plus(x: A, y: A): A 
} 

E abbiamo voluto tradurre in primo stile, dove abbiamo un unparametrized Monoid tratto che sarà il bersaglio di conversioni implicite da tipi che vogliamo essere in grado di trattare come monoidali. Siamo completamente fuori di fortuna, dal momento che non abbiamo un parametro di tipo che possiamo fare riferimento alle nostre empty e plus firme.

Un altro argomento: le classi di tipi nella libreria standard (Ordering, CanBuildFrom, ecc.) Utilizzano il secondo stile, così come la maggior parte delle librerie Scala di terze parti che incontrerete.

In breve, non utilizzare mai la prima versione. Sarà funzionare solo quando si hanno solo le operazioni della forma A => Whatever (per un po 'di cemento Whatever), non ha un comodo supporto sintattico, e non è generalmente considerata idiomatica dalla comunità.

Problemi correlati