从 ReaderWriterState[Either] 短路

Short-circuit from ReaderWriterState[Either]

我正在制作一个游戏原型,并尽量做到纯粹。 所有用例都适合一个场景 -

  1. 尝试在存储中找到播放器
  2. 执行一些业务逻辑
  3. 更新存储中的播放器
  4. 虽然更新一个可以产生一些输出 - 日志消息、给其他玩家的消息等。

从另一侧必须访问环境(数据库、资源等)、全局游戏状态(不可变的游戏配置、种子等)。

为了将它们结合在一起,我最终得到了 scalaz7 ReaderWriterState monad,如下所示:

一些定义:

trait UserService
trait Environment
trait State
sealed trait Error
sealed trait Output

case object GameEnvironment extends Environment
case object GameState extends State
object Output {
  case object Log extends Output
  case object Parcel extends Output
  case object Analytics extends Output
}
object Error {
  case class AppError(code: String) extends Error
  case class ThrowableError(ex: Exception) extends Error
}

服务方法 return 类型 - 通过 Reader 提供对环境的访问,通过 Writer 产生一些输出,提供对 GameState 的访问并产生方法结果 -错误或某种类型

type Result[T] = ReaderWriterState[Environment, List[Output], State, Error \/ T]

只是一个关于如何实现服务的例子

object UserServiceImpl extends UserService {
  def findPlayer(id: Long): Result[Player] = ReaderWriterState { (env, state) =>
    (
      Nil, 
      \/-(Player(id, "name")), 
      state
    )
  }
  def updatePlayer(player: Player): Result[Player] = ReaderWriterState { (env, state) =>
    (
      List(Output.Log), 
      \/-(player.copy(name = "updated")), 
      state
    )
  }
}

上面提到的场景是(不会编译):

val (out, res, state) = (for {
  playerOrError <- userService.findPlayer(1L)         //How to short-circuit if findPlayer returns left either?
  updated <- userService.updatePlayer(playerOrError)  //How to transform playerOrError to right projection and pass it here?
} yield player).run(GameEnvironment, GameState)

所以,我的问题是:

  1. 如果findPlayer:RWS return 还剩findPlayer:RWS怎么短路?
  2. 如何将playerOrError转换为右投影并传递到这里?

看来我可以尝试以某种方式使用 transformer,但无法理解它。

谢谢!

使用ReaderWriterStateT:

type Result[T] = ReaderWriterStateT[Either[Error, ?], Environment, List[Output], State, T]

相当于

(Environment, State) => Either[Error, (List[Output], T, State)]

这也意味着在发生错误的情况下,不会写入任何输出,也不会更改任何状态。

如果您真的想保持与现有的 Result 相同的结构,请使用

type Result[T] = EitherT[ReaderWriterState[Environment, List[Output], State, ?], Error, T]

感谢@tomas,它在左类型的情况下工作并退出。

结果代码如下:

type ErrorOr[+T] = Error \/ T
type Result[T] = ReaderWriterStateT[ErrorOr, Environment, List[Output], State, T]

object UserServiceImpl extends UserService {

  def findPlayer(id: Long): Result[Player] = ReaderWriterStateT { (env, state) =>
    val player = Player(1L, "name")
    \/-((List.empty[Output], player, state))
  }

  def updatePlayer(player: Player): Result[Player] = ReaderWriterStateT { (env, state) =>
    \/-((List.empty[Output], player.copy(name = "updated"), state))
  }

}

val userService = UserServiceImpl

val result = (for {
  player <- userService.findPlayer(1L)
  updated <- userService.updatePlayer(player)
} yield updated).run(GameEnvironment, GameState)

result match {
  case \/-((out, player, state)) => println(player)
  case -\/(error) => println(error)
}