Что за дело со всем этим? - PullRequest
       2

Что за дело со всем этим?

41 голосов
/ 20 августа 2011

Класс 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.

Это может быть просто случай "не обращать внимания на человека за кулисами", но я так не думаю.Я думаю, что это важно.

Ответы [ 4 ]

50 голосов
/ 20 августа 2011

left и right являются важными. Either полезен без проекций (в основном вы делаете сопоставление с образцом), но проекции заслуживают внимания, так как они дают гораздо более богатый API. Вы будете использовать соединения гораздо меньше.

Either часто используется для обозначения «правильного значения или ошибки». В этом отношении он похож на расширенный Option. Когда нет данных, вместо None появляется ошибка. Option имеет богатый API. То же самое можно сделать доступным на Either, при условии, что мы знаем в любом из них, какой из них является результатом, а какой - ошибкой.

left и right проекция говорит именно об этом. Это Either, плюс добавленное знание, что значение находится соответственно слева или справа, а другое является ошибкой.

Например, в Option вы можете отобразить, поэтому opt.map(f) возвращает Option с f, примененным к значению opt, если оно есть, и все еще None, если opt было None. В левой проекции он применяет f к значению слева, если оно равно Left, и оставляет его неизменным, если оно равно Right. Соблюдайте подписи:

  • In 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 является альтернативой (проверенным) исключениям.

Теперь присоединяется. Левый и правый означает то же самое, что и для проекций, и они тесно связаны с 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) Когда один присоединяется, его не волнует, является ли ошибка (B) внутри или снаружи, мы просто хотим либо [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 (личность). Обратите внимание, что Option[A] изоморфно Either[A,Unit] (если вы используете левые проекции и объединения), а также Either[Unit, A] (используя правые проекции).

20 голосов
/ 20 августа 2011

На данный момент игнорируя объединения, проекции - это механизм, позволяющий вам использовать Either в качестве монады.Думайте об этом как об извлечении левой или правой стороны в Option, но без потери другой стороны

Как всегда, это, вероятно, имеет смысл с примером.Итак, представьте, что у вас есть Either[Exception, Int] и вы хотите преобразовать Exception в String (если есть)

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

Это отобразит левую сторону результата, давая вам Either[String,Int]

14 голосов
/ 20 августа 2011

joinLeft и 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)

Редактировать: Мой ответ на этот вопрос показывает один пример того, как вы можете использовать проекции, в этом случае сложить последовательность Either с без сопоставления с образцом или вызова isLeft или isRight. Если вы знакомы с тем, как использовать Option без сопоставления или вызова isDefined, это аналогично.


С любопытством глядя на текущий источник Either , я увидел, что joinLeft и joinRight реализованы с сопоставлением с образцом. Однако я наткнулся на эту более старую версию источника и увидел, что она использовалась для реализации методов соединения с использованием проекций:

def joinLeft[A, B](es: Either[Either[A, B], B]) =
  es.left.flatMap(x => x)
1 голос
/ 26 октября 2015

Я предлагаю добавить следующее в ваш пакет утилит:

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 могут быть полезны для выравнивания функций, как объяснил Дайдер Дюпон, но у меня не было повода использовать их таким образом.Вышеизложенное использует Either как предвзятость, и я подозреваю, что большинство людей используют их.Это как опция со значением ошибки вместо None.

Вот мой собственный код.Извиняет, что это не отточенный код, а пример использования Either in a для понимания.Добавление методов 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))
  }   
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...