tavish pegram
07/24/2020, 4:33 PMConstrained<T>
as a DSL for domain modeling (inspired by this and other similar talks ). What I would really love is to make it a one liner to create constrained types with private constructors so that it becomes effortless to use in domain modeling. Currently in Kotlin the way to implement something like this is
data class Natural private constructor(val value: Int) {
companion object {
fun fromInt(v: Int): Either<NaturalFromIntFailure, Natural> =
when (v < 0) {
true -> NaturalFromIntFailure.IntMustBeGTEZero.left()
false -> Natural(v).right()
}
}
}
sealed class NaturalFromIntFailure {
object IntMustBeGTEZero: NaturalFromIntFailure()
}
which is fine I guess, but if people want to get in the habit of using constrained domain types for every type in a domain (as is recommended), this starts to become tedious and starts to lead toward cutting corners on these types.
From reading about dependent types, it would be awesome to do something like
typealias Natural = Constrained<Int><{
when (it < 0) {
true -> NaturalFromIntFailure.IntMustBeGTEZero.left()
false -> Natural(it).right()
}
}>
val x = Natural(-1) // Doesnt compile
val y = Natural.new(-1) // Left(NaturalFromIntFailure.IntMustBeGTEZero)
Alas messing around hasn’t gotten me close to this at all. This is the best I’ve gotten so far which is still a little too verbose , doesn’t have a consistent interface for constrained types, and has some public “private” methods :(
/*
----------------------------------------------------------------------------------------------------
Generic bits
*/
data class Constrained<A> private constructor(val a: A) {
companion object {
fun <A, E>validateNew(a: A, validator: (A) -> Either<E, A>): Either<E, Constrained<A>> =
validator(a).fold(
{ it.left() },
{ Constrained(a).right() }
)
}
}
/*
----------------------------------------------------------------------------------------------------
Consumer Implementation
*/
typealias Natural = Constrained<Int>
sealed class NaturalFromIntFailure {
object IntMustBeGTEZero: NaturalFromIntFailure()
}
fun natural(a: Int) = Natural.validateNew<Int, NaturalFromIntFailure>(a) {
when (it < 0) {
true -> NaturalFromIntFailure.IntMustBeGTEZero.left()
false -> it.right()
}
}
/*
----------------------------------------------------------------------------------------------------
Examples
*/
val a = Natural(-1) // Doesn't compile
val b = natural(1) // Result: Right(Natural(1))
val c = natural(-1) // Result: Left(NaturalFromIntFailure.IntMustBeGTEZero)
Does anyone have any suggestions on if there are any tools available in arrow that could move this closer to the ideal state?raulraja
07/24/2020, 4:44 PMtavish pegram
07/24/2020, 4:49 PMraulraja
07/24/2020, 4:50 PMraulraja
07/24/2020, 4:51 PMraulraja
07/24/2020, 4:51 PMinline fun Int.portNumber(): Int =
+(0..65535)..this
raulraja
07/24/2020, 4:52 PMthis
had to been prechecked by a logical precondition in the scope data flow before it’s safe to call otherwise a nullable, Either or Validated runtime api like yours is providedraulraja
07/24/2020, 4:52 PMraulraja
07/24/2020, 4:53 PMraulraja
07/24/2020, 4:53 PMraulraja
07/24/2020, 4:54 PMraulraja
07/24/2020, 4:54 PMraulraja
07/24/2020, 4:54 PMtavish pegram
07/24/2020, 4:56 PMraulraja
07/24/2020, 4:57 PM