кошки: сопоставление вложено в любое из значений параметра

У меня есть следующая функция:

def function(i: Int): IO[Either[String, Option[Int]]] = ???

Мне нужна функция вида:

def foo(either: Either[String, Option[Int]]): IO[Either[String, Option[Int]]]

и я хочу, чтобы он имел следующее поведение:

def foo1(either: Either[String, Option[Int]])
: IO[Either[String, Option[Int]]] = either match {
  case Right(Some(i)) => bar(i)
  case Right(None) => IO.pure(None.asRight)
  case Left(s) => IO.pure(s.asLeft)
}

Я хочу сделать это менее явно, поэтому я попробовала BothT:

def foo2(either: Either[String, Option[Int]]): 
  IO[Either[String, Option[Int]]] = {
    val eitherT = for {
      maybe <- EitherT.fromEither[IO](either)
      int <- EitherT.fromOption(maybe, "???")
      x <- EitherT(bar(int))
    } yield x

  eitherT.value
}

но это означает, что Right(None) будет сопоставлено с IO(Left("???")), чего я не хочу.

  • существует ли альтернативная формулировка с EitherT без выражения соответствия, эквивалентная реализации foo1?

  • что более важно, как будет выглядеть реализация, использующая map/traverse/biTraverse/etc. (и не совпадающая ни с одной из опций)?

p.s. Намерение здесь состоит в том, чтобы определить функцию «карты» для следующего типа:

trait Lookup[F[_], K, A] {
  def get(key: K): F[Either[FormatError, Option[A]]]
}

person Maths noob    schedule 28.08.2019    source источник


Ответы (3)


без match

import cats._
import cats.data._
import cats.implicits._

def bar[F[_] : Applicative](i: Int): F[Either[String, Option[Int]]] =
  (i + 1).some.asRight[String].pure[F]

def foo[F[_] : Applicative : Monad : FlatMap](either: Either[String, Option[Int]]): F[Either[String, Option[Int]]] =
  OptionT(EitherT(either.pure[F])).flatMap { i =>
    OptionT(EitherT(bar[F](i)))
  }.value.value

foo[Id](1.some.asRight)
//res0: cats.Id[Either[String,Option[Int]]] = Right(Some(2))
person Dmytro Orlov    schedule 29.08.2019
comment
Используете ли вы определенные флаги компилятора для запуска этого? Запуск его на рабочем листе scala дает мне: параметры типа для метода не применяются: (значение: F[Option[A]])cats.data.OptionT[F,A] в объекте OptionT существуют, поэтому его можно применять к аргументам ( cats.data.EitherT[F,String,Option[Int]]) --- потому что --- тип выражения аргумента не совместим с типом формального параметра; найдено: cats.data.EitherT[F,String,Option[Int]] требуется: ?F[Option[?A]] OptionT( в строке 5. - person Maths noob; 31.08.2019
comment
цитата из typelevel.org/cats : Для Scala 2.12 вы должны добавить следующее в свой build.sbt : scalacOptions += -Ypartial-unification для Scala 2.13 вам это не нужно. Есть ли какая-то конкретная причина не обновляться до 2.13? - person Dmytro Orlov; 02.09.2019

С помощью MonadError мы можем:

  • исключить 1 преобразователь во время реализации бизнес-логики, поэтому в def foo требуется только OptionT
  • не принимайте решения заранее, как мы хотим обрабатывать ошибки, поэтому пользователь должен выбрать конкретное EitherT:
import cats._
import cats.data._
import cats.implicits._
import monix.eval._

type ErrorHandler[F[_]] = MonadError[F, String]

def bar[F[_] : ErrorHandler : Applicative](i: Int): F[Option[Int]] =
  if (i > 0) (i + 1).some.pure[F] else implicitly[ErrorHandler[F]].raiseError("error")

def foo[F[_] : ErrorHandler : Applicative : FlatMap](option: Option[Int]): F[Option[Int]] =
  OptionT(option.pure[F]).flatMap { i =>
    OptionT(bar[F](i))
  }.value

type Effect[A] = EitherT[Task, String, A]

import monix.execution.Scheduler.Implicits.global

foo[Effect](1.some).value.runSyncUnsafe()
//Either[String, Option[Int]] = Right(Some(2))
foo[Effect](0.some).value.runSyncUnsafe()
//Either[String, Option[Int]] = Left("error")
foo[Effect](none).value.runSyncUnsafe()
//Right(None)
person Dmytro Orlov    schedule 29.08.2019

person    schedule
comment
Спасибо Дмитрий, но это по сути прославленная версия foo1. он все еще использует оператор соответствия, который я хочу знать, могу ли я избежать. - person Maths noob; 29.08.2019