IndexedSetter
An IndexedSetter
is a generalization of map
from IxFunctor
.
IndexedSetter
is write-only optic, allowing us to map into a structure and change out the content, but it cannot get the content.
IndexedSetter internal encoding
Polymorphic IndexedSetter
IndexedSetter_[I, S, T, A, B]
IndexedSetter_[I, S, T, A, B]
is a function Indexed[Function, I, A, B] => S => T
.
/**
* @tparam I the index of an IndexedSetter_
* @tparam S the source of an IndexedSetter_
* @tparam T the modified source of an IndexedSetter_—
* @tparam A the focus an IndexedSetter_
* @tparam B the modified focus of an IndexedSetter_
*/
abstract class IndexedSetter_[I, S, T, A, B] extends Serializable { self =>
def apply(indexed: Indexed[Function, I, A, B]): S => T
}
IndexedSetter_[I, S, T, A, B]
changes its focus from A
to B
, resulting in a change of structure from S
to T
.
A IndexedSetter
that changes its focus/structure, is called Polymorphic IndexedSetter
.
Monomorphic IndexedSetter
IndexedSetter[I, S, A]
IndexedSetter[I, S, A]
is a type alias for IndexedSetter_[I, S, S, A, A]
, which has the same type of focus A
, thus preserving the same type of structure S
.
type IndexedSetter[I, S, A] = IndexedSetter_[I, S, S, A, A]
An IndexedSetter
that does not change its focus/structure, is called Monomorphic IndexedSetter
.
Constructing IndexedSetters
IndexedSetter_[I, S, T, A, B]
is constructed using the IndexedSetter_[I, S, T, A, B]#apply function.
For a given IndexedSetter_[I, S, T, A, B]
it takes a function as argument, ((A, I) => B) => S => T
, which is a mapping function ((A, I) => B)
from a focus A
and its index I
to the modified focus B
and a structure S
and returns a structure of T
.
object IndexedSetter_ {
def apply[I, S, T, A, B](f: ((A, I) => B) => S => T): IndexedSetter[I, S, T, A, B]
}
IndexedSetter[I, S, A]
is constructed using the IndexedSetter[I, S, A]#apply function.
For a given IndexedSetter_[I, S, A]
it takes a function as argument, ((A, I) => A) => S => S
, which is a mapping function (A, I) => A
from a focus A
and its index I
a new focus A
and a structure S
and returns a new structure S
.
object IndexedSetter {
def apply[I, S, A](f: ((A, I) => A) => S => S): IndexedSetter[I, S, A]
}
Consider a Map[String, List[String]]
of series recommendations, and we want to update
an entry by an index.
import proptics.IndexedSetter
// import proptics.IndexedSetter
import cats.syntax.eq._ // triple equals (===)
// import cats.syntax.eq._
val seriesMap: Map[String, List[String]] = Map[String, List[String]](
"tt0903747" -> List("True Detective", "Fargo", "Dexter"),
"tt2356777" -> List("Breaking Bad", "Fargo", "Dexter"),
"tt2802850" -> List("Breaking Bad", "True Detective", "Dexter"),
"tt0773262" -> List("Breaking Bad", "True Detective", "Fargo")
)
// seriesMap: Map[String,List[String]] =
// Map(tt0903747 -> List(True Detective, Fargo, Dexter),
// tt2356777 -> List(Breaking Bad, Fargo, Dexter),
// tt2802850 -> List(Breaking Bad, True Detective, Dexter),
// tt0773262 -> List(Breaking Bad, True Detective, Fargo))
val newRecommendations = List("True Detective", "Fargo", "Dexter", "The Mandalorian")
// newRecommendations: List[String] = List(True Detective, Fargo, Dexter, The Mandalorian)
val indexedSetter = IndexedSetter[String, Map[String, List[String]], List[String]] { f => aMap =>
aMap.map { case (k, v) => if (k === "tt0903747") k -> f(v, k) else k -> v }
}
// indexedSetter: proptics.IndexedSetter[String,Map[String,List[String]],List[String]] =
// proptics.IndexedSetter_$$anon$11@3af8e119
Common functions of a IndexedSetter
set
indexedSetter.set(newRecommendations)(seriesMap)
// res1: Map[String,List[String]] =
// Map(tt0903747 -> List(True Detective, Fargo, Dexter, The Mandalorian),
// tt2356777 -> List(Breaking Bad, Fargo, Dexter),
// tt2802850 -> List(Breaking Bad, True Detective, Dexter),
// tt0773262 -> List(Breaking Bad, True Detective, Fargo))
over
indexedSetter.over(_._1 :+ "The Mandalorian")(seriesMap)
// res2: Map[String,List[String]] =
// Map(tt0903747 -> List(True Detective, Fargo, Dexter, The Mandalorian),
// tt2356777 -> List(Breaking Bad, Fargo, Dexter),
// tt2802850 -> List(Breaking Bad, True Detective, Dexter),
// tt0773262 -> List(Breaking Bad, True Detective, Fargo))
Laws
A IndexedSetter
must satisfy all IndexedSetterLaws. These laws reside in the <a href="../../api/proptics/law/>proptics.law package.
Mapping with identity function will get you the same value
def overIdentity[I, S: Eq, A](s: S, indexedSetter: IndexedSetter[I, S, A]): Boolean =
indexedSetter.over(_._1)(s) === s
overIdentity(seriesMap, indexedSetter)
// res0: Boolean = true
Running twice with different handlers is equivalent to running it once with the composition of those handlers
def composeOver[I, S: Eq, A](s: S, indexedSetter: IndexedSetter[I, S, A])
(f: (A, I) => A)
(g: (A, I) => A): Boolean = {
val overTwice = indexedSetter.over(g.tupled)(indexedSetter.over { case (a, i) => f(a, i) }(s))
val composedOver = indexedSetter.over({ case (a, i) => g(f(a, i), i) })(s)
overTwice === composedOver
}
composeOver(seriesMap, indexedSetter)((a, _) => a)((_, _) => List.empty[String])
// res1: Boolean = true
Setting twice is the same as setting once
def setSet[I, S: Eq, A](s: S, a: A, indexedSetter: IndexedSetter[I, S, A]): Boolean =
indexedSetter.set(a)(indexedSetter.set(a)(s)) === indexedSetter.set(a)(s)
setSet(seriesMap, List("The Mandalorian"), indexedSetter)
// res2: Boolean = true