Typklassen in Scala
-
01-10-2019 - |
Frage
einen Hintergrund in Haskell Ich Having ist derzeit versucht, mit Scala vertraut zu machen.
Ich traf einige Probleme versuchen, eine kleine, erweiterbare Ausdruckssprache von Haskell in Scala zu übersetzen. Das zugrunde liegende Problem einen Datentyp zu schreiben, die mit den beiden neuen erweiterbar ist daten Varianten und Operationen ist allgemein bekannt als der Ausdruck Problem .
Meine ursprüngliche Lösung in Haskell verwendet Typklassen und Instanzdeklarationen mit Einschränkungen. Die Basis meines Ausdrucks wird wie folgt definiert:
module Expr where
class Expr e where
eval :: e -> Integer
data Lit = Lit Integer
instance Expr Lit where
eval (Lit l) = l
data Plus a b = (Expr a, Expr b) => Plus a b
instance (Expr a, Expr b) => Expr (Plus a b) where
eval (Plus x y) = (eval x) + (eval y)
Dann habe ich eine Daten-Erweiterung, die Multiplikation fügt hinzu:
module ExprWithMul where
import Expr
data Mul a b = (Expr a, Expr b) => Mul a b
instance (Expr a, Expr b) => Expr (Mul a b) where
eval (Mul x y) = (eval x) * (eval y)
Lassen Sie uns nehmen einen ziemlich-Drucker als Betriebserweiterung:
module FormatExpr where
import Expr
class (Expr t) => FormatExpr t where
format :: t -> String
instance FormatExpr Lit where
format (Lit l) = show l
instance (FormatExpr a, FormatExpr b) => FormatExpr (Plus a b) where
format (Plus x y) = "(" ++ (format x) ++ "+" ++ (format y) ++ ")"
Und schließlich im vierten Modul die beiden unabhängigen Erweiterungen kombiniert werden können:
module FormatExprWithMult where
import FormatExpr
import ExprWithMul
instance (FormatExpr a, FormatExpr b) => FormatExpr (Mul a b) where
format (Mul x y) = "(" ++ (format x) ++ "*" ++ (format y) ++ ")"
Jetzt für mein Problem: In der Regel Klassen von Haskell sind das mit implicits in Scala Konzept-Muster übersetzt. Dies ist, wie weit ich habe:
abstract class Expr[A] { // this corresponds to a type class
def eval(e:A): Int;
}
case class Lit(v: Int)
implicit object ExprLit extends Expr[Lit] {
def eval(e: Lit) = x.v;
}
case class Plus[A,B] (e1: A, e2: B) (implicit c1: Expr[A], c2: Expr[B])
Hier bin ich mit der Umsetzung der impliziten Objekt für Plus-stecken. Wie erkläre ich ein implizites Objekt mit Typ-Parametern und Einschränkungen?
Ich weiß, dass es andere Lösung für das Expression Problem in Scala, ich aber in dieser Version besonders interessiert.
Vielen Dank an all meine etwas längere Frage zu lesen.
Lösung
Erster Versuch (fehlerhaft):
case class Plus[A,B] (e1: A, e2: B) (implicit c1: Expr[A], c2: Expr[B]) {
implicit object ExprPlus extends Expr[Plus[A, B]] {
def eval(p:Plus[A, B]) = c1.eval(p.e1) + c2.eval(p.e2)
}
}
Edit 1:
Das oben nicht stark genug ist (man kann nicht zwei Plus
Ausdrücke hinzufügen), und die implizite Zeugnis muss nicht innerhalb der Plus
Fall Klasse definiert werden ... versuchen Sie stattdessen:
case class Plus[A,B] (e1: A, e2: B) (implicit val c1: Expr[A], c2: Expr[B])
implicit def ExprPlus[A, B](implicit c1: Expr[A], c2: Expr[B]) =
new Expr[Plus[A, B]] {
def eval(p:Plus[A, B]) = c1.eval(p.e1) + c2.eval(p.e2)
}
Edit 2:
Hier ist eine (vielleicht) etwas mehr idiomatische Version:
case class Plus[A: Expr, B: Expr] (e1: A, e2: B)
implicit def ExprPlus[A: Expr, B: Expr] = new Expr[Plus[A, B]] {
def eval(p:Plus[A, B]) = implicitly[Expr[A]].eval(p.e1) +
implicitly[Expr[B]].eval(p.e2)
}
Andere Tipps
Dies ist die komplette Umsetzung des Expression Problems in scala mit Typklassen
trait Exp
case class Lit(value: Int) extends Exp
case class Add[A <: Exp, B <: Exp](left: A, right: B) extends Exp
Eval Typklasse und implizite Implementierungen
//type class
trait Eval[E] {
def eval(e: E): Int
}
implicit def litEval = new Eval[Lit] {
def eval(l: Lit) = l.value
}
implicit def addEval[A <: Exp, B <: Exp](implicit e1: Eval[A], e2: Eval[B]) = new Eval[Add[A, B]] {
def eval(a: Add[A, B]) = e1.eval(a.left) + e2.eval(a.right)
}
Hier können Sie die Lösung erweitern, indem neue Art Zugabe genannt Mult
case class Mult[A <: Exp, B <: Exp](left: A, right: B) extends Exp
implicit def mulEval[A <: Exp, B <: Exp](implicit e1: Eval[A], e2: Eval[B]) = new Eval[Mult[A, B]] {
def eval(m : Mult[A, B]) = e1.eval(m.left) * e2.eval(m.right)
}
Nun können die Ausdrücke wie diese ausgewertet werden
def expressionEvaluator[A <: Exp](exp: A)(implicit e : Eval[A]) = {
e.eval(exp)
}
def main(args: Array[String]): Unit = {
// (3 + 4) * 7
val exp1 = Mult(Add(Lit(3), Lit(4)), Lit(7))
println("" + expressionEvaluator(exp1))
}
Hier können Sie das System erweitern, indem ein neuen Betrieb Druck Hinzufügen
//type class
trait Print[P] {
def print(p: P): Unit
}
implicit def litPrint = new Print[Lit] {
def print(l: Lit) = Console.print(l.value)
}
implicit def addPrint[A <: Exp, B <: Exp](implicit p1: Print[A], p2 : Print[B]) = new Print[Add[A, B]] {
def print(a : Add[A, B]) = { p1.print(a.left); Console.print(" + "); p2.print(a.right); }
}
implicit def mulPrint[A <: Exp, B <: Exp](implicit p1: Print[A], p2: Print[B]) = new Print[Mult[A, B]] {
def print(m : Mult[A, B]) = { p1.print(m.left); Console.print(" * "); p2.print(m.right) }
}
Definieren eine neue Methode, die Ausdrücke drucken
def printExpressions[A <: Exp](exp : A)(implicit p : Print[A]) = {
p.print(exp)
}
Aktualisieren Sie die Hauptmethode um den Ausdruck zu drucken
def main(args: Array[String]): Unit = {
val exp1 = Mult(Add(Lit(3), Lit(4)), Lit(7))
print("Expression : ")
printExpressions(exp1)
print(", Evaluated to : " + expressionEvaluator(exp1))
}
Entire Lösung kann durch Umwickeln Sie den Code innerhalb eines Objekts ausgeführt werden.