从 ReaderWriterState[Either] 短路
Short-circuit from ReaderWriterState[Either]
我正在制作一个游戏原型,并尽量做到纯粹。
所有用例都适合一个场景 -
- 尝试在存储中找到播放器
- 执行一些业务逻辑
- 更新存储中的播放器
- 虽然更新一个可以产生一些输出 - 日志消息、给其他玩家的消息等。
从另一侧必须访问环境(数据库、资源等)、全局游戏状态(不可变的游戏配置、种子等)。
为了将它们结合在一起,我最终得到了 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)
所以,我的问题是:
- 如果findPlayer:RWS return 还剩findPlayer:RWS怎么短路?
- 如何将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)
}
我正在制作一个游戏原型,并尽量做到纯粹。 所有用例都适合一个场景 -
- 尝试在存储中找到播放器
- 执行一些业务逻辑
- 更新存储中的播放器
- 虽然更新一个可以产生一些输出 - 日志消息、给其他玩家的消息等。
从另一侧必须访问环境(数据库、资源等)、全局游戏状态(不可变的游戏配置、种子等)。
为了将它们结合在一起,我最终得到了 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)
所以,我的问题是:
- 如果findPlayer:RWS return 还剩findPlayer:RWS怎么短路?
- 如何将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)
}