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
的调用。所以我开始寻找替代方案并找到了多个库:effectful
、monadless
和 each
。我首先尝试了 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 Unapply
:https://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
上。这可能有用,但我没试过。
编辑: 我更新了问题以使其更具描述性。
注:我使用的是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
的调用。所以我开始寻找替代方案并找到了多个库:effectful
、monadless
和 each
。我首先尝试了 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 Unapply
:https://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
上。这可能有用,但我没试过。