什么是所有的任何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的值,如果optNone则返回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]

leftright是简单的方式来说哪一方被认为是你想要使用其中一个常用的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] 

A1B1在技​​术上是必要的,但不是理解的关键,让我们简化

 joinLeft[C](implicit ev: <:<[A, Either[C, B]) 

隐含的意思是只有当AEither[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]

joinLeftjoinRight使您能够“ 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) 

编辑: 我对这个问题的答案显示了一个如何使用投影的例子,在这种情况下,折叠一系列无模式匹配或调用isLeftisLeft 。 如果您熟悉如何使用Option而无需匹配或调用isDefined ,则它是非常类似的。


在好奇地看着两者的当前来源的同时 ,我看到joinLeftjoinRight是通过模式匹配实现的。 然而,我偶然发现了这个老版本的源代码,并发现它使用投影来实现连接方法:

 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)) } }