【问题标题】:How to chain generically chain functions returning Either with an operator like `andThen`?如何使用像`andThen`这样的运算符链接一般返回Either的链函数?
【发布时间】:2021-05-26 23:13:09
【问题描述】:

问题: 链接多个 Either 返回函数,其中 Left 都是从公共密封特征 InternalError 继承的失败。但是,编译器抱怨 chain 正在返回 Either[_,Success] 而不是 Either[InternalError, Success]

这是执行链接的代码:

import scala.language.implicitConversions
object EitherExtension {
  implicit class AndThenEither[A,B](val e: Function1[A,Either[_,B]]) {
    //get ability to chain/compose functions that return aligning Eithers
    def andThenE[C](f:Function1[B, Either[_,C]]): Function1[A, Either[_,C]] = {
      (v1: A) => e.apply(v1).flatMap(b => f.apply(b))
    }
  }
}

正如 cmets 中所指出的,这会丢弃 Left 的类型。如果我在下面更改它,它将无法正常工作,因为最终输出可以是Either[X|Y, C] 类型,它解析为Either[_,C],我又回到了第一格。

implicit class AndThenEither[A,B,X](val e: (A) => Either[X, B]) {
    def andThenE[C,Y](f:(B) => Either[Y, C]): (A) => Either[_, C] = {
      (v1: A) => e.apply(v1).flatMap(b => f.apply(b))
    }
  }

这是显示类型对齐的组合失败的示例:

import EitherExtension._
object AndThenComposition {
  //sample type definitions of failure responses
  sealed trait InternalError
  case class Failure1() extends InternalError
  case class Failure2() extends InternalError
  //sample type definitions
  case class Id(id: Int)
  case class Stuff()
  //sample type definitions of successful responses
  case class Output1()
  case class Output2()
  case class InputRequest()

  val function1: (InputRequest) => Either[Failure1, Output1] = ???
  val function2: (Output1)  => Either[Failure2, Output2] = ???

  def doSomething(s:Id, l:List[Stuff]): Either[InternalError, Output2] = {
    val pipeline = function1 andThenE function2
    pipeline(InputRequest()) //Why is this of type Either[_, Output2]
  }
}

我错过了什么?我怎样才能让返回类型 notEither[Any, Output2] 而是基本/密封特征?这可以通用吗?

【问题讨论】:

  • 好吧,你的 andThenE 丢弃了 Left 的类型......所以不知道为什么你甚至问它为什么这样做,你告诉它这样做。
  • 我明白你的意思——但我怎样才能让它不这样做呢? andThenE 的输出结果总是Either[_,C],因为错误可能来自链中的 previous 函数或这个函数,两者都可以是不同的类型。我不想在泛型函数中声明错误的 type,因此使用 andThenE 签名。
  • @LuisMiguelMejíaSuárez - 更新了问题以更好地反映问题的根源。
  • @LuisMiguelMejíaSuárez - 请您发表评论/链接一个答案,以便我接受吗?它确实解决了我的问题并使编译成功。如果出现新问题,我会问一个新/后续问题????

标签: scala


【解决方案1】:

您需要保留 left 的类型,因此我们将修改扩展方法来做到这一点。

请注意,由于两个 either 可以有不同的左类型,我们要做的是使用 type bound 要求编译器推断这些类型之间的 LUB;感谢Any,这总是可能的(虽然并不总是有帮助)

object EitherExtension {
  implicit class AndThenEither[I, L1, R1](private val f: I => Either[L1, R1]) extends AnyVal {
    def andThenE[L2 >: L1, R2](g: R1 => Either[L2, R2]): I => Either[L2, R2] =
      i => f(i).flatMap(g)
  }
}

可以这样使用:

import EitherExtension._

object AndThenComposition {
  sealed trait InternalError
  final case object Failure1 extends InternalError
  final case object Failure2 extends InternalError

  val function1: Int => Either[Failure1.type, String] = ???
  val function2: String  => Either[Failure2.type, Boolean] = ???

  def doSomething(input: Int): Either[InternalError, Boolean] = {
    (function1 andThenE function2)(input)
  }
}

查看运行代码here

【讨论】:

    【解决方案2】:

    如果您在生产中使用它,而且它不仅仅是一个学习的东西,您正在寻找的就是 Kleisli,幸运的是 cats-core 已经实现了它。

    根据猫芯docs

    Kleisli 支持组合返回一元值的函数, 例如 Option[Int] 或 Either[String, List[Double]],没有 让函数接受 Option 或 Either 作为参数,可以是 奇怪而笨拙。

    由于 Kleisli 使用签名 A => F[B] 组合了两个函数,因此您只需要一个抽象即可使用 Kleisli,它为您的操作创建了一个新类型:

    type Operation[A] = Either[InternalFailure, A]
    

    通过这样做,您应该能够像这样使用 Kleisli:

    import cats.data.Kleisli
    
    val first: Kleisli[Operation, InputRequest, Output1] = Kleisli { request: InputRequest =>
      Left(Failure1())
    }
    
    val second: Kleisli[Operation, Output1, Output2] = Kleisli { output: Output1 =>
      Right(Output2())
    }
    
    val composed = first.andThen(second)
    

    【讨论】:

    • 是的,我知道cats 中的Kliesli 构造。无需导入库即可实现 TBH。通过强制所有Either.LeftInternalFailure 类型,您键入了别名Operation[A]。我也可以通过在我的andThenE 中修复它来做同样的事情。不过,这并不能回答问题,而是通过回避我的问题来提供解决方案。此外,以这种方式或通过andThenE 这样做都是“生产可行的”恕我直言。创建Function1 的抽象子类并向其添加andThen 方法并完成这项工作相对容易——我正在尝试通过隐式来完成。
    • 我以为您只是在寻找一种方法来组合返回 monad 的函数。关于您的实际问题,我认为仅使用 flatMap 是不可能的,因为 Either.flatMap 期望 A1(或 Y,在您的情况下)是 X 的超类型
    • 这适用于 Scala 3 + Union 类型,不过:gist.github.com/rodrigovedovato/…
    猜你喜欢
    • 1970-01-01
    • 2013-06-29
    • 1970-01-01
    • 1970-01-01
    • 2019-08-26
    • 1970-01-01
    • 2020-08-23
    • 2018-07-29
    • 1970-01-01
    相关资源
    最近更新 更多