2016-02-29 16 views
5

A typeclass Beispiel aus der Programmierung Scala Buch entnommen:Verschiedene Arten Klassen in Scala zu erstellen?

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

Der Code funktioniert gut, aber ich habe den Eindruck (von einigen Blog-Posts), dass typeclasses typischerweise auf diese Weise in scala getan werden:

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

Welcher Weg ist besser oder korrekter? Alle Einblicke sind willkommen.

+0

Sie könnten vielleicht diese Frage in der Programmiergemeinschaft stellen: http://programmers.stackexchange.com/ – ManoDestra

+1

@ManoDestra - Dies würde wahrscheinlich auf Programmierer als in erster Linie auf der Grundlage von Meinungen geschlossen werden. Viele (die meisten) Fragen dieser Art laufen auf die Antwort hinaus: "Wähle einen Ansatz und sei konsistent." – GlenH7

+0

Wahr. Es scheint eher eher eine programmspezifische Frage als eine solide Implementierung zu sein. – ManoDestra

Antwort

18

Es ist eine Strecke, um die erste ToJSON eine "Typklasse" überhaupt zu nennen (obwohl es nicht wie diese Begriffe standardisiert sind, und sogar Ihre zweite, mehr Scala-idiomatische Version unterscheidet sich in vielen wichtigen Arten von zB Klassen in Haskell).

Eine der Eigenschaften von Typklassen, die ich als definitorisch betrachten würde, ist, dass sie generische Typen einschränken können. Scala bietet eine spezielle Syntax, um dies in Form von Kontextgrenzen zu unterstützen, so dass ich z.B. die folgenden:

import io.circe.Encoder 

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

Dies schränkt die Art A haben beide Numeric und Encoder Instanzen.

Diese Syntax ist nicht für die erste ToJSON verfügbar, und Sie müssten stattdessen etwas wie View-Grenzen (jetzt veraltet) oder implizite implizite Konvertierungsparameter verwenden.

Es gibt auch viele Arten von Operationen, die nicht durch die erste ToJSON Art zur Verfügung gestellt werden können. Angenommen, wir Monoid haben, die den Standard Scala Codierung von Typklassen verwendet:

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

Und wollten wir es in den ersten Stil übersetzen, wo wir eine unparametrized Monoid Eigenschaft haben, das das Ziel sein von impliziten Konvertierungen von Typen, die wir monoidal behandeln wollen. Wir haben kein Glück, da wir keinen Typparameter haben, den wir auf unsere Signaturen empty und plus beziehen können.

Ein weiteres Argument: die Typklassen in der Standardbibliothek (Ordering, CanBuildFrom, usw.) verwenden alle den zweiten Stil, ebenso wie die große Mehrheit der Scala-Bibliotheken von Drittanbietern, auf die Sie stoßen.

Kurz gesagt, verwenden Sie niemals die erste Version. Es wird nur funktionieren, wenn Sie nur Operationen des Formulars A => Whatever (für einige konkrete Whatever) haben, hat keine nette syntaktische Unterstützung und wird nicht allgemein als idiomatisch von der Community betrachtet.

Verwandte Themen