我写了以下简单的代码:
import cats.effect.IO
import cats.instances.either._
import cats.syntax.TraverseSyntax
object Test extends App with TraverseSyntax{
val e: Either[String, IO[Int]] = Right(IO(2))
e.sequence //error here
}
不幸的是它拒绝编译
Error:(25, 94) value sequence is not a member of scala.util.Either
你能解释一下原因吗?我导入了either
实例,其中包括Traverse[Either[A, ?]]
。怎么了?
Traverse[F]
被定义为具有一个类型参数F[T]
的类型的类型类。 Either
类型有两个类型参数,因此Scala无法将转换应用于Traverse.Ops
,以便在使用类型Either
定义的对象上使用遍历语法方法。
要使它们可用,您可以为Either
定义类型别名,它修复第一个类型参数的值,因此只有一个类型参数。然后,Scala将能够对使用此类型别名定义的变量使用遍历语法:
type StringOr[T] = Either[String, T]
val e: StringOr[IO[Int]] = Right(IO(2))
e.sequence
另一种方法是使用类型lambdas或Traverse
为你的类型获取kind projector compiler plugin的实例,然后在它上面调用sequence
方法传递你的值:
val e: Either[String, IO[Int]] = Right(IO(2))
// With type lambda
Traverse[({ type L[T] = Either[String, T] })#L].sequence(e)
// With kind projector
Traverse[Either[String, ?]].sequence(e)
除了Kolmar的答案(这是非常彻底的),我想提出一个替代的,更容易的解决方案。
自Scala 2.11.9以来,有一个编译器标志允许它识别具有多个类型参数的类型应该只具有一个类型的类型。我们称之为“部分统一”。
启用部分统一的最简单方法是添加sbt-partial-unification
plugin。
如果你使用的是Scala 2.11.9或更高版本,你也可以简单地添加编译器标志:
scalacOptions += "-Ypartial-unification"
然后你的代码编译没有问题:
import cats.effect.IO
import cats.instances.either._
import cats.syntax.TraverseSyntax
object Test extends App with TraverseSyntax {
val e: Either[String, IO[Int]] = Right(IO(2))
e.sequence // No more error here
}
在最近发布的Scala 2.13中,默认情况下它现在处于打开状态,所以它应该只在那里开箱即用。