Tapez classes Scala
-
01-10-2019 - |
Question
Avoir une formation en Haskell Je tente actuellement de se familiariser avec Scala.
Je rencontrais des problèmes en essayant de traduire un petit langage d'expression extensible de Haskell en Scala. La question sous-jacente de l'écriture d'un type de données qui est extensible avec les nouvelles données variantes et des opérations est communément appelé l'expression problème .
Ma solution originale en Haskell utilise des classes de type et les déclarations d'exemple avec des contraintes. La base de mon expression est définie comme suit:
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)
Alors, j'ai une des données d'extension qui ajoute la multiplication:
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)
Jetons un joli imprimante comme une extension opérationnelle:
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) ++ ")"
Et enfin, dans le quatrième module, les deux extensions indépendantes peuvent être combinées:
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) ++ ")"
Maintenant, pour mon problème: est habituellement en cours de haskell sont convertis au concept-modèle avec implicits à Scala. C'est de savoir jusqu'où je suis arrivé:
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])
Ici, je suis coincé avec la mise en œuvre de l'objet implicite Plus. Comment puis-je déclarer un objet implicite avec des paramètres et des contraintes de type?
Je sais qu'il ya d'autres solutions pour le problème d'expression dans Scala, je suis cependant intéressé par cette version en particulier.
Merci à vous tous d'avoir lu ma question assez longue.
La solution
Première tentative (imparfaite):
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:
Ce qui précède n'est pas assez puissant (vous ne pouvez pas ajouter deux expressions Plus
), et la nécessité de témoin implicite de ne pas être défini à l'intérieur de la classe affaire Plus
... essayez ceci:
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:
Voici une (peut-être) version légèrement plus idiomatiques:
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)
}
Autres conseils
Voici la mise en œuvre complète d'expression Problème dans scala en utilisant des classes de type
trait Exp
case class Lit(value: Int) extends Exp
case class Add[A <: Exp, B <: Exp](left: A, right: B) extends Exp
classe de type Eval et implémentations implicites
//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)
}
permet d'étendre la solution en ajoutant un nouveau type appelé 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)
}
Maintenant, les expressions peuvent être évaluées comme ceci
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))
}
permet d'étendre le système en ajoutant une nouvelle impression de l'opération
//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) }
}
Définir une nouvelle méthode pour imprimer les expressions
def printExpressions[A <: Exp](exp : A)(implicit p : Print[A]) = {
p.print(exp)
}
Mettre à jour la principale méthode pour imprimer l'expression
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))
}
solution entière peut être exécutée en enroulant le code à l'intérieur d'un objet.