Last active
July 27, 2022 09:07
-
-
Save vihangpatil/0e11819ef92ffe2298edeaf476737901 to your computer and use it in GitHub Desktop.
Kotlin custom monad bind() for Arrow-kt Validated returning ValidatedNei similar to either, option and nullable.
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 arrow.core.NonEmptyList | |
import arrow.core.Validated | |
import arrow.core.ValidatedNel | |
import arrow.core.invalid | |
import arrow.core.nonEmptyListOf | |
import arrow.core.valid | |
interface ValidatedEffect<INVALID, VALID> { | |
suspend fun toValidatedNel(): ValidatedNel<INVALID, VALID> | |
} | |
interface ValidatedEffectScope<INVALID, VALID> { | |
suspend fun shift(invalid: INVALID) | |
suspend fun Validated<INVALID, VALID>.bind(): Validated<INVALID, VALID> { | |
when (this) { | |
is Validated.Valid -> value | |
is Validated.Invalid -> shift(value) | |
} | |
return this | |
} | |
} | |
@Suppress("ClassName") | |
object validated { | |
suspend inline operator fun <INVALID, VALID> invoke( | |
crossinline f: suspend ValidatedEffectScope<INVALID, VALID>.() -> VALID | |
): ValidatedNel<INVALID, VALID> = validatedEffect(f).toValidatedNel() | |
} | |
inline fun <INVALID, VALID> validatedEffect( | |
crossinline f: suspend ValidatedEffectScope<INVALID, VALID>.() -> VALID | |
): ValidatedEffect<INVALID, VALID> = object : ValidatedEffect<INVALID, VALID> { | |
override suspend fun toValidatedNel(): ValidatedNel<INVALID, VALID> { | |
var invalidList: NonEmptyList<INVALID>? = null | |
val effectScope = object : ValidatedEffectScope<INVALID, VALID> { | |
override suspend fun shift(invalid: INVALID) { | |
invalidList = invalidList?.let { it + invalid } ?: nonEmptyListOf(invalid) | |
} | |
} | |
val valid = f(effectScope) | |
return invalidList?.invalid() ?: valid.valid() | |
} | |
} |
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 arrow.core.NonEmptyList | |
import arrow.core.Validated | |
import arrow.core.ValidatedNel | |
import arrow.core.invalid | |
import arrow.core.nonEmptyListOf | |
import arrow.core.valid | |
import kotlinx.coroutines.runBlocking | |
import java.util.* | |
data class Identity( | |
val name: String, | |
val age: Int, | |
val countryCode: String, | |
) | |
sealed class InvalidIdentity( | |
val errorMessage: String | |
) { | |
object BlankName : InvalidIdentity("name is blank") | |
class LowAge(age: Int) : InvalidIdentity("age($age) < 18") | |
object BlankCountryCode : InvalidIdentity("country code is blank") | |
class CountryCodeNotFound(code: String) : InvalidIdentity("country code($code) not found") | |
} | |
suspend fun validate( | |
identity: Identity | |
): ValidatedNel<InvalidIdentity, Identity> { | |
return validated { | |
if (identity.age < 18) { | |
InvalidIdentity.LowAge(identity.age).invalid().bind() | |
} | |
if (identity.countryCode.isBlank()) { | |
InvalidIdentity.BlankCountryCode.invalid().bind() | |
} | |
if (!Locale.getISOCountries(Locale.IsoCountryCode.PART1_ALPHA3).contains(identity.countryCode.uppercase())) { | |
InvalidIdentity.CountryCodeNotFound(identity.countryCode).invalid().bind() | |
} | |
if (!Locale.getISOCountries(Locale.IsoCountryCode.PART1_ALPHA3).contains(identity.countryCode.uppercase())) { | |
InvalidIdentity.CountryCodeNotFound(identity.countryCode).invalid().bind() | |
} | |
if (identity.name.isBlank()) { | |
InvalidIdentity.BlankName.invalid().bind() | |
} | |
identity | |
} | |
} | |
fun main() { | |
runBlocking { | |
validate( | |
Identity( | |
name = "", | |
age = 0, | |
countryCode = "" | |
) | |
).fold( | |
{ errors -> errors.forEach { error -> println("error: ${error.errorMessage}") } }, | |
{ identity -> println("valid: $identity") } | |
) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment