Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement filter function for Optional #2588

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions arrow-libs/optics/arrow-optics/api/arrow-optics.api
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,7 @@ public abstract interface class arrow/optics/POptional : arrow/optics/Fold, arro
public static final field Companion Larrow/optics/POptional$Companion;
public abstract fun choice (Larrow/optics/POptional;)Larrow/optics/POptional;
public abstract fun compose (Larrow/optics/POptional;)Larrow/optics/POptional;
public static fun filter (Lkotlin/jvm/functions/Function1;)Larrow/optics/POptional;
public abstract fun first ()Larrow/optics/POptional;
public abstract fun foldMap (Larrow/typeclasses/Monoid;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
public abstract fun getOrModify (Ljava/lang/Object;)Larrow/core/Either;
Expand All @@ -463,6 +464,7 @@ public abstract interface class arrow/optics/POptional : arrow/optics/Fold, arro

public final class arrow/optics/POptional$Companion {
public final fun codiagonal ()Larrow/optics/POptional;
public final fun filter (Lkotlin/jvm/functions/Function1;)Larrow/optics/POptional;
public final fun id ()Larrow/optics/PIso;
public final fun invoke (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)Larrow/optics/POptional;
public final fun listHead ()Larrow/optics/POptional;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,5 +204,42 @@ public interface POptional<S, T, A, B> : PSetter<S, T, A, B>, Fold<S, A>, PTrave
getOption = { if (it.isEmpty()) None else Some(it.drop(1)) },
set = { list, newTail -> if (list.isNotEmpty()) list[0] prependTo newTail else emptyList() }
)

/**
* [Optional] to itself if it satisfies the predicate.
*
* Select all the elements which satisfies the predicate.
* ```kotlin
* import arrow.optics.Traversal
* import arrow.optics.Optional
*
* val positiveNumbers = Traversal.list<Int>() compose Optional.filter { it >= 0 }
*
* positiveNumbers.getAll(listOf(1,2,-3,4,-5)) == listOf(1,2,4)
* positiveNumbers.modify(listOf(1,2,-3,4,-5)) { it * 10 } == listOf(10,20,-3,40,-5)
*```
*
* `filter` can break the fusion property, if `replace` or `modify` do not preserve the predicate. For example, here
* the first `modify` {`x - 3`} transform the positive number 1 into the negative number -2.
*
*```kotlin
* import arrow.optics.Traversal
* import arrow.optics.Optional
*
* val positiveNumbers = Traversal.list<Int>() compose Optional.filter { it >= 0 }
* val list = listOf(1, 5, -3)
* val firstStep = positiveNumbers.modify(list){ it - 3 } // List(-2, 2, -3)
* val secondStep = positiveNumbers.modify(firstStep) { it * 2 } // List(-2, 4, -3)
* val bothSteps = positiveNumbers.modify(list){ (it - 3) * 2) // List(-4, 4, -3)
* // secondStep != bothSteps
* ```
*/

@JvmStatic
public fun <A> filter(predicate: (A) -> Boolean): Optional<A, A> =
Optional(
getOption = { if (predicate(it)) Some(it) else None },
set = { current, newValue -> if (predicate(current)) newValue else current }
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -152,13 +152,15 @@ class OptionalTest : UnitSpec() {

"Checking existence predicate over the target should result in same result as predicate" {
checkAll(Arb.list(Arb.int().orNull()), Arb.boolean()) { list, predicate ->
Optional.listHead<Int?>().exists(list) { predicate } shouldBe (predicate && list.isNotEmpty())
Optional.listHead<Int?>()
.exists(list) { predicate } shouldBe (predicate && list.isNotEmpty())
}
}

"Checking satisfaction of predicate over the target should result in opposite result as predicate" {
checkAll(Arb.list(Arb.int()), Arb.boolean()) { list, predicate ->
Optional.listHead<Int>().all(list) { predicate } shouldBe if (list.isEmpty()) true else predicate
Optional.listHead<Int>()
.all(list) { predicate } shouldBe if (list.isEmpty()) true else predicate
}
}

Expand All @@ -169,5 +171,37 @@ class OptionalTest : UnitSpec() {
joinedOptional.getOrNull(Left(listOf(int))) shouldBe joinedOptional.getOrNull(Right(int))
}
}

"get should return value if predicate is true and null if otherwise" {
checkAll(Arb.int(), Arb.boolean()) { int, predicate ->
Optional.filter<Int> { predicate }.getOrNull(int) shouldBe (if (predicate) int else null)
}
}

"set should return value if predicate is true and null if otherwise" {
checkAll(Arb.int(), Arb.int(), Arb.boolean()) { int, newValue, predicate ->
Optional.filter<Int> { predicate }
.set(int, newValue) shouldBe (if (predicate) newValue else int)
}
}

"getAll should return the old list if predicate is true" {
checkAll(Arb.list(Arb.int()), Arb.boolean()) { list, predicate ->
(Fold.list<Int>() compose Optional.filter { predicate }).getAll(list) shouldBe (if (predicate) list else emptyList())
}
}

"set with predicate true should have the same result with map" {
checkAll(Arb.list(Arb.int()), Arb.functionAToB<Int, Int>(Arb.int())) { list, f ->
(Traversal.list<Int>() compose Optional.filter { true }).modify(list, f) shouldBe list.map(f)
}
}

"set with predicate false should return the old list" {
checkAll(Arb.list(Arb.int()), Arb.functionAToB<Int, Int>(Arb.int())) { list, f ->
(Traversal.list<Int>() compose Optional.filter { false }).modify(list, f) shouldBe list
}
}

}
}