diff --git a/README.md b/README.md index 14c85ec..cdbfb08 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -Module Arrow Exact +# Arrow Exact Arrow Exact allows you to use Kotlin's type system to enforce exactness of data structures. @@ -11,17 +11,19 @@ example easily create a `NotBlankString` type that is a `String` that is not bla the Arrow's `Raise` DSL to `ensure` the value is not blank. ```kotlin +import arrow.core.raise.Raise import arrow.core.raise.ensure import arrow.exact.Exact import arrow.exact.ExactError -import arrow.exact.exact @JvmInline -value class NotBlankString private constructor(val value: String) { - companion object : Exact by exact({ - ensure(raw.isNotBlank()) { ExactError("Cannot be blank.") } - NotBlankString(raw) - }) +value class NotBlankString private constructor(val value: String) { + companion object : Exact { + override fun Raise.spec(raw: String): NotBlankString { + ensure(raw.isNotBlank()) { ExactError("Cannot be blank.") } + return NotBlankString(raw) + } + } } ``` @@ -48,31 +50,54 @@ Either.Left(ExactError(message=Cannot be blank.)) +You can also define `Exact` by using Kotlin delegation. + +```kotlin +@JvmInline +value class NotBlankString private constructor(val value: String) { + companion object : Exact by Exact({ + ensure(it.isNotBlank()) { ExactError("Cannot be blank.") } + NotBlankString(it) + }) +} +``` + + You can define a second type `NotBlankTrimmedString` that is a `NotBlankString` that is also -trimmed. Since the `exact` constructor allows us to compose `Exact` instances, we can easily +trimmed. Since the `ensure` allows us to compose `Exact` instances, we can easily reuse the `NotBlankString` type. ```kotlin @JvmInline -value class NotBlankTrimmedString private constructor(val value: String) { - companion object : Exact by exact({ - val notBlank = ensure(NotBlankString) - NotBlankTrimmedString(notBlank.value.trim()) - }) +value class NotBlankTrimmedString private constructor(val value: String) { + companion object : Exact { + override fun Raise.spec(raw: String): NotBlankTrimmedString { + ensure(raw, NotBlankString) + return NotBlankTrimmedString(raw.trim()) + } + } } ``` - + diff --git a/guide/src/commonMain/kotlin/examples/example-exact-01.kt b/guide/src/commonMain/kotlin/examples/example-exact-01.kt index db8f4da..38254ce 100644 --- a/guide/src/commonMain/kotlin/examples/example-exact-01.kt +++ b/guide/src/commonMain/kotlin/examples/example-exact-01.kt @@ -1,17 +1,19 @@ // This file was automatically generated from Exact.kt by Knit tool. Do not edit. package arrow.exact.knit.example.exampleExact01 +import arrow.core.raise.Raise import arrow.core.raise.ensure import arrow.exact.Exact import arrow.exact.ExactError -import arrow.exact.exact @JvmInline value class NotBlankString private constructor(val value: String) { - companion object : Exact by exact({ - ensure(raw.isNotBlank()) { ExactError("Cannot be blank.") } - NotBlankString(raw) - }) + companion object : Exact { + override fun Raise.spec(raw: String): NotBlankString { + ensure(raw.isNotBlank()) { ExactError("Cannot be blank.") } + return NotBlankString(raw) + } + } } fun example() { diff --git a/guide/src/commonMain/kotlin/examples/example-exact-02.kt b/guide/src/commonMain/kotlin/examples/example-exact-02.kt index 8a5b2c6..d8df775 100644 --- a/guide/src/commonMain/kotlin/examples/example-exact-02.kt +++ b/guide/src/commonMain/kotlin/examples/example-exact-02.kt @@ -4,19 +4,11 @@ package arrow.exact.knit.example.exampleExact02 import arrow.core.raise.ensure import arrow.exact.Exact import arrow.exact.ExactError -import arrow.exact.exact - -@JvmInline value class NotBlankString private constructor(val value: String) { - companion object : Exact by exact({ - ensure(raw.isNotBlank()) { ExactError("Cannot be blank.") } - NotBlankString(raw) - }) -} @JvmInline -value class NotBlankTrimmedString private constructor(val value: String) { - companion object : Exact by exact({ - val notBlank = ensure(NotBlankString) - NotBlankTrimmedString(notBlank.value.trim()) +value class NotBlankString private constructor(val value: String) { + companion object : Exact by Exact({ + ensure(it.isNotBlank()) { ExactError("Cannot be blank.") } + NotBlankString(it) }) } diff --git a/guide/src/commonMain/kotlin/examples/example-exact-03.kt b/guide/src/commonMain/kotlin/examples/example-exact-03.kt index d082a4c..40f11a4 100644 --- a/guide/src/commonMain/kotlin/examples/example-exact-03.kt +++ b/guide/src/commonMain/kotlin/examples/example-exact-03.kt @@ -1,34 +1,27 @@ // This file was automatically generated from Exact.kt by Knit tool. Do not edit. package arrow.exact.knit.example.exampleExact03 +import arrow.core.raise.Raise import arrow.core.raise.ensure import arrow.exact.Exact -import arrow.exact.ExactEither import arrow.exact.ExactError -import arrow.exact.exact -import arrow.exact.exactEither +import arrow.exact.ensure -@JvmInline value class NotBlankTrimmedString private constructor(val value: String) { - companion object : Exact by exact({ - ensure(raw.isNotBlank()) { ExactError("Cannot be blank.") } - NotBlankTrimmedString(raw.trim()) - }) -} - -sealed interface UsernameError { - object Invalid : UsernameError - data class Offensive(val username: String) : UsernameError +class NotBlankString private constructor(val value: String) { + companion object : Exact { + override fun Raise.spec(raw: String): NotBlankString { + ensure(raw.isNotBlank()) { ExactError("Cannot be blank.") } + return NotBlankString(raw) + } + } } @JvmInline -value class Username private constructor(val value: String) { - companion object : ExactEither by exactEither({ - val username = - ensure(NotBlankTrimmedString) { - UsernameError.Invalid - }.value - ensure(username.length < 100) { UsernameError.Invalid } - ensure(username !in listOf("offensive")) { UsernameError.Offensive(username) } - Username(username) - }) +value class NotBlankTrimmedString private constructor(val value: String) { + companion object : Exact { + override fun Raise.spec(raw: String): NotBlankTrimmedString { + ensure(raw, NotBlankString) + return NotBlankTrimmedString(raw.trim()) + } + } } diff --git a/guide/src/commonMain/kotlin/examples/example-exact-04.kt b/guide/src/commonMain/kotlin/examples/example-exact-04.kt new file mode 100644 index 0000000..63ca190 --- /dev/null +++ b/guide/src/commonMain/kotlin/examples/example-exact-04.kt @@ -0,0 +1,39 @@ +// This file was automatically generated from Exact.kt by Knit tool. Do not edit. +package arrow.exact.knit.example.exampleExact04 + +import arrow.core.raise.Raise +import arrow.core.raise.ensure +import arrow.exact.Exact +import arrow.exact.ExactEither +import arrow.exact.ExactError +import arrow.exact.ensure + +@JvmInline +value class NotBlankTrimmedString private constructor(val value: String) { + companion object : Exact { + override fun Raise.spec(raw: String): NotBlankTrimmedString { + ensure(raw.isNotBlank()) { ExactError("Cannot be blank.") } + return NotBlankTrimmedString(raw.trim()) + } + } +} + +sealed interface UsernameError { + object Invalid : UsernameError + data class Offensive(val username: String) : UsernameError +} + +@JvmInline +value class Username private constructor(val value: String) { + companion object : ExactEither { + override fun Raise.spec(raw: String): Username { + val username = + ensure(raw, NotBlankTrimmedString) { + UsernameError.Invalid + }.value + ensure(username.length < 100) { UsernameError.Invalid } + ensure(username !in listOf("offensive")) { UsernameError.Offensive(username) } + return Username(username) + } + } +} diff --git a/guide/src/commonMain/kotlin/examples/example-readme-01.kt b/guide/src/commonMain/kotlin/examples/example-readme-01.kt index 208b422..a3c6fed 100644 --- a/guide/src/commonMain/kotlin/examples/example-readme-01.kt +++ b/guide/src/commonMain/kotlin/examples/example-readme-01.kt @@ -1,17 +1,19 @@ // This file was automatically generated from README.md by Knit tool. Do not edit. package arrow.exact.knit.example.exampleReadme01 +import arrow.core.raise.Raise import arrow.core.raise.ensure import arrow.exact.Exact import arrow.exact.ExactError -import arrow.exact.exact @JvmInline -value class NotBlankString private constructor(val value: String) { - companion object : Exact by exact({ - ensure(raw.isNotBlank()) { ExactError("Cannot be blank.") } - NotBlankString(raw) - }) +value class NotBlankString private constructor(val value: String) { + companion object : Exact { + override fun Raise.spec(raw: String): NotBlankString { + ensure(raw.isNotBlank()) { ExactError("Cannot be blank.") } + return NotBlankString(raw) + } + } } fun example() { diff --git a/guide/src/commonMain/kotlin/examples/example-readme-02.kt b/guide/src/commonMain/kotlin/examples/example-readme-02.kt index f29ec61..93b772e 100644 --- a/guide/src/commonMain/kotlin/examples/example-readme-02.kt +++ b/guide/src/commonMain/kotlin/examples/example-readme-02.kt @@ -4,19 +4,11 @@ package arrow.exact.knit.example.exampleReadme02 import arrow.core.raise.ensure import arrow.exact.Exact import arrow.exact.ExactError -import arrow.exact.exact - -@JvmInline value class NotBlankString private constructor(val value: String) { - companion object : Exact by exact({ - ensure(raw.isNotBlank()) { ExactError("Cannot be blank.") } - NotBlankString(raw) - }) -} @JvmInline -value class NotBlankTrimmedString private constructor(val value: String) { - companion object : Exact by exact({ - val notBlank = ensure(NotBlankString) - NotBlankTrimmedString(notBlank.value.trim()) - }) +value class NotBlankString private constructor(val value: String) { + companion object : Exact by Exact({ + ensure(it.isNotBlank()) { ExactError("Cannot be blank.") } + NotBlankString(it) + }) } diff --git a/guide/src/commonMain/kotlin/examples/example-readme-03.kt b/guide/src/commonMain/kotlin/examples/example-readme-03.kt new file mode 100644 index 0000000..9134af8 --- /dev/null +++ b/guide/src/commonMain/kotlin/examples/example-readme-03.kt @@ -0,0 +1,28 @@ +// This file was automatically generated from README.md by Knit tool. Do not edit. +package arrow.exact.knit.example.exampleReadme03 + +import arrow.core.raise.Raise +import arrow.core.raise.ensure +import arrow.exact.Exact +import arrow.exact.ExactError +import arrow.exact.ensure + +@JvmInline +value class NotBlankString private constructor(val value: String) { + companion object : Exact { + override fun Raise.spec(raw: String): NotBlankString { + ensure(raw.isNotBlank()) { ExactError("Cannot be blank.") } + return NotBlankString(raw) + } + } +} + +@JvmInline +value class NotBlankTrimmedString private constructor(val value: String) { + companion object : Exact { + override fun Raise.spec(raw: String): NotBlankTrimmedString { + ensure(raw, NotBlankString) + return NotBlankTrimmedString(raw.trim()) + } + } +} diff --git a/src/commonMain/kotlin/arrow/exact/Exact.kt b/src/commonMain/kotlin/arrow/exact/Exact.kt index fc94b43..df076c5 100644 --- a/src/commonMain/kotlin/arrow/exact/Exact.kt +++ b/src/commonMain/kotlin/arrow/exact/Exact.kt @@ -2,6 +2,7 @@ package arrow.exact import arrow.core.Either import arrow.core.raise.Raise +import arrow.core.raise.either import arrow.core.raise.ensure /** @@ -12,17 +13,19 @@ import arrow.core.raise.ensure * the Arrow's [Raise] DSL to [ensure] the value is not blank. * * ```kotlin + * import arrow.core.raise.Raise * import arrow.core.raise.ensure * import arrow.exact.Exact * import arrow.exact.ExactError - * import arrow.exact.exact * * @JvmInline * value class NotBlankString private constructor(val value: String) { - * companion object : Exact by exact({ - * ensure(raw.isNotBlank()) { ExactError("Cannot be blank.") } - * NotBlankString(raw) - * }) + * companion object : Exact { + * override fun Raise.spec(raw: String): NotBlankString { + * ensure(raw.isNotBlank()) { ExactError("Cannot be blank.") } + * return NotBlankString(raw) + * } + * } * } * ``` * @@ -47,32 +50,54 @@ import arrow.core.raise.ensure * * * + * You can also define [Exact] by using Kotlin delegation. + * + * ```kotlin + * @JvmInline + * value class NotBlankString private constructor(val value: String) { + * companion object : Exact by Exact({ + * ensure(it.isNotBlank()) { ExactError("Cannot be blank.") } + * NotBlankString(it) + * }) + * } + * ``` + * + * * You can define a second type `NotBlankTrimmedString` that is a `NotBlankString` that is also - * trimmed. Since the `exact` constructor allows us to compose `Exact` instances, we can easily + * trimmed. Since the [ensure] allows us to compose [Exact] instances, we can easily * reuse the `NotBlankString` type. * * ```kotlin * @JvmInline * value class NotBlankTrimmedString private constructor(val value: String) { - * companion object : Exact by exact({ - * val notBlank = ensure(NotBlankString) - * NotBlankTrimmedString(notBlank.value.trim()) - * }) + * companion object : Exact { + * override fun Raise.spec(raw: String): NotBlankTrimmedString { + * ensure(raw, NotBlankString) + * return NotBlankTrimmedString(raw.trim()) + * } + * } * } * ``` - * + * * * @see ExactEither if you need to return an [Either] with a custom error type. */ @@ -87,18 +112,21 @@ public data class ExactError(val message: String) * [ExactError], we can easily combine the two by mapping from [ExactError] to our custom [E] type. * * * ```kotlin @@ -109,22 +137,26 @@ public data class ExactError(val message: String) * * @JvmInline * value class Username private constructor(val value: String) { - * companion object : ExactEither by exactEither({ - * val username = - * ensure(NotBlankTrimmedString) { - * UsernameError.Invalid - * }.value - * ensure(username.length < 100) { UsernameError.Invalid } - * ensure(username !in listOf("offensive")) { UsernameError.Offensive(username) } - * Username(username) - * }) + * companion object : ExactEither { + * override fun Raise.spec(raw: String): Username { + * val username = + * ensure(raw, NotBlankTrimmedString) { + * UsernameError.Invalid + * }.value + * ensure(username.length < 100) { UsernameError.Invalid } + * ensure(username !in listOf("offensive")) { UsernameError.Offensive(username) } + * return Username(username) + * } + * } * } * ``` - * + * */ -public fun interface ExactEither { +public fun interface ExactEither { + + public fun Raise.spec(raw: A): R - public fun from(value: A): Either + public fun from(value: A): Either = either { spec(value) } public fun fromOrNull(value: A): R? = from(value).getOrNull() diff --git a/src/commonMain/kotlin/arrow/exact/ExactDsl.kt b/src/commonMain/kotlin/arrow/exact/ExactDsl.kt index 5e71e14..f1d2468 100644 --- a/src/commonMain/kotlin/arrow/exact/ExactDsl.kt +++ b/src/commonMain/kotlin/arrow/exact/ExactDsl.kt @@ -2,33 +2,20 @@ package arrow.exact import arrow.core.Either import arrow.core.raise.Raise -import arrow.core.raise.either +import arrow.core.raise.RaiseDSL -@DslMarker public annotation class ExactDsl - -@ExactDsl -public fun exact(construct: ExactScope.() -> R): Exact = - Exact { value -> either { construct(ExactScope(value, this)) } } - -@ExactDsl -public fun exactEither(construct: ExactScope.() -> R): ExactEither = - ExactEither { value -> either { construct(ExactScope(value, this)) } } - -public class ExactScope(public val raw: A, raise: Raise) : Raise by raise { - - @ExactDsl - public fun ensure(exact: ExactEither): B { - return when (val result = exact.from(raw)) { - is Either.Left -> raise(result.value) - is Either.Right -> result.value - } +@RaiseDSL +public inline fun Raise.ensure(raw: A, exact: ExactEither): B { + return when (val result = exact.from(raw)) { + is Either.Left -> raise(result.value) + is Either.Right -> result.value } +} - @ExactDsl - public fun ensure(exact: Exact, error: () -> E): B { - return when (val result = exact.from(raw)) { - is Either.Left -> raise(error()) - is Either.Right -> result.value - } +@RaiseDSL +public inline fun Raise.ensure(raw: A, exact: Exact, error: (ExactError) -> Error): B { + return when (val result = exact.from(raw)) { + is Either.Left -> raise(error(result.value)) + is Either.Right -> result.value } }