【问题标题】:Implicit conversion in monadic for comprehension in ScalaScala 中理解的单子隐式转换
【发布时间】:2016-11-08 15:23:02
【问题描述】:

假设我有以下功能:

case class ErrA(msg: String)

case class ErrB(msg: String)

def doA(): Either[ErrA, Int] = Right(2)

def doB(): Either[ErrB, Int] = Right(3)

ErrA 和 ErrB 是不相关的类型,实际上并未在本示例之外的同一文件中声明。它们不能轻易地从通用类型继承。

我想介绍一种新类型,它可以代表两种错误类型:

sealed trait ErrAOrB

case class ErrAWrapper(e: ErrA) extends ErrAOrB

case class ErrBWrapper(e: ErrB) extends ErrAOrB

然后使用 for 推导式编写以下函数:

def doAplusB(): Either[ErrAOrB, Int] =
  for (a <- doA().right;
       b <- doB().right) yield a + b

有没有办法让编译器将那些特定的Either 类型隐式转换为常见的Either[ErrAOrB, Int] 类型?

例如:

implicit def wrapA[T](res: Either[ErrA, T]): Either[ErrAOrB, T] = res.left.map(ErrAWrapper(_))

implicit def wrapB[T](res: Either[ErrB, T]): Either[ErrAOrB, T] = res.left.map(ErrBWrapper(_))

但这不起作用,因为隐式转换仅应用于 for 理解中的最终表达式,然后编译器必须将其绑定到 doA 并且由于类型 ErrA 和 ErrAOrB 不相关最好的办法是使泛型有意义是使用与预期类型不兼容的Object。

【问题讨论】:

  • 只是要注意在 Scala 中不推荐使用隐式视图 - 使用隐式类代替,所以它可能类似于 doA().right.wrap。想象一下人们阅读您的代码并想知道Either[ErrA, Int] 是如何变成ErrAOrB 的?没有 IDE 提示,所以没有找到你的 wrapAimplicits 的好方法

标签: scala monads implicit-conversion for-comprehension implicits


【解决方案1】:

在 Scala 中不建议使用隐式视图,正如您在定义 wrapA/wrapB 时编译器的功能警告中所见。

即使您通过在Either.RightProjection 而不是Either 上定义隐式视图来抓住机会 - 想象一下人们阅读您的代码并想知道Either[ErrA, Int] 是如何变成Either[ErrAOrB, Int] 的?没有 IDE 提示,所以没有找到 wrapA 隐含的好方法

所以,改用隐式类:

implicit class WrapA[T](x: Either[ErrA, T]) {
  def wrap = x.left.map(ErrAWrapper(_)): Either[ErrAOrB, T]
}

implicit class WrapB[T](x: Either[ErrB, T]) {
  def wrap = x.left.map(ErrBWrapper(_)): Either[ErrAOrB, T]
}

scala> def doAplusB(): Either[ErrAOrB, Int] =
     |   for {
     |      a <- doA().wrap.right
     |      b <- doB().wrap.right
     |   } yield a + b
doAplusB: ()Either[ErrAOrB,Int]

附:如果您的计算是独立的(如您的示例中所示),您不需要 monad 进行 + 操作 - 应用就足够了。例如看一下cats.data.Validated 或scalaz.Validation。


回答是否可以欺骗 scalac:

implicit def wrapBB[T](res: Either.RightProjection[ErrB, T]): Either.RightProjection[ErrAOrB, T] = res.e.left.map(ErrBWrapper(_)).right

implicit def wrapAA[T](res: Either.RightProjection[ErrA, T]): Either.RightProjection[ErrAOrB, T] = res.e.left.map(ErrAWrapper(_)).right

def doAplusB(): Either[ErrAOrB, Int] =
  for (a <- doA().right: Either.RightProjection[ErrAOrB, Int] ;
       b <- doB().right: Either.RightProjection[ErrAOrB, Int]) yield a + b

但这需要Either.RightProjection类型归属,但如果你有一些并行分配(应用风格而不是理解),我相信具有临时超类型的东西可以工作。

甚至(定义你的wrapB):

implicit def wrapAA[T] ...
implicit def wrapBB[T] ...

implicit def wrapB[T](res: Either[ErrB, T]): Either[ErrAOrB, T] = res.left.map(ErrBWrapper(_))

def doAplusB(): Either[ErrAOrB, Int] =
  for (a <- doA().right:  Either.RightProjection[ErrAOrB, Int];
       b <- doB().right) yield a + b

原因是扩展为:

doA().right.flatMap(a => doB().right.map(b => a + b))

flatMap 要求返回 RightProjection,但 map 不需要。

【讨论】:

  • 我完全同意显式优于隐式,我只是好奇是否有可能在没有任何显式转换的情况下滥用隐式来实现这一点。实际操作相互依赖。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-07-21
  • 1970-01-01
  • 2018-09-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-30
相关资源
最近更新 更多