-
-
Save channingwalton/2846428 to your computer and use it in GitHub Desktop.
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)) | |
*/ |
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.
I'll make a snippet
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
Aha! That does look interesting. Thanks, I'll try it out.
After browsing Scalaz 6 code base, this is not going to work for this reason:
- There is no StateT Applicative instance (it easy to implement) but
- 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)
- 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 :)
Please don't apologise, you are being very helpful. I think I'll look at scalaz 7.
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
Thanks, I was thinking of something similar. This really helps :)
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.