我正在研究游戏原型,尽量做到尽可能纯净。 所有用例都适用于一种情况 -
从另一方面,我们必须访问环境(数据库,资源等),全局游戏状态(不可变游戏配置,种子等)。
为了将它们组合在一起,我最终得到了像这样的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)
所以,我的问题是:
看起来我可以尝试以某种方式使用变压器,但无法理解它。
谢谢!
答案 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)
}