Last active
November 1, 2022 21:04
-
-
Save mantask/5c6ea89ad9274c7255571a35e705b360 to your computer and use it in GitHub Desktop.
Testing RiskEvaluationResult monad for composing multiple risk check functions
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 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