State

State is a structure that provides a functional approach for handling application state. State<S, A> is an abstraction over S -> Tuple2<S, A>. S represents the state type, and Tuple2<S, A> is the result, with S for the newly updated state and A for the function return.

We can start with a simple example, a function that returns two things, a price and the steps to calculate it. To calculate a price, we need to add  VAT of 20% and apply a discount if the price value goes above some threshold:

import arrow.core.Tuple2
import arrow.core.toT
import arrow.data.State

typealias PriceLog = MutableList<Tuple2<String, Double>>

fun addVat(): State<PriceLog, Unit> = State { log: PriceLog ->
val (_, price) = log.last()
val vat = price * 0.2
log.add("Add VAT: $vat" toT price + vat)
log toT Unit
}

We have a type alias PriceLog for MutableList<Tuple2<String, Double>>. PriceLog will be our State representation; each step represented with Tuple2<String, Double>.

Our first function, addVat(): State<PriceLog, Unit> represents the first step. We write the function using a State builder that receives PriceLog, the state before applying any step and must return a Tuple2<PriceLog, Unit>, we use Unit because we don't need the price at this point:

fun applyDiscount(threshold: Double, discount: Double): State<PriceLog, Unit> = State { log ->
val (_, price) = log.last()
if (price > threshold) {
log.add("Applying -$discount" toT price - discount)
} else {
log.add("No discount applied" toT price)
}
log toT Unit
}

The applyDiscount function is our second step. The only new element that we introduce here are two parameters, one for threshold and the other for discount:

fun finalPrice(): State<PriceLog, Double> = State { log ->
val (_, price) = log.last()
log.add("Final Price" toT price)
log toT price
}

The last step is represented by the function finalPrice(), and now we return Double instead of Unit:

import arrow.data.ev
import arrow.instances.monad
import arrow.typeclasses.binding

fun calculatePrice(threshold: Double, discount: Double) = State().monad<PriceLog>().binding {
addVat().bind() //Unit
applyDiscount(threshold, discount).bind() //Unit
val price: Double = finalPrice().bind()
price
}.ev()

To represent the sequence of steps, we use a monad comprehension and use the State functions sequentially. From one function to the next one, the PriceLog state is flowing implicitly (is just some coroutine continuations magic). At the end, we yield the final price. Adding new steps or switching existing ones is as easy as adding or moving lines:

import arrow.data.run
import arrow.data.runA

fun
main(args: Array<String>) {
val (history: PriceLog, price: Double) = calculatePrice(100.0, 2.0).run(mutableListOf("Init" toT 15.0))
println("Price: $price")
println("::History::")
history
.map { (text, value) -> "$text | $value" }
.forEach(::println)

val bigPrice: Double = calculatePrice(100.0, 2.0).runA(mutableListOf("Init" toT 1000.0))
println("bigPrice = $bigPrice")
}

To use the calculatePrice function, you must provide the threshold and discount values and then invoke the extension function run with an initial state. If you're interested just in the price, you can use runA or for just the history, runS.

Avoid problems using State. Don't confuse the extension function arrow.data.run with the extension function, kotlin.run (imported by default).
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.217.254.118