【问题标题】:How to express this existential type from Haskell in Scala?如何在 Scala 中从 Haskell 中表达这种存在类型?
【发布时间】:2017-03-30 12:18:41
【问题描述】:

我正在尝试将expression problem 的基于类型类的解决方案的this Haskell implementation 适应Scala。我当前的代码如下。我在 Scala 中表达存在类型 Exp 时遇到问题。

我怎样才能达到同样的目的?

object ExpressionProblem {

  // class Eval a where
  //   eval :: a -> Int
  trait Eval[A] {
    def eval(expr: A): Int
  }

  // data Exp = forall t. Eval t => Expr t
  sealed abstract class Exp
  case class Expr[T](val e: T)(implicit ev: Eval[T]) extends Exp

  // instance Eval Exp where
  //   eval (Expr e) = eval e
  implicit val exprInstance = new Eval[Exp] {
    def eval(expr: Exp) = expr match { case Expr(e, ev) => ev.eval(e) }
  }
  //                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  //                                       here is the problem

  // data BaseExp = Const Int | Add Exp Exp | Mul Exp Exp
  sealed abstract class BaseExpr
  case class Const(c: Int) extends BaseExpr
  case class Add(lhs: Exp, rhs: Exp) extends BaseExpr
  case class Mul(lhs: Exp, rhs: Exp) extends BaseExpr

  // instance Eval BaseExp where
  //   eval (Const n) = n
  //   eval (Add a b) = eval a + eval b
  //   eval (Mul a b) = eval a * eval b
  implicit val baseExprInstance = new Eval[BaseExpr] {
    def eval(baseExpr: BaseExpr)(implicit e: Eval[Exp]): Int =
      baseExpr match {
        case Const(c)      => c
        case Add(lhs, rhs) => e.eval(lhs) + e.eval(rhs)
        case Mul(lhs, rhs) => e.eval(lhs) + e.eval(rhs)
      }
  }

  // TODO: Is there an easier way to make all of them implicitly convertible?
  //
  // The following doesn't seem to work:
  //
  //    implicit def baseExprToExp[T <: BaseExpr](t: T): Exp = Expr[BaseExpr](t)
  //
  implicit def constToExp(c: Const): Exp = Expr[BaseExpr](c)
  implicit def addToExp(a: Add): Exp = Expr[BaseExpr](a)
  implicit def mulToExp(m: Mul): Exp = Expr[BaseExpr](m)

  ///////////////////////////////////////////////
  // Possibly in another module/lib.
  ///////////////////////////////////////////////

  // data SubExp = Sub Exp Exp
  case class SubExpr(val lhs: Exp, val rhs: Exp)

  // instance Eval SubExp where
  //   eval (Sub a b) = eval a - eval b
  implicit val subExprInstance = new Eval[SubExpr] {
    def eval(subExpr: SubExpr)(implicit e: Eval[Exp]): Int =
      e.eval(subExpr.lhs) - e.eval(subExpr.rhs)
  }

  // Make it implicitly convertible to Exp.
  implicit def subExprToExp(s: SubExpr): Exp = Expr(s)

  object Test {
    val exprs: List[Exp] = List(
      SubExpr(Const(10), Const(3)),
      Add(Const(1), Const(1))
    )
  }

} // ExpressionProblem

编辑:同时我找到了这个relevant StackOverflow 的答案,并根据它调整了我的代码。

import scala.language.implicitConversions

object ExpressionProblem {

  // class Eval a where
  //   eval :: a -> Int
  trait Eval[A] {
    def eval(expr: A): Int
  }

  //////////////////////////////////////////
  // HERE'S THE MAGIC
  //
  // data Expr = forall t. Eval t => Expr t
  trait Expr {
    type T
    val t: T
    val evalInst: Eval[T]
  }

  object Expr {
    def apply[T0 : Eval](t0: T0) = new Expr {
      type T = T0
      val t = t0
      val evalInst = implicitly[Eval[T]]
    }
  }

  // Default boxing is needed
  implicit def box[T : Eval](t: T) = Expr(t)

  // instance Eval Expr where
  //   eval (Expr e) = eval e
  implicit object exprInstance extends Eval[Expr] {
    def eval(expr: Expr) = expr.evalInst.eval(expr.t)
  }

