scala and state monad: no free lunch part 2
As a follow up to my last post about no free lunch I wanted to show more state monad usage and recognize that you can change function return types in the middle of a for-comprehension. This allows each function to return different types of values from the computation along the way.
In the code below, you compose a function by using the for-comprehension. The yield part of the for-comprehension is a State object. When that state object is called, it returns a tuple of (state, value). You can use ._1 or ._2 to obtain the value of interest.
The last composed function shows an example of changing the state type (instead of changing the returned value type mentioned above) in the middle of the for-comprehension using scalaz IndexedState's iPut and iModify. This allows you to change the type of state as you go to adapt to different function's API needs.
The entire article is at gisthub.
In the code below, you compose a function by using the for-comprehension. The yield part of the for-comprehension is a State object. When that state object is called, it returns a tuple of (state, value). You can use ._1 or ._2 to obtain the value of interest.
The last composed function shows an example of changing the state type (instead of changing the returned value type mentioned above) in the middle of the for-comprehension using scalaz IndexedState's iPut and iModify. This allows you to change the type of state as you go to adapt to different function's API needs.
The entire article is at gisthub.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import scalaz._ | |
import scalaz.State._ | |
case class Session(version: Int = 0) | |
// Create a new output string and increment session version. | |
def func1(arg: String) = State((x: Session) => (x.copy(version = x.version + 1), arg + "-" + x.version)) | |
val composedFunction1: State[Session, String] = for { | |
// Because we gave this val an explicit type, init does not need the type specification | |
_ <- init | |
// Increment the state and return state+value. Essentially, throw away the value. | |
_ <- func1("joe") | |
// Modify the state directly. | |
_ <- modify { s: Session => Session(100) } | |
// Increment the state and return state+value. Essentially, throw away the value. | |
_ <- func1("alice") | |
// Modify the state directly again! | |
_ <- put(Session(200)) | |
// Increment the state and return state+value. Keep the value this time. | |
x <- func1("nathan") | |
} yield x | |
println("composedFunction1: " + composedFunction1(Session(0))) | |
// Now create a composition where the returned value changes. Since the returned | |
// value does not really matter when the state propagates (the state needs to be the | |
// same between for steps or have conversions available) so the returned value can | |
// change each step of the way. | |
def funcReturnInt(arg: String): State[Session, Int] = | |
State((x: Session) => (x.copy(version=x.version+1), arg.toInt)) | |
def funcReturnString(arg: String): State[Session, String] = | |
State((x: Session) => (x.copy(version=x.version+1), arg + "-" + x.version)) | |
val composedFunction2 = for { | |
// by using put first, we effectively ignore any input value | |
_ <- put(Session(0)) | |
_ <- func1("joe") | |
_ <- funcReturnInt("30") | |
x <- funcReturnString("alice") | |
} yield x | |
// Null is used because we have to call the composed function with | |
// an argument. null is used for illustration purposes. | |
println("composedFunction2: " + composedFunction2(null)) | |
// We can also compose the two composed functions together. We explicitly | |
// make the type known (State[Session, String]) for illustration purposes | |
// but the compiler can figure it out on its own. | |
def composedFunction1and2 = for { | |
// Put the argument given by the programmer function into a state object | |
_ <- init[Session] | |
_ <- composedFunction1 | |
x <- composedFunction2 | |
} yield x | |
// Since composedFunction2 resets the state at the start of its function, | |
// we should expect the saem result as composedFunction2. | |
println("composedFunction1and2: " + composedFunction1and2(Session(0))) | |
/** Prints: | |
composedFunction1: (Session(201),nathan-200) | |
composedFunction2: (Session(3),alice-2) | |
composedFunction1and2: (Session(3),alice-2) | |
**/ |
Comments
Post a Comment