Skip to content

Commit

Permalink
Arrow Optics, DSL syntax improvements (#3427)
Browse files Browse the repository at this point in the history
  • Loading branch information
nomisRev authored May 16, 2024
1 parent b35685c commit dc7baf1
Show file tree
Hide file tree
Showing 9 changed files with 372 additions and 0 deletions.
31 changes: 31 additions & 0 deletions arrow-libs/optics/arrow-optics/api/arrow-optics.api
Original file line number Diff line number Diff line change
Expand Up @@ -457,8 +457,14 @@ public final class arrow/optics/PrismKt {

public final class arrow/optics/dsl/AtKt {
public static final fun at (Larrow/optics/PLens;Larrow/optics/typeclasses/At;Ljava/lang/Object;)Larrow/optics/PLens;
public static final fun at (Larrow/optics/PLens;Ljava/lang/Object;)Larrow/optics/PLens;
public static final fun at (Larrow/optics/POptional;Larrow/optics/typeclasses/At;Ljava/lang/Object;)Larrow/optics/POptional;
public static final fun at (Larrow/optics/POptional;Ljava/lang/Object;)Larrow/optics/POptional;
public static final fun at (Larrow/optics/PTraversal;Larrow/optics/typeclasses/At;Ljava/lang/Object;)Larrow/optics/PTraversal;
public static final fun at (Larrow/optics/PTraversal;Ljava/lang/Object;)Larrow/optics/PTraversal;
public static final fun atSet (Larrow/optics/PLens;Ljava/lang/Object;)Larrow/optics/PLens;
public static final fun atSet (Larrow/optics/POptional;Ljava/lang/Object;)Larrow/optics/POptional;
public static final fun atSet (Larrow/optics/PTraversal;Ljava/lang/Object;)Larrow/optics/PTraversal;
}

public final class arrow/optics/dsl/EitherKt {
Expand All @@ -472,12 +478,37 @@ public final class arrow/optics/dsl/EitherKt {

public final class arrow/optics/dsl/EveryKt {
public static final fun every (Larrow/optics/PTraversal;Larrow/optics/PTraversal;)Larrow/optics/PTraversal;
public static final fun everyChar (Larrow/optics/PTraversal;)Larrow/optics/PTraversal;
public static final fun everyNonEmptyList (Larrow/optics/PTraversal;)Larrow/optics/PTraversal;
public static final fun everyRight (Larrow/optics/PTraversal;)Larrow/optics/PTraversal;
public static final fun everySequence (Larrow/optics/PTraversal;)Larrow/optics/PTraversal;
public static final fun everySome (Larrow/optics/PTraversal;)Larrow/optics/PTraversal;
public static final fun everyValue (Larrow/optics/PTraversal;)Larrow/optics/PTraversal;
public static final fun getEvery (Larrow/optics/PTraversal;)Larrow/optics/PTraversal;
}

public final class arrow/optics/dsl/FilterIndexKt {
public static final fun filter (Larrow/optics/PTraversal;Larrow/optics/typeclasses/FilterIndex;Lkotlin/jvm/functions/Function1;)Larrow/optics/PTraversal;
public static final fun filter (Larrow/optics/PTraversal;Lkotlin/jvm/functions/Function1;)Larrow/optics/PTraversal;
public static final fun filterChars (Larrow/optics/PTraversal;Lkotlin/jvm/functions/Function1;)Larrow/optics/PTraversal;
public static final fun filterNonEmptyList (Larrow/optics/PTraversal;Lkotlin/jvm/functions/Function1;)Larrow/optics/PTraversal;
public static final fun filterSequence (Larrow/optics/PTraversal;Lkotlin/jvm/functions/Function1;)Larrow/optics/PTraversal;
public static final fun filterValues (Larrow/optics/PTraversal;Lkotlin/jvm/functions/Function1;)Larrow/optics/PTraversal;
}

public final class arrow/optics/dsl/IndexKt {
public static final fun index (Larrow/optics/POptional;I)Larrow/optics/POptional;
public static final fun index (Larrow/optics/POptional;Larrow/optics/typeclasses/Index;Ljava/lang/Object;)Larrow/optics/POptional;
public static final fun index (Larrow/optics/PTraversal;I)Larrow/optics/PTraversal;
public static final fun index (Larrow/optics/PTraversal;Larrow/optics/typeclasses/Index;Ljava/lang/Object;)Larrow/optics/PTraversal;
public static final fun indexNonEmptyList (Larrow/optics/POptional;I)Larrow/optics/POptional;
public static final fun indexNonEmptyList (Larrow/optics/PTraversal;I)Larrow/optics/PTraversal;
public static final fun indexSequence (Larrow/optics/POptional;I)Larrow/optics/POptional;
public static final fun indexSequence (Larrow/optics/PTraversal;I)Larrow/optics/PTraversal;
public static final fun indexString (Larrow/optics/POptional;I)Larrow/optics/POptional;
public static final fun indexString (Larrow/optics/PTraversal;I)Larrow/optics/PTraversal;
public static final fun indexValues (Larrow/optics/POptional;Ljava/lang/Object;)Larrow/optics/POptional;
public static final fun indexValues (Larrow/optics/PTraversal;Ljava/lang/Object;)Larrow/optics/PTraversal;
}

public final class arrow/optics/dsl/NullableKt {
Expand Down
34 changes: 34 additions & 0 deletions arrow-libs/optics/arrow-optics/api/arrow-optics.klib.api

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package arrow.optics.dsl

import arrow.core.Option
import arrow.optics.Lens
import arrow.optics.Optional
import arrow.optics.Prism
import arrow.optics.Traversal
import arrow.optics.typeclasses.At
import kotlin.jvm.JvmName

/**
* DSL to compose [At] with a [Lens] for a structure [S] to focus in on [A] at given index [I].
Expand Down Expand Up @@ -35,3 +37,25 @@ public fun <T, S, I, A> Optional<T, S>.at(at: At<S, I, A>, i: I): Optional<T, A>
* @return [Traversal] with a focus in [A] at given index [I].
*/
public fun <T, S, I, A> Traversal<T, S>.at(at: At<S, I, A>, i: I): Traversal<T, A> = this.compose(at.at(i))

public fun <T, K, V> Lens<T, Map<K, V>>.at(key: K): Lens<T, Option<V>> =
this.compose(At.map<K, V>().at(key))

public fun <T, K, V> Optional<T, Map<K, V>>.at(key: K): Optional<T, Option<V>> =
this.compose(At.map<K, V>().at(key))

public fun <T, K, V> Traversal<T, Map<K, V>>.at(key: K): Traversal<T, Option<V>> =
this.compose(At.map<K, V>().at(key))

@JvmName("atSet")
public fun <T, A> Lens<T, Set<A>>.at(value: A): Lens<T, Boolean> =
this.compose(At.set<A>().at(value))

@JvmName("atSet")
public fun <T, A> Optional<T, Set<A>>.at(value: A): Optional<T, Boolean> =
this.compose(At.set<A>().at(value))

@JvmName("atSet")
public fun <T, A> Traversal<T, Set<A>>.at(value: A): Traversal<T, Boolean> =
this.compose(At.set<A>().at(value))

Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package arrow.optics.dsl

import arrow.core.Either
import arrow.core.NonEmptyList
import arrow.core.Option
import arrow.optics.Every
import arrow.optics.Optional
import arrow.optics.Traversal
import kotlin.jvm.JvmName

/**
* DSL to compose [Traversal] with a [Traversal] for a structure [S] to see all its foci [A]
Expand All @@ -14,3 +19,27 @@ public fun <T, S, A> Traversal<T, S>.every(tr: Traversal<S, A>): Traversal<T, A>

public val <T, A> Traversal<T, List<A>>.every: Traversal<T, A>
get() = this.compose(Every.list())

@get:JvmName("everyRight")
public val <T, Error, A> Traversal<T, Either<Error, A>>.every: Traversal<T, A>
get() = this.compose(Every.either())

@get:JvmName("everyValue")
public val <T, K, V> Traversal<T, Map<K, V>>.every: Traversal<T, V>
get() = this.compose(Every.map())

@get:JvmName("everyNonEmptyList")
public val <T, A> Traversal<T, NonEmptyList<A>>.every: Traversal<T, A>
get() = this.compose(Every.nonEmptyList())

@get:JvmName("everySome")
public val <T, A> Traversal<T, Option<A>>.every: Traversal<T, A>
get() = this.compose(Every.option())

@get:JvmName("everySequence")
public val <T, A> Traversal<T, Sequence<A>>.every: Traversal<T, A>
get() = this.compose(Every.sequence())

@get:JvmName("everyChar")
public val <T> Traversal<T, String>.every: Traversal<T, Char>
get() = this.compose(Every.string())
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package arrow.optics.dsl

import arrow.core.NonEmptyList
import arrow.core.Predicate
import arrow.optics.Optional
import arrow.optics.Traversal
import arrow.optics.typeclasses.FilterIndex
import kotlin.jvm.JvmName

/**
* DSL to compose [FilterIndex] with an [Traversal] for a structure [S] to focus in on [A] at given index [I]
*
* @receiver [Optional] with a focus in [S]
* @param filter [FilterIndex] instance to provide a [Optional] to focus into [S] at [I]
* @param i index [I] to focus into [S] and find focus [A]
* @return [Optional] with a focus in [A] at given index [I]
*/
public fun <T, S, I, A> Traversal<T, S>.filter(filter: FilterIndex<S, I, A>, predicate: Predicate<I>): Traversal<T, A> =
this.compose(filter.filter(predicate))

public fun <T, A> Traversal<T, List<A>>.filter(predicate: Predicate<Int>): Traversal<T, A> =
this.compose(FilterIndex.list<A>().filter(predicate))

@JvmName("filterNonEmptyList")
public fun <T, A> Traversal<T, NonEmptyList<A>>.filter(predicate: Predicate<Int>): Traversal<T, A> =
this.compose(FilterIndex.nonEmptyList<A>().filter(predicate))

@JvmName("filterSequence")
public fun <T, A> Traversal<T, Sequence<A>>.filter(predicate: Predicate<Int>): Traversal<T, A> =
this.compose(FilterIndex.sequence<A>().filter(predicate))

@JvmName("filterValues")
public fun <T, K, A> Traversal<T, Map<K, A>>.filter(predicate: Predicate<K>): Traversal<T, A> =
this.compose(FilterIndex.map<K, A>().filter(predicate))

@JvmName("filterChars")
public fun <T> Traversal<T, String>.filter(predicate: Predicate<Int>): Traversal<T, Char> =
this.compose(FilterIndex.string().filter(predicate))
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package arrow.optics.dsl

import arrow.core.NonEmptyList
import arrow.optics.Lens
import arrow.optics.Optional
import arrow.optics.Prism
import arrow.optics.Traversal
import arrow.optics.typeclasses.Index
import kotlin.jvm.JvmName

/**
* DSL to compose [Index] with an [Optional] for a structure [S] to focus in on [A] at given index [I]
Expand All @@ -25,3 +27,41 @@ public fun <T, S, I, A> Optional<T, S>.index(idx: Index<S, I, A>, i: I): Optiona
* @return [Traversal] with a focus in [A] at given index [I].
*/
public fun <T, S, I, A> Traversal<T, S>.index(idx: Index<S, I, A>, i: I): Traversal<T, A> = this.compose(idx.index(i))

public fun <T, A> Optional<T, List<A>>.index(index: Int): Optional<T, A> =
this.compose(Index.list<A>().index(index))

public fun <T, A> Traversal<T, List<A>>.index(index: Int): Traversal<T, A> =
this.compose(Index.list<A>().index(index))

@JvmName("indexNonEmptyList")
public fun <T, A> Optional<T, NonEmptyList<A>>.index(index: Int): Optional<T, A> =
this.compose(Index.nonEmptyList<A>().index(index))

@JvmName("indexNonEmptyList")
public fun <T, A> Traversal<T, NonEmptyList<A>>.index(index: Int): Traversal<T, A> =
this.compose(Index.nonEmptyList<A>().index(index))

@JvmName("indexValues")
public fun <T, K, A> Optional<T, Map<K, A>>.index(key: K): Optional<T, A> =
this.compose(Index.map<K, A>().index(key))

@JvmName("indexValues")
public fun <T, K, A> Traversal<T, Map<K, A>>.index(key: K): Traversal<T, A> =
this.compose(Index.map<K, A>().index(key))

@JvmName("indexSequence")
public fun <T, A> Optional<T, Sequence<A>>.index(index: Int): Optional<T, A> =
this.compose(Index.sequence<A>().index(index))

@JvmName("indexSequence")
public fun <T, A> Traversal<T, Sequence<A>>.index(index: Int): Traversal<T, A> =
this.compose(Index.sequence<A>().index(index))

@JvmName("indexString")
public fun <T> Optional<T, String>.index(index: Int): Optional<T, Char> =
this.compose(Index.string().index(index))

@JvmName("indexString")
public fun <T> Traversal<T, String>.index(index: Int): Traversal<T, Char> =
this.compose(Index.string().index(index))
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package arrow.optics.dsl

import arrow.core.None
import kotlin.test.Test
import kotlin.test.assertEquals

class AtSyntaxTest {

@Test
fun mapModify() {
val original = mapOf("one" to 1, "two" to 2)
val expected = Wrapper(original.mapValues { (k, i) ->
if (k == "one") i + 1 else i
})
val actual = Wrapper.lens<Map<String, Int>>()
.at("one")
.some
.modify(Wrapper(original), Int::inc)
assertEquals(expected, actual)
}

@Test
fun mapRemove() {
val original = mapOf("one" to 1, "two" to 2)
val expected = Wrapper(original - "one")
val actual = Wrapper.lens<Map<String, Int>>()
.at("one")
.set(Wrapper(original), None)
assertEquals(expected, actual)
}

fun setKeep() {
val original = setOf(1)
val expected = Wrapper(setOf(1, 2))
val actual = Wrapper.lens<Set<Int>>()
.at(2)
.set(Wrapper(original), false)
assertEquals(expected, actual)
}

@Test
fun setRemove() {
val original = setOf(1)
val expected = Wrapper(setOf(1, 2))
val actual = Wrapper.lens<Set<Int>>()
.at(2)
.set(Wrapper(original), true)
assertEquals(expected, actual)
}

@Test
fun setFilter() {
val original = setOf(1)
val expected = Wrapper(emptySet<Int>())
val actual = Wrapper.lens<Set<Int>>()
.at(1)
.set(Wrapper(original), false)
assertEquals(expected, actual)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package arrow.optics.dsl

import arrow.core.*
import arrow.optics.Lens
import kotlin.jvm.JvmInline
import kotlin.test.Test
import kotlin.test.assertEquals

class EverySyntaxTest {

@Test
fun list() {
val original = listOf(1, 2)
val expected = Wrapper(original.map(Int::inc))
val actual = Wrapper.lens<List<Int>>()
.every
.modify(Wrapper(original), Int::inc)
assertEquals(expected, actual)
}

@Test
fun either_right() {
val original: Either<String, Int> = 1.right()
val expected = Wrapper(original.map(Int::inc))
val actual = Wrapper.lens<Either<String, Int>>()
.every
.modify(Wrapper(original), Int::inc)
assertEquals(expected, actual)
}

@Test
fun either_left() {
val original: Either<String, Int> = "one".left()
val expected = Wrapper(original)
val actual = Wrapper.lens<Either<String, Int>>()
.every
.modify(Wrapper(original), Int::inc)
assertEquals(expected, actual)
}

@Test
fun map() {
val original = mapOf("one" to 1, "two" to 2)
val expected = Wrapper(original.mapValues { (_, i) -> i + 1 })
val actual = Wrapper.lens<Map<String, Int>>()
.every
.modify(Wrapper(original), Int::inc)
assertEquals(expected, actual)
}

@Test
fun nonEmptyList() {
val original = nonEmptyListOf(1, 2)
val expected = Wrapper(original.map(Int::inc))
val actual = Wrapper.lens<NonEmptyList<Int>>()
.every
.modify(Wrapper(original), Int::inc)
assertEquals(expected, actual)
}

@Test
fun some() {
val original = 1.some()
val expected = Wrapper(original.map(Int::inc))
val actual = Wrapper.lens<Option<Int>>()
.every
.modify(Wrapper(original), Int::inc)
assertEquals(expected, actual)
}

@Test
fun none() {
val original = none<Int>()
val expected = Wrapper(original.map(Int::inc))
val actual = Wrapper.lens<Option<Int>>()
.every
.modify(Wrapper(original), Int::inc)
assertEquals(expected, actual)
}

@Test
fun sequence() {
val original = sequenceOf(1, 2)
val expected = Wrapper(original.map(Int::inc))
val actual = Wrapper.lens<Sequence<Int>>()
.every
.modify(Wrapper(original), Int::inc)
assertEquals(expected.value.toList(), actual.value.toList())
}

private fun String.mapEach(block: (Char) -> Char): String =
map(block).joinToString(separator = "")

@Test
fun string() {
val original = "abc"
val expected = Wrapper(original.mapEach(Char::titlecaseChar))
val actual = Wrapper.lens<String>()
.every
.modify(Wrapper(original), Char::titlecaseChar)
assertEquals(expected, actual)
}
}
Loading

0 comments on commit dc7baf1

Please sign in to comment.