  // data BaseExpr = Const Int | Add Expr Expr | Mul Expr Exp
  sealed abstract class BaseExpr
  case class Const(c: Int) extends BaseExpr
  case class Add(lhs: Expr, rhs: Expr) extends BaseExpr
  case class Mul(lhs: Expr, rhs: Expr) extends BaseExpr

  // instance Eval BaseExpr where
  //   eval (Const n) = n
  //   eval (Add a b) = eval a + eval b
  //   eval (Mul a b) = eval a * eval b
  implicit object baseExprInstance extends Eval[BaseExpr] {
    def eval(baseExpr: BaseExpr): Int =
      baseExpr match {
        case Const(c)      => c
        case Add(lhs, rhs) => exprInstance.eval(lhs) + exprInstance.eval(rhs)
        case Mul(lhs, rhs) => exprInstance.eval(lhs) + exprInstance.eval(rhs)
      }
  }

  // Implicit conversions for all base expressions
  implicit def baseExprToExpr[T <: BaseExpr](t: T): Expr = Expr[BaseExpr](t)

  ///////////////////////////////////////////////
  // Possibly somewhere else (in the future).
  ///////////////////////////////////////////////

  // data SubExpr = Sub Expr Exp
  case class SubExpr(val lhs: Expr, val rhs: Expr)

  // instance Eval SubExpr where
  //   eval (Sub a b) = eval a - eval b
  implicit object subExprInstance extends Eval[SubExpr] {
    def eval(subExpr: SubExpr): Int =
      exprInstance.eval(subExpr.lhs) - exprInstance.eval(subExpr.rhs)
  }

  // NOTE: We don't have to provide an implicit conversion to Expr as the
  // default `box` one suffices.

  object Test {
    val exprs: List[Expr] = List(
      SubExpr(Const(10), Const(3)),
      Add(Const(1), Const(1))
    )
  }

} // ExpressionProblem

【问题讨论】:

    标签: scala haskell typeclass existential-type


    【解决方案1】:

    您的问题是您正在使用提取器 (unapply),但忽略了隐式默认情况下不会作为 unapply 的一部分公开的事实。

    所以这一行:

    def eval(expr: Exp) = expr match { case Expr(e, ev) => ev.eval(e) }
    

    没有case Expr(e, ev),只有case Expr(e),因为只暴露了e。要么编写自定义提取器,要么找到不同的方法。

    Scala 确实提供了形式的存在类型:

    Expr[T] forSome { type T})
    

    有一个可用的速记类型表示法:Expr[_]

    您的代码还有一些问题:

    如果您在eval 上定义implicit,则所有实现者都必须使用该特定签名实现eval 函数,您不能使用不包含签名中隐含的实现来覆盖eval就像你在里面做的那样。

     implicit val baseExprInstance = new Eval[BaseExpr] {
        def eval(baseExpr: BaseExpr)(implicit e: Eval[Exp]): Int =
          baseExpr match {
            case Const(c)      => c
            case Add(lhs, rhs) => e.eval(lhs) + e.eval(rhs)
            case Mul(lhs, rhs) => e.eval(lhs) + e.eval(rhs)
          }
      }
    

    接下来你需要在Expr 上使T 逆变或使SubExpr 也扩展Exp,这里有问题:

      // instance Eval SubExp where
      //   eval (Sub a b) = eval a - eval b
      implicit val subExprInstance = new Eval[SubExpr] {
        def eval(subExpr: SubExpr)(implicit e: Eval[SubExpr]): Int =
          e.eval(subExpr.lhs) - e.eval(subExpr.rhs)
      }
    

    如果您尝试匹配类型签名,implicit e: Eval[SubExpr] 可以评估 T &gt;: SubExpr 的类型,但您需要的是较低的 Exp 隐含 Eval

    【讨论】:

    • 谢谢!我更新了我的顶帖,请看一下。在新方法中我唯一不喜欢的是我必须删除隐式参数,正如您指出的那样,但我必须每次都明确指定 exprInstance。有什么办法可以避免吗?谢谢!
    • @CălinCruceru 恐怕我手上没有更多时间了,但我会花一些时间学习其中的技巧和常见模式。
    • 我想我可以创建另一个函数,我们将其命名为 evaluate 并将其放在顶部,而不是将 Expr 特征作为 Eval 的实例。在所有被覆盖的evals 的实现中,我会递归调用evaluate
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-04-13
    • 2015-01-22
    • 2018-12-27
    • 2022-01-24
    • 2016-10-25
    • 2013-09-12
    • 1970-01-01
    相关资源
    最近更新 更多