You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用Scala Cats的Kleisli与Either组合返回Monad的函数

把Kleisli从Option迁移到Either的实现方案

嘿,我来帮你把这段基于Option的Kleisli代码改成用Either实现,这样你就能传递具体的错误信息啦~

核心调整点

首先要明确,Either是双参数类型(Either[E, A]),而Kleisli要求它的M是单参数的Monad类型构造器。所以我们需要把Either的错误类型固定为你定义的Failure,这样它就变成了符合要求的单参数类型(Either[Failure, *])。

完整代码实现

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

object KleisliEitherEx extends App {
  case class Failure(msg: String)
  sealed trait Context
  case class Initial(age: Int) extends Context
  case class AgeCategory(category: String, t: Int) extends Context
  case class AgeSquared(s: String, t: Int, u: Int) extends Context

  // 定义Result类型:固定错误为Failure的Kleisli
  type Result[A, B] = Kleisli[Either[Failure, *], A, B]

  // 第一个Kleisli函数:从Initial生成AgeCategory,带错误信息
  val ageCategory: Result[Initial, AgeCategory] = Kleisli { initial =>
    initial.age match {
      case a if a < 0 => Left(Failure("年龄不能为负数"))
      case a if a < 18 => Right(AgeCategory("未成年人", a))
      case a if a < 60 => Right(AgeCategory("成年人", a))
      case a => Right(AgeCategory("老年人", a))
    }
  }

  // 第二个Kleisli函数:从AgeCategory生成AgeSquared,带错误检查
  val calculateAgeSquared: Result[AgeCategory, AgeSquared] = Kleisli { category =>
    // 这里可以加额外的错误检查,比如t不能为0(示例)
    if (category.t == 0) Left(Failure("年龄不能为0"))
    else {
      val squared = category.t * category.t
      Right(AgeSquared(category.category, category.t, squared))
    }
  }

  // 组合两个Kleisli函数:从Initial直接到AgeSquared
  val fullPipeline: Result[Initial, AgeSquared] = ageCategory andThen calculateAgeSquared

  // 测试不同场景
  println(fullPipeline.run(Initial(-5))) // Left(Failure(年龄不能为负数))
  println(fullPipeline.run(Initial(10))) // Right(AgeSquared(未成年人,10,100))
  println(fullPipeline.run(Initial(0)))  // Left(Failure(年龄不能为0))
  println(fullPipeline.run(Initial(30))) // Right(AgeSquared(成年人,30,900))
}

关键细节说明

  • Result类型定义:Kleisli[Either[Failure, *], A, B]用了kind-projector语法,让Either[Failure, *]成为一个单参数的类型构造器(类似Option),满足Kleisli对Monad类型的要求。如果你的项目没启用kind-projector,可以用类型投影的写法:Kleisli[({ type E[X] = Either[Failure, X] })#E, A, B],效果是一样的。
  • 错误处理:原来返回None的分支,现在换成Left(Failure(...))来携带具体错误信息;成功分支用Right(...)包装结果,和Option的Some逻辑一致。
  • 函数组合:和Option版本一样,你可以用andThen、compose或者for推导式来组合Kleisli函数,因为Kleisli[Either[Failure, *], A, B]本身是Monad实例,支持所有Monad操作。

比如用for推导式组合的写法:

val fullPipelineFor: Result[Initial, AgeSquared] = for {
  category <- ageCategory
  squared <- calculateAgeSquared
} yield squared

这样就完全替换了原来的Option实现,同时保留了Kleisli的函数组合能力,还能提供更清晰的错误反馈~

内容的提问来源于stack exchange,提问作者KaC

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 07:25:13