我正在使用Scala 2.10.2。
我需要一个功能
def extractEither(m: M[(Key, Either[TLeft, TRight])])
: Either[TLeft, M[(Key, TRight)]]
其中M
可以是Seq,List,Map或其他任何东西,返回类型仍然合适。
我正在使用以下测试:
val map = Map(1 -> Left("foo"), 2 -> Right('bar), 3 -> Right('baz))
我目前的尝试如下:
尝试#1
def extractEither[
Key, TLeft, TRight, M[_] <: TraversableOnce[_]
]
(monad: M[(Key, Either[TLeft, TRight])])
(implicit cbf: CanBuildFrom[
M[(Key, Either[TLeft, TRight])],
(Key, TRight),
M[(Key, TRight)]
]): Either[TLeft, M[(Key, TRight)]] = {
val builder = cbf(monad)
builder.sizeHint(monad.size)
(monad: GenTraversableOnce[_]).foreach { x =>
val (key, either) = x.asInstanceOf[(Key, Either[TLeft, TRight])]
either.fold(
leftVal => return Left(leftVal),
rightVal => builder += ((key, rightVal))
)
}
Right(builder.result())
}
这失败了:
scala> extractEither(map)
<console>:20: error: no type parameters for method extractEither: (monad: M[(Key, Either[TLeft,TRight])])(implicit cbf: scala.collection.generic.CanBuildFrom[M[(Key, Either[TLeft,TRight])],(Key, TRight),M[(Key, TRight)]])Either[TLeft,M[(Key, TRight)]] exist so that it can be applied to arguments (scala.collection.immutable.Map[Int,Product with Serializable with scala.util.Either[String,Symbol]])
--- because ---
argument expression's type is not compatible with formal parameter type;
found : scala.collection.immutable.Map[Int,Product with Serializable with scala.util.Either[String,Symbol]]
required: ?M
extractEither(map)
^
<console>:20: error: type mismatch;
found : scala.collection.immutable.Map[Int,Product with Serializable with scala.util.Either[String,Symbol]]
required: M[(Key, Either[TLeft,TRight])]
extractEither(map)
^
<console>:20: error: Cannot construct a collection of type M[(Key, TRight)] with elements of type (Key, TRight) based on a collection of type M[(Key, Either[TLeft,TRight])].
extractEither(map)
^
尝试#2
这个仅限于地图,可变或不可变。
def extractEither[
Key, TLeft, TRight, M <: collection.Map[Key, Either[TLeft, TRight]]
](map: M): Either[TLeft, M] = {
Right[TLeft, M](map.map { case (key, either) =>
either.fold(
leftVal => return Left(leftVal),
rightVal => key -> rightVal
)
}.asInstanceOf[M])
}
这失败了:
scala> extractEither(map)
<console>:20: error: inferred type arguments [Nothing,Nothing,Nothing,scala.collection.immutable.Map[Int,Product with Serializable with scala.util.Either[String,Symbol]]] do not conform to method extractEither's type parameter bounds [Key,TLeft,TRight,M <: scala.collection.Map[Key,Either[TLeft,TRight]]]
extractEither(map)
^
<console>:20: error: type mismatch;
found : scala.collection.immutable.Map[Int,Product with Serializable with scala.util.Either[String,Symbol]]
required: M
extractEither(map)
^
使用非通用解决方案
def extractEither[
Key, TLeft, TRight
](map: Map[Key, Either[TLeft, TRight]]): Either[TLeft, Map[Key, TRight]] = {
Right(map.map { case (key, either) =>
either.fold(
leftVal => return Left(leftVal),
rightVal => key -> rightVal
)
})
}
但这根本不是通用的:|
有人能说明如何正确地写这个吗?
答案 0 :(得分:4)
第一个解决方案几乎就在那里,但你应该使用M [X]&lt ;:TraversableOnce [X]来避免必须施放。并且您需要向编译器键入提示以使映射的类型正确,无论是将其传递给方法还是定义它时。
scala> def extractEither[K, L, R, M[X] <: TraversableOnce[X]](monad: M[(K, Either[L, R])])(implicit cbf: CanBuildFrom[M[(K, Either[L, R])], (K, R), M[(K, R)]]): Either[L, M[(K, R)]] = {
val builder = cbf(monad)
builder.sizeHint(monad.size)
monad.foreach({x =>
val (key, either) = x
either.fold(
leftVal => return Left(leftVal),
rightVal => builder += ((key, rightVal))
)})
Right(builder.result())
}
warning: there were 1 feature warning(s); re-run with -feature for details
extractEither: [K, L, R, M[X] <: TraversableOnce[X]](monad: M[(K, Either[L,R])])(implicit cbf: scala.collection.generic.CanBuildFrom[M[(K, Either[L,R])],(K, R),M[(K, R)]])Either[L,M[(K, R)]]
scala> val map = Map(1 -> Left("foo"), 2 -> Right('bar), 3 -> Right('baz))
map: scala.collection.immutable.Map[Int,Product with Serializable with scala.util.Either[String,Symbol]] = Map(1 -> Left(foo), 2 -> Right('bar), 3 -> Right('baz))
scala> extractEither(map: TraversableOnce[(Int, Either[String, Symbol])])
res2: Either[String,TraversableOnce[(Int, Symbol)]] = Left(foo)
scala> val map2: Map[Int, Either[String, Symbol]] = Map(1 -> Left("foo"), 2 -> Right('bar), 3 -> Right('baz))
map2: Map[Int,Either[String,Symbol]] = Map(1 -> Left(foo), 2 -> Right('bar), 3 -> Right('baz))
scala> extractEither(map2)
res3: Either[String,scala.collection.immutable.Iterable[(Int, Symbol)]] = Left(foo)