3
votes

I'm trying to create some kind of failover behaviour in a composition of "scalaz-core"%"7.2.14" StateT monads.

The StateT monad wraps around EitherT thus it's a monad transformer:

type Error = String
type ErrOrT[T] = Error \/ T
type State[T] = StateT[ErrOrT, String, T]

With these types everything works great - I can leverage the power of Either plus State. I have short-circuit in case of error and can stack my States in one monadic composition:

def func1: State[Int] = ???
def func2: State[Int] = ???
def func3: State[Int] = ???

val stateMonad = for {
  res1 <- func1
  res2 <- func2
  res3 <- func3
} yield res3

In addition I want to create something like tryWithFailover method. It returns original State[T] monad or fallback State[T] monad in case of inner EitherTcontains left:

def tryWithFailover[T](run: State[T])(failover: Error => State[T]): State[T] = ???

So the resulting chain would be:

val stateMonad = for {
  res1 <- func1
  res2 <- tryWithFailover(func2)(err => failover)
  res3 <- func3
} yield res3

If the failover was just a value it would not be a problem. I can use mapT/mapK methods which have an access to inner monad and thus able to check whether the result left or right. In case of left I can recreate inner monad with fallback value. But it's not a value, it's a monad itself and I need something like flatMapT.

Do I miss something, any thought on how it might be done? Failover util function will help me a lot, don't want to break the chain in the middle with explicit run call.


UPD:

Abovementioned failover with value might be like this:

def tryWithFailover[T](run: State[T])(failover: Error => T): State[T] = {
  for {
    lockedState <- State(st => (st, st))
    result <- run.mapT[ErrOrT, T, String] {
      case ok@ \/-(_) => ok
      case -\/(error) => \/-((lockedState, failover(error)))
    }
  } yield result
}

UPD2:

Bad implementation which breaks overall monadic chain with run in the middle:

def tryWithFailover[T](run: State[T])(failover: Error => State[T]): State[T] = {
  for {
    lockedState <- State(st => (st, st))
    result <- run.mapT[ErrOrT, T, String] {
      case ok@ \/-(_) => ok
      case -\/(error) => failover(error).run(lockedState)
    }
  } yield result
}
1

1 Answers

0
votes

After some considerations I came to the conclusion that mentioned approach is not bad:

def tryWithFailover[T](run: State[T])(failover: Error => State[T]): State[T] = {
  for {
    lockedState <- State(st => (st, st))
    result <- run.mapT[ErrOrT, T, String] {
      case ok@DRight(_) => ok
      case DLeft(error) => failover(error).run(lockedState)
    }
  } yield result
}

May be someday somebody will correct my answer.