Scala中Free monad代数返回Either时结果被Right包裹问题咨询
问题根源
你当前的代数定义将Either直接作为Command的返回值类型参数,因此通过C.ask拿到的name本身就是Either[Exception, String]实例,字符串插值时会调用其toString方法,自然会打印出Right(George)格式的内容。同时这种写法也没有利用到Free Monad的栈自动处理错误语义的能力,错误需要手动在业务代码里判断。
解决方案
这里提供三种常用的改法,可根据你的现有代码适配:
方案1:调整代数定义,将错误语义整合到Free Monad栈(推荐)
直接把Command的返回值参数设为原始类型,不要嵌套Either,错误能力通过Monad栈的MonadError提供:
// 修改Command定义,去掉返回值里的Either嵌套 sealed trait Command[A] object Command { case class Tell(line: String) extends Command[Unit] case class Ask(line: String) extends Command[String] } // 你可以在App栈上叠加EitherT来承载错误,也可以给Free[App, *]实现MonadError实例 type App[A] = EitherK[Command, Log, A] type Program[A] = EitherT[Free[App, *], Exception, A] def program(implicit L: LogOps[App], C: CommandOps[App]): Program[Unit] = for { _ <- L.show("start <ask>").liftTo[Program] // 这里拿到的name直接是String类型 name <- C.ask("What's your name?").liftTo[Program] _ <- L.show("start <tell>").liftTo[Program] _ <- C.tell(s"Hi <$name>, nice to meet you!").liftTo[Program] _ <- L.show("done.").liftTo[Program] } yield ()
解释器执行Command操作时如果发生错误,直接调用MonadError.raiseError抛出即可,整个流程会自动终止并返回Left。
方案2:不调整代数定义,在业务代码中手动展开Either
如果不想修改现有Command和Ops的定义,直接在for推导中处理Either即可:
import cats.syntax.all._ import cats.MonadError def program(implicit L: LogOps[App], C: CommandOps[App], // 需给Free[App, *]提供MonadError实例 ME: MonadError[Free[App, *], Exception]): Free[App, Unit] = for { _ <- L.show("start <ask>") nameEither <- C.ask("What's your name?") // 将Either转换为MonadError的错误效果,出错则直接终止流程 name <- ME.fromEither(nameEither) _ <- L.show("start <tell>") _ <- C.tell(s"Hi <$name>, nice to meet you!") _ <- L.show("done.") } yield ()
如果需要自定义错误处理逻辑,也可以手动match:
name <- nameEither match { case Right(v) => Free.pure(v) case Left(e) => // 比如打印错误日志然后终止流程 L.show(s"ask failed: ${e.getMessage}") *> Free.raiseError(e) }
方案3:调整CommandOps的lift逻辑,自动展开Either
如果不想改业务代码和Command定义,可以修改CommandOps的实现,在lift操作时自动把Either展开:
// 原来的ask实现大概是这样的 // def ask(line: String): Free[App, Either[Exception, String]] = liftF(Command.Ask(line)) // 修改后自动展开Either def ask(line: String)(implicit ME: MonadError[Free[App, *], Exception]): Free[App, String] = liftF(Command.Ask(line)).flatMap(ME.fromEither)
修改后上层业务代码不需要做任何调整,调用C.ask直接拿到String类型的返回值。
内容的提问来源于stack exchange,提问作者marc0x71
相关产品推荐
相关产品推荐

