Типы занятий в Scala - PullRequest
       39

Типы занятий в Scala

12 голосов
/ 09 сентября 2010

Имея опыт работы с Haskell, я сейчас пытаюсь познакомиться со Scala.

Я столкнулся с некоторыми проблемами при попытке перевести небольшой расширяемый язык выражений из Haskell в Scala.Основная проблема написания типа данных, расширяемого как новыми вариантами данных, так и операциями, обычно известна как проблема выражения .

Мое оригинальное решение в Haskell использует классы типов и объявления экземпляров.с ограничениями.Основа моего выражения определяется следующим образом:

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)

Затем у меня есть одно расширение данных, которое добавляет умножение:

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)

Давайте возьмем в качестве рабочего расширения симпатичный принтер:

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) ++ ")"

И, наконец, в четвертом модуле можно объединить два независимых расширения:

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) ++ ")"

Теперь для моей проблемы: обычно классы типов из haskell переводятся в концепцию-шаблон с замешательствами в Scala.Вот как далеко я дошёл:

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

Здесь я застрял в реализации неявного объекта для Plus.Как мне объявить неявный объект с параметрами и ограничениями типа?

Я знаю, что в Scala есть и другие решения проблемы с выражениями, однако мне особенно интересна эта версия.

СпасибоВы все за то, что прочитали мой довольно длинный вопрос.

Ответы [ 2 ]

15 голосов
/ 09 сентября 2010

Первая попытка (ошибочная):

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

Редактировать 1:

Вышеприведенное не является достаточно мощным (вы не можете добавить два Plus выражения), и неявного свидетеля не нужно определять внутри * Plus case-класса ... попробуйте это вместо:

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

Редактировать 2:

Вот (возможно) немного более идиоматическая версия:

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)
}
4 голосов
/ 24 сентября 2015

Вот полная реализация задачи Expression в Scala с использованием классов Type

  trait Exp
  case class Lit(value: Int) extends Exp
  case class Add[A <: Exp, B <: Exp](left: A, right: B) extends Exp

Класс Eval и неявные реализации

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

Давайте расширим решение, добавив новый тип с именем 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)
}

Теперь выражения можно оценивать так:

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

Позволяет расширить систему путем добавления новой операции. Печать

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

Определить новый метод для печати выражений

def printExpressions[A <: Exp](exp : A)(implicit p : Print[A]) = {
    p.print(exp)
}

Обновление основного метода для печати выражения

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

Полное решение можно выполнить, обернув код внутри объекта.

...