Skip to content

Commit

Permalink
Add map and flatMap functions
Browse files Browse the repository at this point in the history
  • Loading branch information
ustitc committed May 7, 2023
1 parent dde55b8 commit ac67a74
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 43 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@ class NotBlankString private constructor(private val value: String) {
val notBlank = NotBlankString(refined)
```

`Refinement` can be used in the same way as Collections, Either and other monads:

```kotlin
refine(NotBlank(), "Krefty")
.map { NotBlankString(it) }
.flatMap { refine(UserNamePredicate(), it) }
```

### Predicates

Krefty is shipped with predefined `Predicate`s but it strongly encouraged to make domain specific ones.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ internal class LazyRefinement<P : Predicate<T>, T> internal constructor(
return getOrNull() ?: block(value)
}

override fun getOrElse(block: () -> T): T {
return getOrNull() ?: block()
}

override fun getOrThrow(): T {
if (!predicate.isRefined(value)) {
throw RefinementException(value)
Expand All @@ -35,6 +39,18 @@ internal class LazyRefinement<P : Predicate<T>, T> internal constructor(
}
}

override fun <R> map(block: (T) -> R): Refinement<R> {
return flatMap { Holder(block(value)) }
}

override fun <R> flatMap(block: (T) -> Refinement<R>): Refinement<R> {
return if (getOrNull() == null) {
Error(RefinementException(value))
} else {
block(value)
}
}

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false
Expand All @@ -50,4 +66,21 @@ internal class LazyRefinement<P : Predicate<T>, T> internal constructor(
return value?.hashCode() ?: 0
}

private class Holder<T>(private val value: T) : Refinement<T> {
override fun getOrElse(block: () -> T): T = value
override fun getOrThrow(): T = value
override fun getOrNull(): T? = value
override fun getOrError(): Result<T> = Result.success(value)
override fun <R> map(block: (T) -> R): Refinement<R> = Holder(block(value))
override fun <R> flatMap(block: (T) -> Refinement<R>): Refinement<R> = block(value)
}

private class Error<T>(private val exception: RefinementException) : Refinement<T> {
override fun getOrElse(block: () -> T): T = block()
override fun getOrThrow(): T = throw exception
override fun getOrNull(): T? = null
override fun getOrError(): Result<T> = Result.failure(exception)
override fun <R> map(block: (T) -> R): Refinement<R> = Error(exception)
override fun <R> flatMap(block: (T) -> Refinement<R>): Refinement<R> = Error(exception)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,18 @@ interface Refined<P : Predicate<T>, T> {

interface Refinement<T> {

fun getOrElse(block: (T) -> T): T
fun getOrElse(block: () -> T): T

fun getOrThrow(): T

fun getOrNull(): T?

fun getOrError(): Result<T>

fun <R> map(block: (T) -> R): Refinement<R>

fun <R> flatMap(block: (T) -> Refinement<R>): Refinement<R>

}

fun <T, P : Predicate<T>> refine(predicate: P, value: T): Refinement<T> {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package dev.ustits.krefty.core

import dev.ustits.krefty.predicate.string.Blank
import dev.ustits.krefty.predicate.string.Length
import dev.ustits.krefty.predicate.string.NotBlank
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.result.shouldBeFailure
import io.kotest.matchers.result.shouldBeSuccess
import io.kotest.matchers.shouldBe

class RefinementTest : StringSpec ({

"returns value" {
val refinement = refine(NotBlank(), "oil")
refinement.getOrThrow() shouldBe "oil"
refinement.getOrNull() shouldBe "oil"
refinement.getOrElse { "gas" } shouldBe "oil"
refinement.getOrError() shouldBeSuccess "oil"
}

"returns default value" {
val refinement = refine(Blank(), "oil")
refinement.getOrElse { "gas" } shouldBe "gas"
}

"returns null" {
val refinement = refine(NotBlank(), "")
refinement.getOrNull() shouldBe null
}

"returns error" {
val refinement = refine(NotBlank(), "")
refinement.getOrError() shouldBeFailure RefinementException("")
}

"throws exception" {
val refinement = refine(NotBlank(), "")
shouldThrow<RefinementException> {
refinement.getOrThrow()
}
}

"maps if can be refined" {
val refinement = refine(NotBlank(), "oil").map { "gas" }
refinement.getOrThrow() shouldBe "gas"
}

"doesn't map if can't be refined" {
val refinement = refine(NotBlank(), "").map { "gas" }
refinement.getOrNull() shouldBe null
}

"flatMaps if can be refined" {
val refinement = refine(NotBlank(), "oil").flatMap {
refine(Length(3), it)
}
refinement.getOrThrow() shouldBe "oil"
}

"doesn't flatMap if can't be refined" {
val refinement = refine(NotBlank(), "").flatMap {
refine(Length(0), it)
}
refinement.getOrNull() shouldBe null
}

"returns error if after flatMap can't be refined" {
val refinement = refine(NotBlank(), "oil").flatMap {
refine(Length(2), it)
}
refinement.getOrNull() shouldBe null
}
})

0 comments on commit ac67a74

Please sign in to comment.