【问题标题】:What's the deal with all the Either cruft?所有的Either杂货是怎么回事?
【发布时间】:2011-10-31 03:48:15
【问题描述】:

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.

我如何处理投影以及如何调用连接?

Google 只是将我指向 API 文档。

这可能只是“不注意幕后男人”的情况,但我不这么认为。我认为这很重要。

【问题讨论】:

标签: scala


【解决方案1】:

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 展示了如何使用投影的一个示例,在这种情况下,无需模式匹配或调用isLeftisRight 即可将一系列Eithers 折叠在一起。如果您熟悉如何使用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)

【讨论】:

  • 嵌套 Either 的用例是什么?
  • 不是你可能想要一个,而是你可能会得到一个,而加入可以让你摆脱它。假设您在 A 的某种通用容器上有一个函数,它将返回一个 Either[A,E],因为有可能失败。假设您的特定容器包含 Either[YourData, E],因为数据是也可能失败的过程的结果。然后你会得到一个 Either[Either[YourData, E], E],你可能想加入,因为你不关心错误是在构建数据(内部数据)还是检索数据(外部数据)时发生。
【解决方案2】:

暂时忽略连接,投影是一种允许您使用 Either 作为 monad 的机制。将其视为将左侧或右侧提取到Option,但不会丢失另一侧

与往常一样,举个例子可能更有意义。因此,假设您有一个 Either[Exception, Int] 并希望将 Exception 转换为 String(如果存在)

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

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

【讨论】:

  • 您的想法引起了我的兴趣,我希望订阅您的时事通讯。
  • 好奇...你愿意付多少钱?
【解决方案3】:

leftright 是重要的。 Either 在没有投影的情况下很有用(主要是进行模式匹配),但是投影非常值得关注,因为它们提供了更丰富的 API。您将更少使用连接。

Either 通常用于表示“正确的值或错误”。在这方面,它就像一个扩展的 Option 。如果没有数据,而不是None,则会出现错误。 Option 有丰富的 API。同样可以在Either 上提供,前提是我们知道,在Either 中,哪一个是结果,哪一个是错误。

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

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

  • LeftProjection[A,B], map[C](f: A =&gt; C): Either[C,B]
  • RightProjection[A,B]map[C](f: B =&gt; C): Either[A,C]

leftright 只是表示当您想要使用常用 API 例程之一时将哪一侧视为值的方式。

替代方案可能是:

  • 设置一个约定,就像在 Haskell 中一样,有很强的句法理由将值放在正确的位置。当您想在另一边应用方法时(例如,您可能很想用map 更改错误),在前后执行swap
  • 后缀方法名称带有左或右(可能只是 L 和 R)。这将阻止使用理解。使用for 理解(实际上是flatMap,但for 表示法非常方便)Either 是(已检查)异常的替代方案。

现在加入。 Left 和 Right 与投影的含义相同,它们与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 来说是正确的)。 join 所做的就是把它弄平(想想flatMap)。当一个连接时,一个不关心错误(B)是在内部还是在外部,我们只需要Either[C,B]。所以 Left(Left(c)) 产生 Left(c),Left(Right(b)) 和 Right(b) 都产生 Right(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](使用右投影)同构。

【讨论】:

    【解决方案4】:

    我的建议是将以下内容添加到您的实用程序包中:

    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 。正如 Dider Dupont 所解释的那样,joinLeft 和 joinRight 可能作为展平函数很有用,但是我没有机会以这种方式使用它们。以上是使用 Either 作为右偏,我怀疑这是大多数人使用它们的方式。它就像一个带有错误值而不是 None 的选项。

    这是我自己的一些代码。抱歉,它的代码没有经过完善,但它是在 for 理解中使用 Either 的一个例子。将 map 和 flatMap 方法添加到 Either 允许我们使用 for 理解中的特殊语法。它解析 HTTP 标头,返回 Http 和 Html 错误页面响应或解析的自定义 HTTP 请求对象。如果不使用 for 理解,代码将很难理解。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多