Skip to content

Instantly share code, notes, and snippets.

@mantask
Last active November 1, 2022 21:04
Show Gist options
  • Save mantask/5c6ea89ad9274c7255571a35e705b360 to your computer and use it in GitHub Desktop.
Save mantask/5c6ea89ad9274c7255571a35e705b360 to your computer and use it in GitHub Desktop.
Testing RiskEvaluationResult monad for composing multiple risk check functions
import java.math.BigDecimal
// Risk evaluation stuff
data class RiskEvaluation(
val status: Status,
val message: String? = null,
) {
enum class Status {
OK, WARN, FAIL,
}
}
data class RiskEvaluationResponse(
val customerDebt: Boolean,
val customerLeasing: Boolean,
val accountContractCount: Boolean,
val contractAmount: Boolean,
)
fun interface RiskEvaluator<T> {
fun evaluate(ctx: T): RiskEvaluationResult
fun <O> combine(other: RiskEvaluator<O>): RiskEvaluator<Pair<T, O>> = RiskEvaluator { (t, o) ->
evaluate(t).flatMap { other.evaluate(o) }
}
}
typealias CheckType = String
typealias RiskEvaluationMap = Map<CheckType, RiskEvaluation>
/**
* A monadic data wrapper around risk evaluation result (ie checkType->riskEvaluation)
*/
class RiskEvaluationResult(private val evalMap: RiskEvaluationMap) {
/** A monoidic COMBINE operation (selectMany in C#) */
fun flatMap(f: (RiskEvaluationMap) -> RiskEvaluationResult): RiskEvaluationResult =
RiskEvaluationResult(evalMap + f(evalMap).evalMap)
/** Unbox (resolve, yield) a wrapped value (selectOne in C#) */
fun <T> map(f: (RiskEvaluationMap) -> T): T =
f.invoke(evalMap)
fun get(): RiskEvaluationMap =
map { it }
companion object {
/** A monoidic UNIT */
fun empty() = RiskEvaluationResult(emptyMap())
}
}
// some DTOs
data class Customer(
val firstName: String,
val lastName: String,
)
data class Account(
val numberOfContracts: Int,
)
data class Contract(
val amount: BigDecimal,
)
// builders to inject dependencies
interface Service<T> {
fun refresh(t: T) = t
}
val customerService = object : Service<Customer> {}
val accountService = object : Service<Account> {}
val contractService = object : Service<Contract> {}
fun emptyEvaluator() =
RiskEvaluator<Unit> {
RiskEvaluationResult.empty()
}
fun customerEvaluator(service: Service<Customer>) =
RiskEvaluator<Customer> {
service.refresh(it)
// TODO(MK): execute evaluation
RiskEvaluationResult(
mapOf(
"customerDebt" to RiskEvaluation(RiskEvaluation.Status.OK),
"customerLeasing" to RiskEvaluation(RiskEvaluation.Status.FAIL)
)
)
}
fun accountEvaluator(service: Service<Account>) =
RiskEvaluator<Pair<Account, Boolean>> { (account, noDebt) ->
service.refresh(account)
// TODO(MK): execute evaluation
RiskEvaluationResult(mapOf("accountContractCount" to RiskEvaluation(if (noDebt) RiskEvaluation.Status.OK else RiskEvaluation.Status.FAIL)))
}
fun contractEvaluator(service: Service<Contract>) =
RiskEvaluator<Contract> {
service.refresh(it)
// TODO(MK): execute evaluation
RiskEvaluationResult(mapOf("contractAmount" to RiskEvaluation(RiskEvaluation.Status.WARN)))
}
fun evaluator(
customerService: Service<Customer>,
accountService: Service<Account>,
contractService: Service<Contract>,
) = emptyEvaluator()
.combine(customerEvaluator(customerService))
.combine(contractEvaluator(contractService))
.combine(accountEvaluator(accountService))
fun main() {
val customer = Customer("Mantas", "Kanaporis")
val account = Account(14)
val contract = Contract(BigDecimal(10_000.00))
// comprehend monads by chaining
val response = customerEvaluator(customerService).evaluate(customer)
.flatMap { contractEvaluator(contractService).evaluate(contract) }
.flatMap { evalMap ->
val noDebt = evalMap["customerDebt"]?.status == RiskEvaluation.Status.OK
accountEvaluator(accountService).evaluate(Pair(account, noDebt))
}
.map {
RiskEvaluationResponse(
customerDebt = it["customerDebt"]?.status == RiskEvaluation.Status.OK,
customerLeasing = it["customerLeasing"]?.status == RiskEvaluation.Status.OK,
accountContractCount = it["accountContractCount"]?.status == RiskEvaluation.Status.OK,
contractAmount = it["contractAmount"]?.status == RiskEvaluation.Status.OK,
)
}
println(response)
// .. or execute a combine function
val ctx = Pair(Pair(Pair(Unit, customer), contract), Pair(account, false))
val evalMap = evaluator(customerService, accountService, contractService).evaluate(ctx).get()
evalMap.entries.forEach { (checkType, evaluation) ->
println("${checkType.padEnd(30, '.')}${evaluation.status}")
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment