如何使用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
相关产品推荐
相关产品推荐

