什么是所有的任何cruft交易?
Either类看起来很有用,使用它的方式非常明显。 但是接下来我看看API文档,我很困惑:
def joinLeft [A1 >: A, B1 >: B, C] (implicit ev: <:<[A1, Either[C, B1]]): Either[C, B1] Joins an Either through Left. def joinRight [A1 >: A, B1 >: B, C] (implicit ev: <:<[B1, Either[A1, C]]): Either[A1, C] Joins an Either through Right. def left : LeftProjection[A, B] Projects this Either as a Left. def right : RightProjection[A, B] Projects this Either as a Right.
我如何处理投影,我怎样才能调用连接?
谷歌只是指向我的API文档。
这可能只是「不理会幕后男子」的情况,但我不这么认为。 我认为这很重要。
right
是重要的。 Either
没有投影是有用的(主要是你做模式匹配),但预测值得关注,因为它们提供了更丰富的API。 您将使用更less的联接。
Either
经常用来表示“一个适当的价值或错误”。 在这方面,它就像一个扩展Option
。 当没有数据而不是无数据时,你有错误。 Option
有一个丰富的API。 如果我们知道,任何一个都是结果,哪一个是错误的,那么也可以使用相同的方法。
right
投影就是这样说的。 这是Either
,加上增加的知识,价值分别在左边或右边,另一个是错误。
例如,在Option
,可以映射,所以opt.map(f)
返回一个Option
,如果它具有一个,则将f
应用于opt
的值,如果opt
是None
则返回None
。 在左边的投影中,如果它是一个Left
,它将在左边的值上应用f
,如果它是一个Left
,它将保持不变。 观察签名:
- 在
LeftProjection[A,B]
,map[C](f: A => C): Either[C,B]
- 在
RightProjection[A,B]
,map[C](f: B => C): Either[A,C]
。
left
和right
是简单的方式来说哪一方被认为是你想要使用其中一个常用的API例程的价值。
替代品可能是:
- 设定一个惯例,就像在Haskell那里,有强烈的语法理由把价值放在正确的位置上。 当你想在另一边应用一个方法(你可能想用例如
map
来改变错误),在之前和之后做一个swap
。 - 后缀方法名称左或右(也许只是L和R)。 这将阻止用于理解。
for
理解(实际上是flatMap
,但是符号很方便)Either
是(检查)exception的替代方法。
现在连接。 左和右意味着与投影相同的事物,并且它们与flatMap
密切相关。 考虑joinLeft
。 签名可能令人费解:
joinLeft [A1 >: A, B1 >: B, C] (implicit ev: <:<[A1, Either[C, B1]]): Either[C, B1]
A1
和B1
在技术上是必要的,但不是理解的关键,让我们简化
joinLeft[C](implicit ev: <:<[A, Either[C, B])
隐含的意思是只有当A
是Either[C,B]
才能调用该方法。 该方法在Either[A,B]
中一般不可用,但仅在Either[Either[C,B], B]
上可用。 和左投影一样,我们认为这个值在左边(这对于joinRight
来说是正确的)。 连接做的是扁平化( flatMap
)。 当一个人join时,不在乎错误(B)是在内部还是外部,我们只想要[C,B]。 所以左(左(c))产生左(c),左(右)(b)和右(b)右(b)。 与flatMap的关系如下:
joinLeft(e) = e.left.flatMap(identity) e.left.flatMap(f) = e.left.map(f).joinLeft
Option
等价物将在Option[Option[A]]
, Some(Some(x))
将产生Some(x)
Some(None)
和None
将产生None
。 它可以写成o.flatMap(identity)。 请注意, Option[A]
与Either[A,Unit]
同构(如果使用左投影和连接),也Either[Unit, A]
(使用右投影)同构。
现在忽略连接,投影是一种机制,允许您使用one或one作为monad。 把它看作是将一个Option
的左边或右边提取出来,而不会丢失另一边
与往常一样,这可能更有意义的例子。 所以想象一下你有一个Either[Exception, Int]
并想要将Exception
转换为一个String
(如果存在)
val result = opReturningEither val better = result.left map {_.getMessage}
这将映射在结果的左侧,给你一个Either[String,Int]
joinLeft
和joinRight
使您能够“ joinRight
”一个嵌套的Either
:
scala> val e: Either[Either[String, Int], Int] = Left(Left("foo")) e: Either[Either[String,Int],Int] = Left(Left(foo)) scala> e.joinLeft res2: Either[String,Int] = Left(foo)
编辑: 我对这个问题的答案显示了一个如何使用投影的例子,在这种情况下,折叠一系列无模式匹配或调用isLeft
或isLeft
。 如果您熟悉如何使用Option
而无需匹配或调用isDefined
,则它是非常类似的。
在好奇地看着两者的当前来源的同时 ,我看到joinLeft
和joinRight
是通过模式匹配实现的。 然而,我偶然发现了这个老版本的源代码,并发现它使用投影来实现连接方法:
def joinLeft[A, B](es: Either[Either[A, B], B]) = es.left.flatMap(x => x)
我的build议是添加以下到您的实用程序包:
implicit class EitherRichClass[A, B](thisEither: Either[A, B]) { def map[C](f: B => C): Either[A, C] = thisEither match { case Left(l) => Left[A, C](l) case Right(r) => Right[A, C](f(r)) } def flatMap[C](f: B => Either[A, C]): Either[A, C] = thisEither match { case Left(l) => Left[A, C](l) case Right(r) => (f(r)) } }
根据我的经验,唯一有用的方法是折叠。 在function代码中,您不会真的使用isLeft或isRight。 joinLeft和joinRight可能是Dider Dupont解释的扁平化函数,但是我没有这个机会使用它们。 以上是使用任何一个正确的偏见,我怀疑是多数人使用它们。 它就像一个带有错误值而不是无的选项。
这是我自己的一些代码。 道歉它没有抛光的代码,但它是一个例子使用任何一个为理解。 将map和flatMap方法添加到Either中允许我们使用特殊的语法来理解。 它parsingHTTP标头,或者返回一个Http和Html错误页面响应或者一个parsing的自定义HTTP请求对象。 如果不使用理解,代码将很难理解。
object getReq { def LeftError[B](str: String) = Left[HResponse, B](HttpError(str)) def apply(line1: String, in: java.io.BufferedReader): Either[HResponse, HttpReq] = { def loop(acc: Seq[(String, String)]): Either[HResponse, Seq[(String, String)]] = { val ln = in.readLine if (ln == "") Right(acc) else ln.splitOut(':', s => LeftError("400 Bad Syntax in Header Field"), (a, b) => loop(acc :+ Tuple2(a.toLowerCase, b))) } val words: Seq[String] = line1.lowerWords for { a3 <- words match { case Seq("get", b, c) => Right[HResponse, (ReqType.Value, String, String)]((ReqType.HGet, b, c)) case Seq("post", b, c) => Right[HResponse, (ReqType.Value, String, String)]((ReqType.HPost, b, c)) case Seq(methodName, b, c) => LeftError("405" -- methodName -- "method not Allowed") case _ => LeftError("400 Bad Request: Bad Syntax in Status Line") } val (reqType, target, version) = a3 fields <- loop(Nil) val optLen = fields.find(_._1 == "content-length") pair <- optLen match { case None => Right((0, fields)) case Some(("content-length", second)) => second.filterNot(_.isWhitespace) match { case s if s.forall(_.isDigit) => Right((s.toInt, fields.filterNot(_._1 == "content-length"))) case s => LeftError("400 Bad Request: Bad Content-Length SyntaxLine") } } val (bodyLen, otherHeaderPairs) = pair val otherHeaderFields = otherHeaderPairs.map(pair => HeaderField(pair._1, pair._2)) val body = if (bodyLen > 0) (for (i <- 1 to bodyLen) yield in.read.toChar).mkString else "" } yield (HttpReq(reqType, target, version, otherHeaderFields, bodyLen, body)) } }