monadic 中的隐式转换以在 Scala 中理解
Implicit conversion in monadic for comprehension in Scala
假设我有以下功能:
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 comprehension 编写以下函数:
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 中不推荐使用隐式视图,从定义 wrapA
/wrapB
时编译器的功能警告中可以看出。
即使您通过 Either.RightProjection
而不是 Either
定义隐式视图来抓住机会 - 想象一下人们阅读您的代码并想知道 Either[ErrA, Int]
是如何变成 Either[ErrAOrB, Int]
?没有 IDE 提示,所以没有找到 wrapA 隐式
的好方法
因此,请改用隐式 class:
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]
P.S。如果您的计算是独立的(如您的示例),则 +
操作不需要 monad - Applicative 就足够了。例如,看看 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
类型归属,但如果你有一些并行赋值(应用式而不是理解式),我相信具有 ad-hoc 超类型的东西可以工作。
甚至(根据您的 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
不需要。
假设我有以下功能:
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 comprehension 编写以下函数:
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 中不推荐使用隐式视图,从定义 wrapA
/wrapB
时编译器的功能警告中可以看出。
即使您通过 Either.RightProjection
而不是 Either
定义隐式视图来抓住机会 - 想象一下人们阅读您的代码并想知道 Either[ErrA, Int]
是如何变成 Either[ErrAOrB, Int]
?没有 IDE 提示,所以没有找到 wrapA 隐式
因此,请改用隐式 class:
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]
P.S。如果您的计算是独立的(如您的示例),则 +
操作不需要 monad - Applicative 就足够了。例如,看看 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
类型归属,但如果你有一些并行赋值(应用式而不是理解式),我相信具有 ad-hoc 超类型的东西可以工作。
甚至(根据您的 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
不需要。