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

Improve type inference for bidirectional property transformations #452

Merged
merged 5 commits into from
Nov 19, 2019
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ object ModelPropertyListeners extends BenchmarkUtils {
}),
("both-ways transformed property", () => {
val p = ModelProperty(ModelItem.random)
val t = p.transform((v: ModelItem) => v.copy(i = v.i + 1), (v: ModelItem) => v.copy(i = v.i - 1))
val t = p.bitransform(v => v.copy(i = v.i + 1))(v => v.copy(i = v.i - 1))
(p, t)
}),
("one-way transformed property with slow transformer", () => {
Expand All @@ -27,7 +27,7 @@ object ModelPropertyListeners extends BenchmarkUtils {
}),
("both-ways transformed property with slow transformer", () => {
val p = ModelProperty(ModelItem.random)
val t = p.transform((v: ModelItem) => v.copy(i = slowInc(v.i)), (v: ModelItem) => v.copy(i = slowDec(v.i)))
val t = p.bitransform(v => v.copy(i = slowInc(v.i)))(v => v.copy(i = slowDec(v.i)))
(p, t)
})
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ object SinglePropertyListeners extends BenchmarkUtils {
}),
("both-ways transformed property", () => {
val p = Property(0)
val t = p.transform(_ + 1, (v: Int) => v - 1)
val t = p.bitransform(_ + 1)(_ - 1)
(p, t)
}),
("one-way transformed property with slow transformer", () => {
Expand All @@ -27,7 +27,7 @@ object SinglePropertyListeners extends BenchmarkUtils {
}),
("both-ways transformed property with slow transformer", () => {
val p = Property(0)
val t = p.transform(slowInc, (v: Int) => slowDec(v))
val t = p.bitransform(slowInc)(slowDec)
(p, t)
})
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ object TransformedSeqPropertyListeners extends BenchmarkUtils {
}),
("both-ways transformed elements", () => {
val p = SeqProperty(Seq.tabulate(seqSize)(identity))
val t = p.transformElements(_ + 1, (v: Int) => v - 1)
val t = p.bitransformElements(_ + 1)(_ - 1)
(p, t)
}),
("one-way transformed elements with slow transformer", () => {
Expand All @@ -28,7 +28,7 @@ object TransformedSeqPropertyListeners extends BenchmarkUtils {
}),
("both-ways transformed elements with slow transformer", () => {
val p = SeqProperty(Seq.tabulate(seqSize)(identity))
val t = p.transformElements(slowInc, slowDec)
val t = p.bitransformElements(slowInc)(slowDec)
(p, t)
})
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ final class FormElementsFactory(
validator: Validator[Double] = Validator.Default
): UdashBootstrapComponent = {
externalBinding(new InputComponent(
NumberInput(property.transform(_.toString, _.toDouble), debounce)(
NumberInput(property.bitransform(_.toString)(_.toDouble), debounce)(
id := inputId,
BootstrapStyles.Form.control,
inputModifier.map(_.apply(nestedInterceptor)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ object FileInput {
* @return
*/
def single(selectedFile: Property[File])(inputName: String, inputModifiers: Modifier*): InputBinding[JSInput] = {
apply(selectedFile.transformToSeq(Seq(_), _.head), false.toProperty)(inputName, inputModifiers)
apply(selectedFile.bitransformToSeq(Seq(_))(_.head), false.toProperty)(inputName, inputModifiers)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1487,7 +1487,7 @@ class TagsBindingTest extends UdashFrontendTest with Bindings { bindings: Bindin

"work with filtered transformed SeqProperty" in {
val doubles = seq.SeqProperty[Double](1.5, 2.3, 3.7)
val ints = doubles.transformElements(_.toInt, (i: Int) => i.toDouble)
val ints = doubles.bitransformElements(_.toInt)(_.toDouble)
val evens = ints.filter(_ % 2 == 0)

val dom = div(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class CallbackSequencer {

def queue(id: Id, fireListeners: () => Any): Unit = {
if (starts == 0) fireListeners()
else queue += Tuple2(id, fireListeners)
else queue += id -> fireListeners
}

def sequence(code: => Any): Unit = {
Expand Down
8 changes: 4 additions & 4 deletions core/src/main/scala/io/udash/properties/seq/SeqProperty.scala
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@ trait SeqProperty[A, +ElemType <: Property[A]] extends ReadableSeqProperty[A, El
/** Removes all elements from this SeqProperty. */
def clear(): Unit

/** Transforms SeqProperty[A] into SeqProperty[B] element by element.
* Prefer this to `transform` whenever you don't need the whole sequence to perform the transformation.
/** Creates SeqProperty[B] linked to `this`. Changes will be bidirectionally synchronized between `this` and new property.
* Prefer this to `bitransform` whenever you don't need the whole sequence to perform the transformation.
*
* @return New SeqProperty[B], which will be synchronised with original SeqProperty[A]. */
def transformElements[B](transformer: A => B, revert: B => A): SeqProperty[B, Property[B]]
def bitransformElements[B](transformer: A => B)(revert: B => A): SeqProperty[B, Property[B]]

/** Creates `SeqProperty[A]` providing reversed order of elements from `this`. */
override def reversed(): SeqProperty[A, Property[A]]
Expand Down Expand Up @@ -77,7 +77,7 @@ private[properties] trait AbstractSeqProperty[A, +ElemType <: Property[A]]
structureListeners.clear()
}

override def transformElements[B](transformer: A => B, revert: B => A): SeqProperty[B, Property[B]] =
override def bitransformElements[B](transformer: A => B)(revert: B => A): SeqProperty[B, Property[B]] =
new TransformedSeqProperty[A, B](this, transformer, revert)

override def reversed(): SeqProperty[A, Property[A]] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ private[properties] class TransformedSeqProperty[A, B](
with AbstractSeqProperty[B, Property[B]] {

override protected def transformElement(el: Property[A]): Property[B] =
el.transform(transformer, revert)
el.bitransform(transformer)(revert)

override def replaceSeq(idx: Int, amount: Int, values: BSeq[B]): Unit =
origin.replaceSeq(idx, amount, values.map(revert))
Expand Down
8 changes: 4 additions & 4 deletions core/src/main/scala/io/udash/properties/single/Property.scala
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ trait Property[A] extends ReadableProperty[A] {
* @tparam B Type of new Property.
* @return New Property[B], which will be synchronised with original Property[A].
*/
def transform[B](transformer: A => B, revert: B => A): Property[B]
def bitransform[B](transformer: A => B)(revert: B => A): Property[B]

/**
* Creates SeqProperty[B] linked to `this`. Changes will be synchronized with `this` in both directions.
Expand All @@ -49,7 +49,7 @@ trait Property[A] extends ReadableProperty[A] {
* @tparam B Type of elements in new SeqProperty.
* @return New ReadableSeqProperty[B], which will be synchronised with original Property[A].
*/
def transformToSeq[B : PropertyCreator](transformer: A => BSeq[B], revert: BSeq[B] => A): SeqProperty[B, Property[B]]
def bitransformToSeq[B: PropertyCreator](transformer: A => BSeq[B])(revert: BSeq[B] => A): SeqProperty[B, Property[B]]

/**
* Bidirectionally synchronizes Property[B] with `this`. The transformed value is synchronized from `this`
Expand All @@ -73,10 +73,10 @@ private[properties] trait AbstractProperty[A] extends AbstractReadableProperty[A
oneTimeListeners.clear()
}

override def transform[B](transformer: A => B, revert: B => A): Property[B] =
override def bitransform[B](transformer: A => B)(revert: B => A): Property[B] =
new TransformedProperty[A, B](this, transformer, revert)

override def transformToSeq[B : PropertyCreator](transformer: A => BSeq[B], revert: BSeq[B] => A): SeqProperty[B, Property[B]] =
override def bitransformToSeq[B: PropertyCreator](transformer: A => BSeq[B])(revert: BSeq[B] => A): SeqProperty[B, Property[B]] =
new SeqPropertyFromSingleValue(this, transformer, revert)

override def sync[B](p: Property[B])(transformer: A => B, revert: B => A): Registration = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,10 +170,7 @@ class ModelPropertyTest extends UdashCoreTest {
val listener = (v: Any) => values += v

val p = ModelProperty(null: TT)
val t = p.transform[Int](
(p: TT) => p.i + p.t.c.i,
(x: Int) => newTT(x / 2, None, new C(x / 2, ""), Seq.empty)
)
val t = p.bitransform(p => p.i + p.t.c.i)(x => newTT(x / 2, None, new C(x / 2, ""), Seq.empty))

val r1 = p.listen(listener)
val r2 = t.listen(listener)
Expand Down
53 changes: 23 additions & 30 deletions core/src/test/scala/io/udash/properties/PropertyTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -178,13 +178,10 @@ class PropertyTest extends UdashCoreTest {
val oneTimeListener = (v: Any) => oneTimeValues += v

val cp = Property[C](new C(1, "asd"))
val tp = cp.transform[(T, T)](
(c: C) => Tuple2(TC1(c.i), TC2(c.s)),
(t: (T, T)) => t match {
val tp = cp.bitransform(c => (TC1(c.i), TC2(c.s))) {
case (TC1(i), TC2(s)) => new C(i, s)
case _ => new C(0, "")
}
)

tp.listen(listener)
cp.listen(listener)
Expand All @@ -193,55 +190,55 @@ class PropertyTest extends UdashCoreTest {
cp.listenOnce(oneTimeListener)

cp.get should be(new C(1, "asd"))
tp.get should be(Tuple2(TC1(1), TC2("asd")))
tp.get should be(TC1(1) -> TC2("asd"))

cp.set(new C(12, "asd2"))
cp.get should be(new C(12, "asd2"))
tp.get should be(Tuple2(TC1(12), TC2("asd2")))
tp.get should be(TC1(12) -> TC2("asd2"))

tp.set(Tuple2(TC1(-5), TC2("tp")))
tp.set(TC1(-5) -> TC2("tp"))
cp.get should be(new C(-5, "tp"))
tp.get should be(Tuple2(TC1(-5), TC2("tp")))
tp.get should be(TC1(-5) -> TC2("tp"))

tp.set(Tuple2(TC1(-5), TC2("tp")))
tp.set(TC1(-5) -> TC2("tp"))
cp.get should be(new C(-5, "tp"))
tp.get should be(Tuple2(TC1(-5), TC2("tp")))
tp.get should be(TC1(-5) -> TC2("tp"))

tp.touch()
cp.get should be(new C(-5, "tp"))
tp.get should be(Tuple2(TC1(-5), TC2("tp")))
tp.get should be(TC1(-5) -> TC2("tp"))

tp.set(Tuple2(TC1(-5), TC2("tp")), force = true)
tp.set(TC1(-5) -> TC2("tp"), force = true)
cp.get should be(new C(-5, "tp"))
tp.get should be(Tuple2(TC1(-5), TC2("tp")))
tp.get should be(TC1(-5) -> TC2("tp"))

tp.clearListeners()
tp.set(Tuple2(TC1(-12), TC2("tp")))
tp.set(TC1(-12) -> TC2("tp"))

tp.listen(listener)
cp.listen(listener)
tp.set(Tuple2(TC1(-13), TC2("tp")))
tp.set(TC1(-13) -> TC2("tp"))

cp.clearListeners()
tp.set(Tuple2(TC1(-14), TC2("tp")))
tp.set(TC1(-14) -> TC2("tp"))

tp.listen(listener)
cp.listen(listener)
tp.set(Tuple2(TC1(-15), TC2("tp")))
tp.set(TC1(-15) -> TC2("tp"))

values.size should be(12)
values should contain(new C(12, "asd2"))
values should contain(Tuple2(TC1(12), TC2("asd2")))
values should contain(Tuple2(TC1(-5), TC2("tp")))
values should contain(TC1(12) -> TC2("asd2"))
values should contain(TC1(-5) -> TC2("tp"))
values should contain(new C(-5, "tp"))
values should contain(Tuple2(TC1(-13), TC2("tp")))
values should contain(TC1(-13) -> TC2("tp"))
values should contain(new C(-13, "tp"))
values should contain(Tuple2(TC1(-15), TC2("tp")))
values should contain(TC1(-15) -> TC2("tp"))
values should contain(new C(-15, "tp"))

oneTimeValues.size should be(2)
oneTimeValues should contain(new C(12, "asd2"))
oneTimeValues should contain(Tuple2(TC1(12), TC2("asd2")))
oneTimeValues should contain(TC1(12) -> TC2("asd2"))
}

"fire transform method when needed" in {
Expand Down Expand Up @@ -693,10 +690,8 @@ class PropertyTest extends UdashCoreTest {
}

val p = Property("1,2,3,4,5")
val s: SeqProperty[Int, Property[Int]] = p.transformToSeq(
(v: String) => Try(v.split(",").map(_.toInt).toSeq).getOrElse(Seq[Int]()),
(s: BSeq[Int]) => s.mkString(",")
)
val s: SeqProperty[Int, Property[Int]] =
p.bitransformToSeq(v => Try(v.split(",").map(_.toInt).toSeq).getOrElse(Seq[Int]()))(_.mkString(","))

p.listenersCount() should be(0)
registerElementListener(s.elemProperties)
Expand Down Expand Up @@ -869,9 +864,7 @@ class PropertyTest extends UdashCoreTest {
val p = SeqProperty(new ClazzModel(42))
var fromListen = BSeq.empty[Int]

val s = p.transformToSeq(
(v: BSeq[ClazzModel]) => v.map(_.p), (v: BSeq[Int]) => v.map(new ClazzModel(_))
)
val s = p.bitransformToSeq(_.map(_.p))(_.map(new ClazzModel(_)))
p.get.map(_.p) shouldBe Seq(42)
s.get shouldBe Seq(42)

Expand All @@ -889,7 +882,7 @@ class PropertyTest extends UdashCoreTest {

"handle child modification in transformToSeq result" in {
val s = Property("1,2,3,4,5,6")
val i = s.transformToSeq(_.split(",").map(_.toInt), (v: BSeq[Int]) => v.map(_.toString).mkString(","))
val i = s.bitransformToSeq(_.split(",").map(_.toInt))(_.map(_.toString).mkString(","))

i.get should be(Seq(1, 2, 3, 4, 5, 6))

Expand Down
14 changes: 4 additions & 10 deletions core/src/test/scala/io/udash/properties/SeqPropertyTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -376,10 +376,7 @@ class SeqPropertyTest extends UdashCoreTest {

"transform into Property" in {
val p = SeqProperty[Int](1, 2, 3)
val t = p.transform[Int](
(s: BSeq[Int]) => s.sum,
(i: Int) => (1 to i)
)
val t = p.bitransform(_.sum)(1 to _)

p.get should be(Seq(1, 2, 3))
t.get should be(6)
Expand All @@ -406,13 +403,10 @@ class SeqPropertyTest extends UdashCoreTest {
val init: Seq[BadEquals] = (1 to 3).map(new BadEquals(_))
val p = SeqProperty[BadEquals](init)

val t = p.transformElements[T](
(i: BadEquals) => TC1(i.v),
(t: T) => t match {
val t = p.bitransformElements[T](i => TC1(i.v)) {
case TC1(i) => new BadEquals(i)
case _: T => new BadEquals(0)
}
)

p.get.map(_.v) should be(Seq(1, 2, 3))
t.get should be(Seq(TC1(1), TC1(2), TC1(3)))
Expand Down Expand Up @@ -684,7 +678,7 @@ class SeqPropertyTest extends UdashCoreTest {

"be able to modify after transformation" in {
val numbers = SeqProperty[Int](1, 2, 3)
val strings = numbers.transformElements(_.toString, (s: String) => Integer.parseInt(s))
val strings = numbers.bitransformElements(_.toString)(Integer.parseInt)

strings.append("4", "5", "6")
numbers.get should be(Seq(1, 2, 3, 4, 5, 6))
Expand All @@ -696,7 +690,7 @@ class SeqPropertyTest extends UdashCoreTest {

"filter transformed property" in {
val doubles = SeqProperty[Double](1.5, 2.3, 3.7)
val ints = doubles.transformElements(_.toInt, (i: Int) => i.toDouble)
val ints = doubles.bitransformElements(_.toInt)(_.toDouble)
val evens = ints.filter(_ % 2 == 0)

doubles.listenersCount() should be(0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,7 @@ object DatePickerDemo extends AutoDemo with CssView {
),
factory.input.formGroup()(
input = _ => factory.input.select(
pickerOptions.subProp(_.locale).transform[String](
(_: Option[String]).get, Some(_: String)
),
pickerOptions.subProp(_.locale).bitransform(_.get)(Option.apply),
Seq("en_GB", "pl", "ru", "af").toSeqProperty
)(span(_)).render,
labelContent = Some(_ => "Locale")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ object ProgressBarDemo extends AutoDemo {
)(),
),
div(bottomMargin)(
NumberInput(value.transform(_.toString, _.toInt))(
NumberInput(value.bitransform(_.toString)(_.toInt))(
Form.control, placeholder := "Percentage"
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ object SimpleFormDemo extends AutoDemo with CssView {
),
factory.input.formGroup()(
input = _ => factory.input.numberInput(
user.subProp(_.age).transform(_.toDouble, _.toInt),
user.subProp(_.age).bitransform(_.toDouble)(_.toInt),
)(validator = age => if (age < 0) Invalid("Age should be a non-negative integer!") else Valid).render,
labelContent = Some(_ => "Age": Modifier),
invalidFeedback = Some(_ => "Age should be a non-negative integer!")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,11 @@ object CheckButtonsDemo extends AutoDemo {
case object Banana extends Fruit

val favoriteFruits = SeqProperty(Apple, Banana)
val favoriteFruitsStrings = favoriteFruits.transformElements(
_.toString,
(s: String) => s match {
val favoriteFruitsStrings = favoriteFruits.bitransformElements(_.toString) {
case "Apple" => Apple
case "Orange" => Orange
case "Banana" => Banana
}
)

def checkButtons: UdashInputGroup = UdashInputGroup()(
UdashInputGroup.prependText("Fruits:"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,7 @@ object CheckboxDemo extends AutoDemo {
val propA = Property(true)
val propB = Property(false)
val propC = Property("Yes")
val propCAsBoolean = propC.transform(
(s: String) => s.equalsIgnoreCase("yes"),
(b: Boolean) => if (b) "Yes" else "No"
)
val propCAsBoolean = propC.bitransform(_.equalsIgnoreCase("yes"))(if (_) "Yes" else "No")

def checkboxes: JsDom.TypedTag[Div] = div(Grid.row)(
div(Grid.col(4, ResponsiveBreakpoint.Medium))(
Expand Down Expand Up @@ -51,7 +48,7 @@ object CheckboxDemo extends AutoDemo {
checkboxes.render

(checkboxes, checkboxes)
}.withSourceCode
}.withSourceCode

override protected def demoWithSource(): (Modifier, Iterator[String]) = {
import io.udash.bootstrap.utils.BootstrapStyles._
Expand Down
Loading