Skip to content

Instantly share code, notes, and snippets.

@channingwalton
Created May 31, 2012 21:28
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save channingwalton/2846428 to your computer and use it in GitHub Desktop.
Save channingwalton/2846428 to your computer and use it in GitHub Desktop.
State Monad and Validation with Scalaz
import scalaz._
import Scalaz._
/**
* Use the state monad to 'process a trade' and store the new trade.
* As well as processing the trade, handle validation errors.
*/
object StateMonad extends App {
case class Trade(info: String)
type Store = List[Trade]
// a function that takes a new trade and processes it yielding new state and validation
val newTrade = (newTrade: Trade) ⇒
for (
_ ← log("New Trade");
accepted ← accept(newTrade);
_ ← log("Hedging New Trade");
hedged ← hedge(newTrade);
_ ← log("Validating portfolio");
portfolio ← validatePortfolio;
_ ← log("New trade processed")
) yield (accepted.liftFailNel |@| hedged.liftFailNel |@| portfolio.liftFailNel) {_ + _ + _}
println("Haven't done anything yet!")
// assume some existing state
var globalState: Store = Nil
// exercise the newTrade function with the existing state
val (newState, validation) = newTrade(Trade("Big Trade"))(globalState)
// assign the new state to our global state if the validation says its ok
globalState = validation.fold(failures ⇒ { println(failures); globalState }, msg ⇒ newState)
println("Store contains " + globalState)
// does nothing but print a messages and return the state its given
def log(m: String) = state[Store, Unit](s ⇒ (s, println(m)))
// accepts a trade putting it into the store
def accept(newTrade: Trade) = state[Store, Validation[String, String]](s ⇒ (newTrade :: s, "trade accepted".success))
// hedge against the new trade - apparently its all the rage
def hedge(against: Trade) = state[Store, Validation[String, String]](s ⇒ (Trade("Hedge Trade against " + against) :: s, "hedge trade step".success))
// validate the portfolio doing nothing with the state
def validatePortfolio = state[Store, Validation[String, String]](s ⇒ {
if (s.size > 10) (s, "Portolio is too big".fail)
else (s, "All ok".success)
})
}
/*
Running this produces:
Haven't done anything yet!
New Trade
Hedging New Trade
Validating portfolio
New trade processed
Store contains List(Trade(Hedge Trade against Trade(Big Trade)), Trade(Big Trade))
*/
@YoEight
Copy link

YoEight commented Jun 1, 2012

Are you going to deal with a lot of validation during your stateful computation ? Because, you could use State monad transformer, which is also an Applicative Functor and keep your validation error accumulated with a simpler syntax.

@channingwalton
Copy link
Author

That sounds interesting. The validation part is exactly what I was puzzled about. I am not sure I know how to do what you are suggesting though.

@YoEight
Copy link

YoEight commented Jun 1, 2012

I'll make a snippet

@channingwalton
Copy link
Author

channingwalton commented Jun 1, 2012 via email

@YoEight
Copy link

YoEight commented Jun 1, 2012

def accept(newTrade: Trade): StateT[({ type f[x] =  Validation[String, x]})#f, Store, String] = stateT(s => (newTrade :: s, "trade accepted").success)

def hedge(against: Trade): StateT[({ type f[x] = Validation[String, x]})#f, Store, String] = stateT(s => (Trade("Hedge Trade against " + against) :: s, "hedge trade step").success)

def validatePortfolio: StateT[({ type f[x] = Validation[String, x]})#f, Store, String]  = stateT(s => {
  if (s.size > 10) "Portolio is too big".fail
  else (s, "All ok").success
})

newTrade becomes:

val newTrade = (newTrade: Trade) => accepted |@| hedged |@| portfolio { _ + _ + _ }

Here's how you can extract value (a fold would be nicer):

newTrade(Trade("Big Trade"))(globalState) match {
  case Failure(e) => //handle accumulated errors
  case Success((store, report)) => ...
}

More type annotations may be needed

@channingwalton
Copy link
Author

Aha! That does look interesting. Thanks, I'll try it out.

@YoEight
Copy link

YoEight commented Jun 1, 2012

After browsing Scalaz 6 code base, this is not going to work for this reason:

  1. There is no StateT Applicative instance (it easy to implement) but
  2. Validation applicative is implemented through flatMap, so it can't capture the Semigroup instance of your error type (it's not the case in Scalaz 7)
  3. you can't implement StateT Applicative instance if Validation is just an Applicative Functor because you need to "extract" previous state value and applying it to the next computation, Applicative can't capture that, so you need Validation to be a Monad but this leads us to 2)

I am sorry for wasting your time, I'll find another way :)

@channingwalton
Copy link
Author

Please don't apologise, you are being very helpful. I think I'll look at scalaz 7.

@YoEight
Copy link

YoEight commented Jun 5, 2012

I Think you should use stateful computation as

S => Validation[E, (S, A)]  // like a monad transformer

not like

S => (s, Validation[E, A])

Like so you'll sure your state can't be modified if your previous validation is a failure.

You just need those methods

def modifyM[F[_], S](f: S => S)(implicit: Pure[F]): StateT[F, S, Unit] = stateT(s => (f(s), ()).pure[F]) 

def liftM[F[_], S, A](fa: F[A])(implicit: Functor[F]): StateT[F, S, A] = stateT(s => fa.map(a => (s, a)))

def initM[F[_], S, S](implicit P: Pure[F]): StateT[F, S, S] = stateT(s => (s, s).pure[F])

newTrade becomes

type VS[A] = Validation[String, A]

val newTrade: (newTrade: Trade) => for {
  _ <- modifyM[VS, Store](newTrade ::  _) // accepted
  _ <- modifyM[VS, Store](Trade("Hedge Trade against " + newTrade) ::  _) //hedge
  s <- initM[VS, Store]
  r <- if (s.size > 10) liftM[VS, String]("Portolio is too big".fail)
        else liftM[VS, String]("All ok".success)
} yield r

@channingwalton
Copy link
Author

Thanks, I was thinking of something similar. This really helps :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment