Skip to content

Instantly share code, notes, and snippets.

@richard-gibson
Created October 25, 2018 21:57
Show Gist options
  • Save richard-gibson/61596733a7cb65bd75139b5eabe4e36c to your computer and use it in GitHub Desktop.
Save richard-gibson/61596733a7cb65bd75139b5eabe4e36c to your computer and use it in GitHub Desktop.
Arrow Either Validation examples
package co.brightcog.validation
import arrow.core.*
import arrow.data.*
import arrow.typeclasses.*
sealed class Failure {
data class EmptyString(val fieldName: String) : Failure()
data class InvalidCity(val fieldName: String) : Failure()
data class ValueOutOfRange(val i: Int) : Failure()
}
inline fun <A, B> Either<A, B>.valid(pred: (B) -> Boolean, onFail: () -> A): Either<A, B> = when (this) {
is Either.Left<A, B> -> this
is Either.Right<A, B> -> if (pred(this.b)) this else Either.Left(onFail())
}
inline fun <A, B> Either<A, B>.toValidatedNel(): ValidatedNel<A, B> =
this.fold({ Invalid(Nel.of(it)) }, { Valid(it) })
val zip = listOf("00111", "001122", "000333")
val cities = listOf("Dublin", "London", "Madrid")
data class Employee(val name: String, val zipCode: String, val city: String, val salary: Int)
fun nonBlank(fieldName: String, data: String): Either<Failure, String> =
data.right().valid({ it.isNotEmpty() }) { Failure.EmptyString("$fieldName cannot be blank") }
fun inRange(lower: Int, upper: Int, data: Int): Either<Failure, Int> =
data.right().valid({ it in lower..upper }) { Failure.ValueOutOfRange(data) }
fun validZip(data: String): Either<Failure, String> =
data.right().valid({ it in zip }) { Failure.InvalidCity(data) }
fun validCities(data: String): Either<Failure, String> =
data.right().valid({ it in cities }) { Failure.InvalidCity(data) }
fun empValidatedFromApp(name: String, zipCode: String, city: String, salary: Int): Validated<Nel<Failure>, Employee> =
Validated.applicative<Nel<Failure>>(NonEmptyList.semigroup()).map(
nonBlank("name", name).toValidatedNel(),
validZip(zipCode).toValidatedNel(),
validCities(city).toValidatedNel(),
inRange(10, 20, salary).toValidatedNel()) {
(n, z, c, s) -> Employee(n, z, c, s)
}.fix()
fun empEitherFromMonad(name: String, zipCode: String, city: String, salary: Int): Either<Failure, Employee> =
Either.monadError<Failure>().binding {
val n = nonBlank("name", name).bind()
val z = validZip(zipCode).bind()
val c = validCities(city).bind()
val s = inRange(10, 20, salary).bind()
Employee(n, z, c, s)
}.fix()
fun empEitherFromApp(name: String, zipCode: String, city: String, salary: Int): Either<Failure, Employee> =
Either.applicative<Failure>().map(
nonBlank("name", name),
validZip(zipCode),
validCities(city),
inRange(10, 20, salary)) {
(n, z, c, s) -> Employee(n, z, c, s)
}.fix()
fun main(a: Array<String>) {
println(empEitherFromApp("foo", "00111", "Dublin", 15))
println(empEitherFromApp("", "00", "Washington", 17500))
println(empEitherFromApp("foo", "00", "Washington", 17500))
println()
println()
println(empEitherFromMonad("foo", "00111", "Dublin", 15))
println(empEitherFromMonad("", "00", "Washington", 17500))
println(empEitherFromMonad("foo", "00", "Washington", 17500))
println()
println()
println(empValidatedFromApp("foo", "00111", "Dublin", 15))
println(empValidatedFromApp("", "00", "Washington", 17500))
println(empValidatedFromApp("foo", "00", "Washington", 17500))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment