beginner
A Setter
is an optic that can see into a structure and set or modify its focus.
It is a generalisation of Functor#map
. Given a Functor<F>
we can apply a function (A) -> B
to Kind<F, A>
and get Kind<F, B>
. We can think of Kind<F, A>
as a structure S
that has a focus A
.
So given a PSetter<S, T, A, B>
we can apply a function (A) -> B
to S
and get T
.
Functor.map(fa: Kind<F, A>, f: (A) -> B) -> Kind<F, B>
PSetter.modify(s: S, f: (A) -> B): T
You can get a Setter
for any existing Functor
.
import arrow.*
import arrow.optics.*
import arrow.core.*
import arrow.mtl.*
import arrow.core.extensions.listk.functor.*
val setter: Setter<ListKOf<Int>, Int> = Setter.fromFunctor(ListK.functor())
setter.set(listOf(1, 2, 3, 4).k(), 5)
// ListK(list=[5, 5, 5, 5])
setter.modify(listOf(1, 2, 3, 4).k()) { int -> int + 1 }
// ListK(list=[2, 3, 4, 5])
To create your own Setter
you need to define how to apply (A) -> B
to S
.
A Setter<Player, Int>
can set and modify the value of Player
. So we need to define how to apply a function (Int) -> Int
to Player
.
data class Player(val health: Int)
val playerSetter: Setter<Player, Int> = Setter { player: Player, f: (Int) -> Int ->
val fHealth= f(player.health)
player.copy(health = fHealth)
}
val increment: (Int) -> Int = Int::inc
playerSetter.modify(Player(75), increment)
// Player(health=76)
val lift = playerSetter.lift(increment)
lift(Player(75))
// Player(health=76)
There are also some convenience methods to make working with State easier. This can make working with nested structures in stateful computations significantly more elegant.
import arrow.optics.mtl.*
val takeDamage = playerSetter.update_ { it - 15 }
takeDamage.run(Player(75))
// Tuple2(a=Player(health=60), b=kotlin.Unit)
val restoreHealth = playerSetter.assign_(100)
restoreHealth.run(Player(75))
// Tuple2(a=Player(health=100), b=kotlin.Unit)
Unlike a regular set
function a Setter
composes. Similar to a Lens
we can compose Setter
s to focus into nested structures and set or modify a value.
data class Bar(val player: Player)
val barSetter: Setter<Bar, Player> = Setter { bar, modifyPlayer ->
val modifiedPlayer = modifyPlayer(bar.player)
bar.copy(player = modifiedPlayer)
}
(barSetter compose playerSetter).modify(Bar(Player(75)), Int::inc)
// Bar(player=Player(health=76))
Setter
can be composed with all optics but Getter
and Fold
. It results in the following optics.
Iso | Lens | Prism | Optional | Getter | Setter | Fold | Traversal | |
---|---|---|---|---|---|---|---|---|
Setter | Setter | Setter | Setter | Setter | X | Setter | X | Setter |
When dealing with polymorphic types we can also have polymorphic setters that allow us to morph the type of the focus.
Previously when we used a Setter<ListKOf<Int>, Int>
it was able to morph the Int
values in the constructed type ListK<Int>
.
With a PSetter<ListKOf<Int>, ListKOf<String>, Int, String>
we can morph an Int
value to a String
value and thus also morph the type from ListK<Int>
to ListK<String>
.
val pSetter: PSetter<ListKOf<Int>, ListKOf<String>, Int, String> = PSetter.fromFunctor(ListK.functor())
pSetter.set(listOf(1, 2, 3, 4).k(), "Constant")
// ListK(list=[Constant, Constant, Constant, Constant])
pSetter.modify(listOf(1, 2, 3, 4).k()) {
"Value at $it"
}
// ListK(list=[Value at 1, Value at 2, Value at 3, Value at 4])
Arrow provides SetterLaws
in the form of test cases for internal verification of lawful instances and third party apps creating their own setters.