Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@raulraja
Last active November 9, 2018 11:18
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save raulraja/f94af921749b6c0d67547f1d2398d3df to your computer and use it in GitHub Desktop.
Save raulraja/f94af921749b6c0d67547f1d2398d3df to your computer and use it in GitHub Desktop.
Type Refinement with Type Classes and ApplicativeError
package arrow.refined
import arrow.Kind
import arrow.core.*
import arrow.data.*
import arrow.effects.validated.positiveInt.plus
import arrow.effects.validated.positiveInt.positive
import arrow.extension
import arrow.instances.either.applicativeError.applicativeError
import arrow.instances.listk.traverse.traverse
import arrow.instances.nonemptylist.semigroup.semigroup
import arrow.instances.validated.applicativeError.applicativeError
import arrow.typeclasses.ApplicativeError
import arrow.typeclasses.Traverse
/** Arrow refined library */
data class RefinedPredicateException(val msg: String) : IllegalArgumentException(msg)
interface Refinement<F, A> {
fun applicativeError(): ApplicativeError<F, Nel<RefinedPredicateException>>
fun A.refinement(): Boolean
fun invalidValueMsg(a: A): String
fun refine(value: A): Kind<F, A> = applicativeError().run {
value.refinement()
.maybe { just(value) }
.getOrElse { raiseError(Nel.of(RefinedPredicateException(invalidValueMsg(value)))) }
}
fun <B> refine(value: A, f: (A) -> B): Kind<F, B> =
applicativeError().run { refine(value).map(f) }
fun <B> refine2(a: A, b: A, f: (Tuple2<A, A>) -> B): Kind<F, B> =
applicativeError().run {
map(refine(a), refine(b), f)
}
fun <B> refine3(a: A, b: A, c: A, f: (Tuple3<A, A, A>) -> B): Kind<F, B> =
applicativeError().run {
map(refine(a), refine(b), refine(c), f)
}
fun <B> refine4(a: A, b: A, c: A, d: A, f: (Tuple4<A, A, A, A>) -> B): Kind<F, B> =
applicativeError().run {
map(refine(a), refine(b), refine(c), refine(d), f)
}
fun <G, B> refineTraverse(value: Kind<G, A>, traverse: Traverse<G>, f: (A) -> B): Kind<F, Kind<G, B>> =
traverse.run { value.traverse(applicativeError()) { refine(it, f) } }
fun <B> refineTraverseList(elements: List<A>, f: (A) -> B): Kind<F, List<B>> =
applicativeError().run {
refineTraverse(elements.k(), ListK.traverse(), f).map { it.fix() }
}
fun <G> refineSequence(value: Kind<G, A>, traverse: Traverse<G>): Kind<F, Kind<G, A>> =
refineTraverse(value, traverse, ::identity)
fun refineSequenceList(elements: List<A>): Kind<F, List<A>> =
applicativeError().run {
refineSequence(elements.k(), ListK.traverse()).map { it.fix() }
}
}
/** Included with Arrow but similar to what a user would define for a custom data type **/
interface PositiveInt<F> : Refinement<F, Int> {
override fun Int.refinement(): Boolean =
this > 0
override fun invalidValueMsg(a: Int): String =
"$a should be > 0"
fun Int.positive(): Kind<F, Int> =
refine(this)
fun <B> Int.positive(f: Int.() -> B): Kind<F, B> =
refine(this, f)
fun positive(elements: List<Int>): Kind<F, List<Int>> =
refineSequenceList(elements)
fun <B> positive(elements: List<Int>, f: (Int) -> B): Kind<F, List<B>> =
refineTraverseList(elements, f)
operator fun Kind<F, Int>.plus(b: Int): Kind<F, Int> =
applicativeError().run { map { it + b } }
operator fun Kind<F, Int>.plus(b: Kind<F, Int>) =
applicativeError().run { map(this@plus, b) { (a, b) -> a + b } }
operator fun Int.plus(b: Kind<F, Int>): Kind<F, Int> =
applicativeError().run { b.map { this@plus + it } }
}
/** Error accumulating strategy instance based on `Validated` **/
@extension
interface ValidatedPositiveInt : PositiveInt<ValidatedPartialOf<Nel<RefinedPredicateException>>> {
override fun applicativeError(): ApplicativeError<ValidatedPartialOf<Nel<RefinedPredicateException>>, Nel<RefinedPredicateException>> =
Validated.applicativeError(NonEmptyList.semigroup())
}
/** Fail fast strategy instance based on `Either` **/
@extension
interface EitherPositiveInt : PositiveInt<EitherPartialOf<Nel<RefinedPredicateException>>> {
override fun applicativeError(): ApplicativeError<EitherPartialOf<Nel<RefinedPredicateException>>, Nel<RefinedPredicateException>> =
Either.applicativeError()
}
/** test **/
object test {
@JvmStatic
fun main(args: Array<String>) {
println(0.positive() + 1.positive()) //Valid(1)
println(0.positive() + (-1).positive()) //Invalid(e=NonEmptyList(all=[RefinedPredicateException(msg=-1 should be > 0)]))
println(positive(listOf(1, 2, 3)) { it * 10 }) //Valid(a=ListK(list=[10, 20, 30]))
println(positive(listOf(-1, 2, -3)) { it * 10 }) //Invalid(e=NonEmptyList(all=[RefinedPredicateException(msg=-1 should be >= 0), RefinedPredicateException(msg=-3 should be > 0)]))
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment