Scala 在有效宏中隐式转换为 monadic 值

Scala implicit conversion to monadic value within effectful macro

编辑: 我更新了问题以使其更具描述性。

注:我使用的是Scala 2.11编译器,因为那是LMS教程项目使用的编译器版本。

我正在将用 Haskell 编写的 DSL 移植到 Scala。 DSL 是一种命令式语言,所以我使用带有 do-notation 的 monads,即 WriterT [Stmt] (State Label) a。我在将它移植到 Scala 时遇到了问题,但通过使用 ReaderWriterState monad 并仅将 Unit 用于 Reader 组件来解决它。然后我开始寻找 Haskell 中的 do-notation 的替代方法。 For-comprehensions 应该是 Scala 中的这种替代方法,但它们是针对序列量身定制的,例如无法模式匹配元组,它会插入对 filter 的调用。所以我开始寻找替代方案并找到了多个库:effectfulmonadlesseach。我首先尝试了 effectful,这正是我想要实现的,我什至更喜欢它而不是 Haskell 的 do-notation,并且它与来自 [=22= 的 ReaderWriterState monad 一起工作得很好] 我一直在用。在我的 DSL 中,我有像 Drop()(一个案例 class)这样的动作,我希望能够直接将其用作语句。我希望为此使用隐式,但因为 effectful! 方法(或 monadless 等同于此)太笼统了,我无法让 Scala 自动转换我的 Action case classes 到 Stmt 类型的东西(ReaderWriterState 即 returns Unit)。

所以如果不是隐含的,是否有不同的方法来实现它?

Main.passes2 所示,我确实找到了一个我不介意使用的解决方法,但我很好奇我是否偶然发现了语言的某些限制,或者仅仅是我缺乏使用经验斯卡拉

使用 Main.fails1 我将收到以下错误消息:找不到隐式:scalaz.Unapply[scalaz.Monad, question.Label]。无法将类型 question.Label 取消应用到类型 M[_] 的类型构造函数中,该类型构造函数 class 由类型 class scalaz.Monad 化。检查类型 class 是否通过编译 implicitly[scalaz.Monad[type constructor]] 定义,并查看对象 Unapply 中的隐式,它仅涵盖常见类型 'shapes.'

它来自 ScalaZ Unapplyhttps://github.com/scalaz/scalaz/blob/d2aba553e444f951adc847582226d617abae24da/core/src/main/scala/scalaz/Unapply.scala#L50

使用 Main.fails2 我会得到:值 ! 不是 question.Label

的成员

我认为这只是编写缺少的隐式定义的问题,但我不太确定 Scala 要我编写哪个。

我的 build.sbt 最重要的部分是版本:

scalaVersion := "2.11.2",

以及依赖项:

libraryDependencies += "org.scala-lang" % "scala-compiler" % "2.11.2",
libraryDependencies += "org.scala-lang" % "scala-library" % "2.11.2",
libraryDependencies += "org.scala-lang" % "scala-reflect" % "2.11.2",
libraryDependencies += "org.pelotom" %% "effectful" % "1.0.1",

这是相关代码,包含我尝试过的东西以及 运行 这些东西所需的代码:

package question

import scalaz._
import Scalaz._

import effectful._

object DSL {
  type GotoLabel = Int
  type Expr[A] = ReaderWriterState[Unit, List[Action], GotoLabel, A]
  type Stmt = Expr[Unit]

  def runStmt(stmt: Stmt, startLabel: GotoLabel): (Action, GotoLabel) = {
    val (actions, _, updatedLabel) = stmt.run(Unit, startLabel)
    val action = actions match {
      case List(action) => action
      case _ => Seq(actions)
    }
    (action, updatedLabel)
  }

  def runStmt(stmt: Stmt): Action = runStmt(stmt, 0)._1

  def getLabel(): Expr[GotoLabel] =
    ReaderWriterState((_, label) => (Nil, label, label))

  def setLabel(label: GotoLabel): Stmt =
    ReaderWriterState((_, _) => (Nil, Unit, label))

  implicit def actionStmt(action: Action): Stmt =
    ReaderWriterState((_, label) => (List(action), Unit, label))
}

import DSL._

final case class Label(label: String) extends Action
final case class Goto(label: String) extends Action
final case class Seq(seq: List[Action]) extends Action
sealed trait Action {
  def stmt(): Stmt = this
}

object Main {
  def freshLabel(): Expr[String] = effectfully {
    val label = getLabel.! + 1
    setLabel(label).!
    s"ants-$label"
  }

  def passes0() =
    freshLabel()
      .flatMap(before => Label(before))
      .flatMap(_ => freshLabel())
      .flatMap(after => Label(after));

  def passes1() = effectfully {
    unwrap(actionStmt(Label(unwrap(freshLabel()))))
    unwrap(actionStmt(Label(unwrap(freshLabel()))))
  }

  def fails1() = effectfully {
    unwrap(Label(unwrap(freshLabel())))
    unwrap(Label(unwrap(freshLabel())))
  }

  def pasess2() = effectfully {
    Label(freshLabel.!).stmt.!
    Label(freshLabel.!).stmt.!
  }

  def fails2() = effectfully {
    Label(freshLabel.!).!
    Label(freshLabel.!).!
  }

  def main(args: Array[String]): Unit = {
    runStmt(passes0())
  }
}

问题质量

我想从抱怨问题质量开始。您几乎没有提供有关您要实现的目标的文本描述,然后只是向我们展示了一堵代码墙,但没有明确引用您的依赖项。这远不能算作 Minimal, Complete, and Verifiable example。通常,如果您提供一个易于理解和重现的明确问题,您将有更多机会获得一些答案。

回到正题

当你写类似

的东西时
unwrap(Label(unwrap(freshLabel())))

你对 Scala 编译器的要求太多了。特别是 unwrap 只能解开包裹在某些 Monad 中的东西,但 Label 不是 monad。这不仅仅是没有 Monad[Label] 实例的事实,而且它在结构上不适合杀死你的事实。简单来说,ScalaZ Unapply 的实例是一个对象,它允许您将应用的泛型类型 MonadType[SpecificType](或其他 FunctorType[SpecificType])拆分为 "unapplied"/"partially-applied" MonadType[_]SpecificType 即使(如您的情况)MonadType[_] 实际上是像 ReaderWriterState[Unit, List[Action], GotoLabel, _] 这样复杂的东西。所以错误说没有已知的方法可以将 Label 拆分为一些 MonadType[_]SpecifictType。您可能期望您的 implicit actionStmt 会自动将 Label 转换为 Statement 但这一步对于 Scala 编译器来说太多了,因为它无法工作,它也意味着拆分复合类型。请注意,这种转换对于编译器而言远非显而易见,因为 unwrap 本身就是一个可以处理任何 Monad 的通用方法。实际上在某种意义上 ScalaZ 需要 Unapply 正是因为编译器不能自动做这些事情。不过,如果您通过指定泛型类型来帮助编译器一点点,它就可以完成剩下的工作:

def fails1() = effectfully {
  // fails
  // unwrap(Label(unwrap(freshLabel()))) 
  // unwrap(Label(unwrap(freshLabel())))

  // works
  unwrap[Stmt](Label(unwrap(freshLabel())))
  unwrap[Stmt](Label(unwrap(freshLabel())))
}

还有另一种可能的解决方案,但这是一个非常肮脏的 hack:您可以推出自定义 Unapply 来说服编译器 Label 实际上与 Expr[Unit] 相同可以拆分为 Expr[_]Unit:

  implicit def unapplyAction[AC <: Action](implicit TC0: Monad[Expr]): Unapply[Monad, AC] {
    type M[X] = Expr[X]
    type A = Unit
  } = new Unapply[Monad, AC] {
    override type M[X] = Expr[X]
    override type A = Unit

    override def TC: Monad[Expr] = TC0

    // This can't be implemented because Leibniz really witness only exactly the same types rather than some kind of isomorphism
    // Luckily effectful doesn't use leibniz implementation         
    override def leibniz: AC === Expr[Unit] = ???
  }

这是一个肮脏的 hack 的明显原因是 Label 实际上与 Expr[Unit] 不同,你可以通过无法实现 leibniz 在你的 Unapply 中。无论如何,如果你导入 unapplyAction,即使你原来的 fails1 也会编译和工作,因为 effectful 没有在里面使用 leibniz

至于您的 fails2,我认为您无法通过任何简单的方式使其工作。可能您可以尝试的唯一方法是创建另一个宏,该宏将在 action(或其隐式包装器)上将对 ! 的就地调用转换为 effectful!action.stmt 上。这可能有用,但我没试过。