ReaderWriterState短路[Either]

时间:2017-07-20 14:54:15

标签: scala scalaz monad-transformers

我正在研究游戏原型,尽量做到尽可能纯净。 所有用例都适用于一种情况 -

  1. 尝试在存储中找到播放器
  2. 执行一些业务逻辑
  3. 更新存储空间中的播放器
  4. 虽然更新一个可以产生一些输出 - 记录消息,消息给其他玩家等。
  5. 从另一方面,我们必须访问环境(数据库,资源等),全局游戏状态(不可变游戏配置,种子等)。

    为了将它们组合在一起,我最终得到了像这样的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
    }
    

    服务方法返回类型 - 通过Reader提供对Environment的访问,通过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向左返回?
    2. 如何将playerOrError转换为正确投影并将其传递到此处?
    3. 看起来我可以尝试以某种方式使用变压器,但无法理解它。

      谢谢!

2 个答案:

答案 0 :(得分:1)

使用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]

答案 1 :(得分:0)

感谢@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)
}