2011-08-20 55 views
40

Either类似乎很有用,使用它的方式非常明显。但后来我看API文档,我百思不得其解:什么是所有的任何cruft交易?

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文档。

这可能只是“不理睬幕后男子”而已,但我不这么认为。我认为这很重要。

+0

强制性注意,'Try'往往容易比'Either'使用异常处理,但斯卡拉2.10之前是不可用的。 [http://www.scala-lang.org/api/current/scala/util/Try.html] – broadmonkey

回答

14

joinLeftjoinRight使你“变平”的嵌套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) 

编辑:My answer to this question示出了如何使用突起,在这种情况下折叠在一起的一个序列中的一个示例Either s没有模式匹配或呼叫isLeftisRight。如果您熟悉如何在没有匹配的情况下使用Option或致电isDefined,那么它是非常类似的。


虽然好奇地看着当前source of Either,我看到joinLeftjoinRight与模式匹配来实现。不过,我偶然发现这个older version of the source,看到它用来实现使用投影的连接方法:

def joinLeft[A, B](es: Either[Either[A, B], B]) = 
    es.left.flatMap(x => x) 
+1

巢式Either的用例是什么? – Malvolio

+4

这并不是说你可能需要一个,而是你可能会得到一个,并且加入可以让你摆脱它。假设你在A的某种通用容器上有一个函数,它会返回给你一个[A,E],因为有可能失败。假设你的特定容器包含[YourData,E],因为数据是可能失败的进程的结果。然后你得到一个Either [或[YourData,E],E],并且你可能想要加入,因为你不关心在数据建立时(内部的)还是检索到的(外部的)。 –

20

忽略的加入,现在,预测是让您在使用使用Either作为一个单子的机制。把它看成是提取的左侧或右侧为Option,但不失对方

一如往常,这可能更有意义用一个例子。所以,想象一下你有一个Either[Exception, Int]并希望将Exception转换为String(如果存在的话)

val result = opReturningEither 
val better = result.left map {_.getMessage} 

这将映射在结果的左侧,给你一个Either[String,Int]

+1

你的想法让我感兴趣,我希望订阅你的电子报。 – Malvolio

+0

好奇......你愿意付多少钱? –

49

leftright是重要的那些。 Either在没有预测的情况下很有用(主要是你做模式匹配),但是预测非常值得关注,因为它们提供了更丰富的API。您将使用更少的联接。

Either通常用于表示“合适的值或错误”。在这方面,它就像是一个扩展的Option。当没有数据时,而不是None,您有错误。 Option有一个丰富的API。如果我们知道的话,可以在Either上得到同样的结果,哪一个是结果,哪一个是错误。

leftright投影就是这样说的。这是Either,加上增加的知识,该值分别在左边或右边,另一个是错误。

例如,在Option,可以映射,所以opt.map(f)如果它有一个返回与fOption应用于opt的价值,并且仍然None如果optNone。在左投影中,如果它是Left,它将在左侧的值上应用f,如果它是Right,则将其保持不变。观察签名:

  • 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是(检查)例外的替代。

现在加入。左和右意味着与投影相同的东西,它们与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]) 

什么隐含的意思是,该方法只能如果A被称为是Either[C,B]。该方法通常在Either[A,B]上不可用,但仅在Either[Either[C,B], B]上可用。与左投影一样,我们认为该值在左侧(对于joinRight这是正确的)。这个连接做的是扁平化(想想flatMap)。当一个人加入时,不关心错误(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](使用右投影)同构。

+1

很好的答案,感谢您花时间! –

1

我的建议是将以下添加到您的应用程序包:

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

在我的经验,唯一有用的提供的方法是折。您在功能代码中并不真正使用isLeft或isRight。 joinLeft和joinRight可能会像Dider Dupont所解释的扁平化函数一样有用,但我没有机会以这种方式使用它们。以上是使用任一作为正确的偏见,我怀疑是大多数人如何使用它们。它就像一个带有错误值而不是无的选项。

这是我自己的一些代码。道歉它没有抛光的代码,但它是一个例子,用于理解。将map和flatMap方法添加到Either允许我们在理解中使用特殊语法。它解析HTTP标头,或者返回Http和Html错误页面响应或者解析的自定义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)) 
    } 
}