GPL: Generic Programming Library
The library aims to provide several concepts missed in vanilla scala:
- (make) enhanced approach to create new instances of case classes
- (patch) ability to compare, create patch, apply patch for standard scala types
Supported types:
- basic types like
string
,boolean
,numeric
- temporal types (
java.time
,java.util
,java.sql
) - collections
unordered
(Seq, Iterable, ...)ordered
(LinerSeq, List, LazyList, ...)indexed
(Array, Vector, ...)keyed
(Maps)
- generic sum types (Option, Either)
- product types (case classes)
import scalax.patch._
// values
val left = List(1, 2, 3)
val right = List(1, 3, 4, 5)
// make a patch
val patch = Patch.make(left, right)
// apply a patch
patch(left) == right
// apply inverted patch
patch.inverted(right) == left
// patch is structured
// toString
println(patch)
patch> UpdateOrdered(Diff(Upgrade(IncreaseValue(1),IncreaseValue(1)),Insert(4)))
// patch rendered
PatchVisitor stringify patch
patch> upgrade
patch> - increase 1
patch> - increase 1
patch> insert
patch> - 4
Often we need more specific explanation of why one value is not equal to another or, more specifically,
what is a delta of two values, or
what should be modified in value A
(and how) so it become equal to value B
.
When we may need it?
- Testing. More detailed difference reports.
- In network enabled applications where update remote state (patch size is in average less in size then an updated state copy)
- In CQRS applications it might be helpful to disassemble a diff of 2 states into a sequence of events.
The Patch is a sum type of following definition
Patch[T] =
UpdateValue (from: T, to: T) |
SetValue (to: T) |
UnsetValue (from: T) |
IncreaseValue (delta: ArithmeticAdapter[T]#Delta) |
DecreaseValue (delta: ArithmeticAdapter[T]#Delta) |
UpdateIndexed [F[_], V] (delta: Map[Int, Patch[V]], sizeDelta: Int) where T = F[V] |
UpdateKeyed [F[_, _], K, V] (delta: Map[K, Patch[T]]) where T = F[K, V] |
UpdateUnordered [F[_], V] (delta: UnorderedAdapter[F, T]#Diff) where T = F[V] and
UnorderedAdapter.Diff[T] = Seq[UnorderedAdapter.Diff.Evt[T]] where
UnorderedAdapter.Diff.Evt[T] =
Add(items: Seq[T]) |
Remove(items: Seq[T]) |
UpdateOrdered [F[_], V] (delta: OrderedAdapter[F, T]#Diff)
OrderedAdapter.Diff[T] = List[OrderedAdapter.Diff.Evt[T]] where
OrderedAdapter.Diff.Evt[T] =
Skip(n: Int) |
Insert(items: List[T]) |
Drop(items: List[T]) |
Update(patches: List[Patch[T]])
Patch Maker derivation is provided for case classes.
// case classes
case class CC(
name: String,
age: Int,
props: Map[String, String])
object CC {
implicit val ccPM: PatchMaker[CC] = DerivePatchMaker.derive[CC]
}
val left = CC("shelly", 23, Map("prop1" -> "v1", "prop2" -> "v2"))
val right = CC("cristine", 37, Map("prop1" -> "vv1", "prop2" -> "vv2"))
val patch = Patch.make(left, right)
println(patch)
patch> $CC$Patch(UpdateValue(shelly,cristine),IncreaseValue(14),UpdateKeyed(Map(prop1 -> UpdateValue(v1,vv1), prop2 -> UpdateValue(v2,vv2))))
PatchVisitor stringify patch
patch> field 'name' {
patch> update: from shelly
patch> : to cristine
patch> }
patch> field 'age' {
patch> increase 14
patch> }
patch> field 'props' {
patch> key 'prop1' {
patch> update: from v1
patch> : to vv1
patch> }
patch> key 'prop2' {
patch> update: from v2
patch> : to vv2
patch> }
patch> }
By default string patch gives you a Constant patch (SetValue, UpdateValue, UnsetValue), which under some circumstances may look non optimal.
For more sophisticated string manipulation you can use texts
module`
libraryDependencies += "com.github.andyglow" %% "scala-patch-texts" % $version
It is based on google's patch-match-diff
library and give more detailed patches over strings.
All you need is
import scalax.patch.texts._
Example:
import scalax.patch.texts._
val patch = Patch.make("hello, dear friend!", "hello, my friend!")
patch> TextPatch(Step(3, 3, 12, 10, Equal("lo, "), Delete("dear"), Insert("my"), Equal(" fri")))