Eugene Zhulkov Eugene Zhulkov - 1 year ago 58
Scala Question

Short-circuit from ReaderWriterState[Either]

I'm working on a game prototype and trying to be as pure as possible.
All use-cases are fit into one scenario -


  1. Try to find player in a storage

  2. Perform some business logic

  3. Update player in the storage

  4. While updating one can produce some output - Log messages, Messages to another players, etc.



From another side one have to get access to Environment (databases, resources, etc), global game state (immutable game configs, seeds etc).

To tie it all together I ended up with scalaz7 ReaderWriterState monad like this:

Some definitions:

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
}


Service methods return type - provides access to Environment via Reader, produces some output via Writer, gives access to GameState and produces method result - Error or Some type

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


Just an example on how Service might be implemented

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
)
}
}


Above mentioned scenario is (won't compile):

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)


So, my questions are:


  1. How to short-circuit if findPlayer:RWS returns left either?

  2. How to transform playerOrError to right projection and pass it here?



It looks like I can try to use transformer somehow but can't get my head around it.

Thanks!

Answer Source

Use ReaderWriterStateT:

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

which is equivalent to

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

which also means that in case of error, no output is written and no state is changed.

If you really want to keep the same structure of Result that you have, use

type Result[T] = EitherT[ReaderWriterState[Environment, List[Output], State, ?], Error, T]
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download