From 9b09bf14047f7b7f82ac68ea60b8933d0763e21e Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Thu, 6 Jul 2017 22:43:26 -0400 Subject: [PATCH 01/16] Introduce PresenceBox, TryBox, and ParamTryBox. These three intermediary types allow APIs and consumers of boxes to indicate that they expect a particular subset of Box types. This is useful to restrict types to what is expected, while still being able to interoperate with APIs designed to interact with many all Box types. For example, you can declare that a function expectes a PresenceBox to ensure that interactors guarantee either a Full or Empty is passed. Or, you can specify that your function returns a TryBox so callers only need to worry about handling a Full or a Failure. Lastly, ParamTryBox allows indicating that a given situation will only get a Full or a ParamFailure with a particular error type. A lot of tests and additional API conversions are required to fully bring this vision to fruition, but this is the foundation for that work. --- .../main/scala/net/liftweb/common/Box.scala | 145 +++++++++++++----- .../scala/net/liftweb/common/BoxSpec.scala | 102 ++++++++++++ 2 files changed, 210 insertions(+), 37 deletions(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index ad6db68cc4..0b56b9cf6b 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -17,8 +17,8 @@ package net.liftweb package common -import scala.language.implicitConversions import scala.language.existentials +import scala.language.implicitConversions import scala.reflect.Manifest import java.util.{Iterator => JavaIterator, ArrayList => JavaArrayList} @@ -198,7 +198,7 @@ sealed trait BoxTrait { * @return `Full` with the contents if the `Option` is `Some` and `Empty` * otherwise. */ - implicit def option2Box[T](in: Option[T]): Box[T] = Box(in) + implicit def option2Box[T](in: Option[T]): PresenceBox[T] = Box(in) /** * This implicit transformation allows one to use a `Box` as an `Option`. @@ -218,7 +218,7 @@ sealed trait BoxTrait { * * @return `Full` if `in` is not null and `Empty` otherwise. */ - def legacyNullTest[T](in: T): Box[T] = in match { + def legacyNullTest[T](in: T): PresenceBox[T] = in match { case null => Empty case _ => Full(in) } @@ -226,7 +226,7 @@ sealed trait BoxTrait { /** * Alias for `[[legacyNullTest]]`. */ - def !![T](in: T): Box[T] = legacyNullTest(in) + def !![T](in: T): PresenceBox[T] = legacyNullTest(in) /** * Create a `Full` box containing the specified value if `in` is an instance of @@ -600,7 +600,7 @@ sealed abstract class Box[+A] extends Product with Serializable{ * @return A `Failure` with the message if this `Box` is `Empty`, this box * otherwise. */ - def ?~(msg: => String): Box[A] = this + def ?~(msg: => String): TryBox[A] /** * Transform an `Empty` or `Failure` to a `ParamFailure` with the specified @@ -611,12 +611,12 @@ sealed abstract class Box[+A] extends Product with Serializable{ * `ParamFailure` or a `Full`. If this is a `Failure`, the * `ParamFailure` will preserve the message of the `Failure`. */ - def ~>[T](errorCode: => T): Box[A] = this + def ~>[T](errorCode: => T): ParamTryBox[A, T] /** * Alias for `[[?~]]`. */ - def failMsg(msg: => String): Box[A] = ?~(msg) + def failMsg(msg: => String): TryBox[A] = ?~(msg) /** * Chain the given `msg` as a `Failure` ahead of any failures this `Box` may @@ -632,12 +632,12 @@ sealed abstract class Box[+A] extends Product with Serializable{ * this box to the new `Failure` if this is a `Failure`. The unchanged * box if it is a `Full`. */ - def ?~!(msg: => String): Box[A] = ?~(msg) + def ?~!(msg: => String): TryBox[A] = ?~(msg) /** * Alias for `?~!`. */ - def compoundFailMsg(msg: => String): Box[A] = ?~!(msg) + def compoundFailMsg(msg: => String): TryBox[A] = ?~!(msg) /** * If this `Box` contains a value and it satisfies the specified `predicate`, @@ -649,7 +649,7 @@ sealed abstract class Box[+A] extends Product with Serializable{ * @return A `Failure` with the message if the box is empty or the predicate * is not satisfied by the value contained in this Box. */ - def filterMsg(msg: String)(p: A => Boolean): Box[A] = filter(p) ?~ msg + def filterMsg(msg: String)(p: A => Boolean): TryBox[A] = filter(p) ?~ msg /** * This method calls the specified function with the specified `in` value and @@ -790,29 +790,74 @@ sealed abstract class Box[+A] extends Product with Serializable{ } } +/** + * A `Full` or `Empty` box (stands in trivially for `Option`). + */ +sealed trait PresenceBox[+T] extends Box[T] { + def or[B >: T](alternative: => Full[B]): Full[B] + def or[B >: T](alternative: => PresenceBox[B])(implicit a: DummyImplicit): PresenceBox[B] + def or[B >: T](alternative: => TryBox[B])(implicit a: DummyImplicit, b: DummyImplicit): TryBox[B] + def or[B >: T, E](alternative: => ParamTryBox[B, E])(implicit a: DummyImplicit, b: DummyImplicit, c: DummyImplicit): ParamTryBox[B, E] + + def flatMap[B](f: T => PresenceBox[B]): PresenceBox[B] +} +/** + * A `Full` or `Failure` box (somewhat similar to `Try`, but can carry an error + * message without needing an exception, and a parameter as in `ParamFailure`). + */ +sealed trait TryBox[+T] extends Box[T] { + def or[B >: T](alternative: => Full[B]): Full[B] + def or[B >: T](alternative: => PresenceBox[B])(implicit a: DummyImplicit): PresenceBox[B] // * + def or[B >: T](alternative: => TryBox[B])(implicit a: DummyImplicit, b: DummyImplicit): TryBox[B] + def or[B >: T, E](alternative: => ParamTryBox[B, E])(implicit a: DummyImplicit, b: DummyImplicit, c: DummyImplicit): ParamTryBox[B, E] + + def flatMap[B](f: T => TryBox[B]): TryBox[B] +} +/** + * A `Full` or `ParamFailure` box; `T` is the type in the box, `E` is the type + * of the `ParamFailure`'s param. + */ +sealed trait ParamTryBox[+T, +E] extends Box[T] with TryBox[T] { + def or[B >: T](alternative: => Full[B]): Full[B] + def or[B >: T](alternative: => PresenceBox[B])(implicit a: DummyImplicit): PresenceBox[B] // * + def or[B >: T](alternative: => TryBox[B])(implicit a: DummyImplicit, b: DummyImplicit): TryBox[B] + def or[B >: T, E2](alternative: => ParamTryBox[B, E2])(implicit a: DummyImplicit, b: DummyImplicit, c: DummyImplicit): ParamTryBox[B, E2] + + def flatMap[B, E2 >: E](f: T => ParamTryBox[B, E2]): ParamTryBox[B, E2] +} + /** * `Full` is a `[[Box]]` that contains a value. */ -final case class Full[+A](value: A) extends Box[A] { +final case class Full[+A](value: A) extends Box[A] + with PresenceBox[A] + with TryBox[A] + with ParamTryBox[A, Nothing] { def isEmpty: Boolean = false def openOrThrowException(justification: => String): A = value override def openOr[B >: A](default: => B): B = value - override def or[B >: A](alternative: => Box[B]): Box[B] = this + override def or[B >: A](alternative: => Full[B]): Full[A] = this + override def or[B >: A](alternative: => PresenceBox[B])(implicit a: DummyImplicit): Full[A] = this + override def or[B >: A](alternative: => TryBox[B])(implicit a: DummyImplicit, b: DummyImplicit): Full[A] = this + override def or[B >: A, T](alternative: => ParamTryBox[B, T])(implicit a: DummyImplicit, b: DummyImplicit, c: DummyImplicit): Full[A] = this override def exists(func: A => Boolean): Boolean = func(value) override def forall(func: A => Boolean): Boolean = func(value) - override def filter(p: A => Boolean): Box[A] = if (p(value)) this else Empty + override def filter(p: A => Boolean): PresenceBox[A] = if (p(value)) this else Empty override def foreach[U](f: A => U): Unit = f(value) - override def map[B](f: A => B): Box[B] = Full(f(value)) + override def map[B](f: A => B): Full[B] = Full(f(value)) override def flatMap[B](f: A => Box[B]): Box[B] = f(value) + override def flatMap[B](f: A => PresenceBox[B]): PresenceBox[B] = f(value) + override def flatMap[B](f: A => TryBox[B]): TryBox[B] = f(value) + override def flatMap[B, E2 >: Nothing](f: A => ParamTryBox[B, E2]): ParamTryBox[B, E2] = f(value) override def elements: Iterator[A] = Iterator(value) @@ -824,13 +869,16 @@ final case class Full[+A](value: A) extends Box[A] { override def fullXform[T](v: T)(f: T => A => T): T = f(v)(value) + override def ?~(msg: =>String): TryBox[A] = this + override def ?~!(msg: =>String): TryBox[A] = this + override def ~>[T](errorCode: =>T): ParamTryBox[A, T] = this override def toRight[B](left: => B): Either[B, A] = Right(value) override def toLeft[B](right: => B): Either[A, B] = Left(value) - override def isA[B](clsOrg: Class[B]): Box[B] = value match { + override def isA[B](clsOrg: Class[B]): PresenceBox[B] = value match { case value: AnyRef => val cls = Box.primitiveMap.get(clsOrg) match { case Some(c) => c @@ -842,7 +890,11 @@ final case class Full[+A](value: A) extends Box[A] { case _ => Empty } - override def asA[B](implicit m: Manifest[B]): Box[B] = this.isA(m.runtimeClass).asInstanceOf[Box[B]] + override def asA[B](implicit m: Manifest[B]): PresenceBox[B] = { + this + .isA(m.runtimeClass) // convert + .asInstanceOf[PresenceBox[B]] // restore type + } override def ===[B >: A](to: B): Boolean = value == to @@ -853,7 +905,16 @@ final case class Full[+A](value: A) extends Box[A] { * Singleton object representing a completely empty `Box` with no value or * failure information. */ -case object Empty extends EmptyBox +case object Empty extends EmptyBox with PresenceBox[Nothing] { + override def filter(p: Nothing => Boolean): PresenceBox[Nothing] = this + + override def or[B >: Nothing](alternative: => Full[B]): Full[B] = alternative + override def or[B >: Nothing](alternative: => PresenceBox[B])(implicit a: DummyImplicit): PresenceBox[B] = alternative + override def or[B >: Nothing](alternative: => TryBox[B])(implicit a: DummyImplicit, b: DummyImplicit): TryBox[B] = alternative + override def or[B >: Nothing, E](alternative: => ParamTryBox[B, E])(implicit a: DummyImplicit, b: DummyImplicit, c: DummyImplicit): ParamTryBox[B, E] = alternative + + override def flatMap[B](f: Nothing => PresenceBox[B]): PresenceBox[B] = this +} /** * An `EmptyBox` is a `Box` containing no value. It can sometimes carry @@ -893,7 +954,7 @@ object Failure { * exception and/or a chain of previous `Failure`s that may have caused this * one. */ -sealed case class Failure(msg: String, exception: Box[Throwable], chain: Box[Failure]) extends EmptyBox { +sealed case class Failure(msg: String, exception: Box[Throwable], chain: Box[Failure]) extends EmptyBox with TryBox[Nothing] { type A = Nothing override def openOrThrowException(justification: => String) = @@ -902,11 +963,17 @@ sealed case class Failure(msg: String, exception: Box[Throwable], chain: Box[Fai override def getCause() = exception openOr null } - override def map[B](f: A => B): Box[B] = this + override def or[B >: Nothing](alternative: => Full[B]): Full[B] = alternative + override def or[B >: Nothing](alternative: => PresenceBox[B])(implicit a: DummyImplicit): PresenceBox[B] = alternative + override def or[B >: Nothing](alternative: => TryBox[B])(implicit a: DummyImplicit, b: DummyImplicit): TryBox[B] = alternative + override def or[B >: Nothing, E](alternative: => ParamTryBox[B, E])(implicit a: DummyImplicit, b: DummyImplicit, c: DummyImplicit): ParamTryBox[B, E] = alternative + + override def map[B](f: A => B): Failure = this override def flatMap[B](f: A => Box[B]): Box[B] = this + override def flatMap[B](f: A => TryBox[B]): TryBox[B] = this - override def isA[B](cls: Class[B]): Box[B] = this + override def isA[B](cls: Class[B]): Failure = this override def asA[B](implicit m: Manifest[B]): Box[B] = this @@ -1029,25 +1096,29 @@ object ParamFailure { */ final class ParamFailure[T](override val msg: String, override val exception: Box[Throwable], - override val chain: Box[Failure], val param: T) extends - Failure(msg, exception, chain) with Serializable{ - override def toString(): String = "ParamFailure("+msg+", "+exception+ - ", "+chain+", "+param+")" - - override def equals(that: Any): Boolean = that match { - case ParamFailure(m, e, c, p) => - m == msg && e == exception && c == chain && p == param - case _ => false - } + override val chain: Box[Failure], val param: T) + extends Failure(msg, exception, chain) + with ParamTryBox[Nothing, T] + with Serializable { + override def toString(): String = "ParamFailure("+msg+", "+exception+ + ", "+chain+", "+param+")" + + override def equals(that: Any): Boolean = that match { + case ParamFailure(m, e, c, p) => + m == msg && e == exception && c == chain && p == param + case _ => false + } - override def hashCode(): Int = super.hashCode() + (param match { - case null => 0 - case x => x.hashCode() - }) + override def hashCode(): Int = super.hashCode() + (param match { + case null => 0 + case x => x.hashCode() + }) - override def ~>[T](errorCode: => T): ParamFailure[T] = - ParamFailure(msg, exception, Full(this), errorCode) - } + override def ~>[T](errorCode: => T): ParamFailure[T] = + ParamFailure(msg, exception, Full(this), errorCode) + + override def flatMap[B, E2 >: T](f: A => ParamTryBox[B, E2]): ParamTryBox[B, T] = this +} /** * A trait that a class can mix into itself to indicate that it can convert diff --git a/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala b/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala index bcdd55a97f..6c43497aab 100644 --- a/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala +++ b/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala @@ -447,6 +447,108 @@ class BoxSpec extends Specification with ScalaCheck with BoxGenerator { } } + def typingTest = { + val myBox: PresenceBox[String] = Full("hai") + val otherBox: TryBox[String] = Full("bai") + val thirdBox: ParamTryBox[String, Int] = Full("sai") + myBox match { + case Full(hai) => + val thingie: String = hai + case Empty => + val thingie = "" + /*case Failure("hai", _, _) => + println("Fail!")*/ + } + otherBox match { + case Full(hai) => + val thingie: String = hai + /*case Empty => + val thingie = ""*/ + case Failure(hai, _, _) => + println("Fail!" + hai) + } + thirdBox match { + case Full(hai) => + val thingie: String = hai + /*case Failure(hai, _, _) => + println("shai")*/ + /*case Empty => + println("boo")*/ + // FIXME We want the next two flipped, but the compiler won't properly + // FIXME resolve the overloaded unapplies :( + /*case ParamFailure(_, _, _, zem) => + val myThingie: Int = zem*/ + case ParamFailure(_, _, _, zem: String) => + println("bam") + case parammable: ParamFailure[Int] => + val myThingie: Int = parammable.param + } + + val myFailureBox: PresenceBox[String] = Full("bai").filter(_ != "boom") + val otherFailureBox: TryBox[String] = Full("bai").filter(_ != "boom") ?~ "Boom" + val otherOtherFailureBox: TryBox[String] = Full("bai").filter(_ != "boom") ?~ "Boom" ?~! "Asplode" + val thirdFailureBox: ParamTryBox[String, Int] = Full("sai") ?~ "yes" ~> 5 + + val seriousBoxes = + Seq(thirdFailureBox, thirdFailureBox).map { + case intParamFailure: ParamFailure[Int] => + intParamFailure + case a @ Full(stringyThing) => + a + } + + val newThing: Seq[ParamTryBox[String, Int]] = seriousBoxes + + val stringParamFailure = Failure("slap") ~> "boom" + + val orredPresence: PresenceBox[String] = myBox or Empty + val orredFailure: TryBox[String] = myBox or Failure("boom") + val orredParamFailure: ParamTryBox[String, Int] = myBox or thirdBox + val orredParamFailure2: TryBox[String] = myBox or thirdBox + + val flattened: PresenceBox[String] = + myBox.flatMap { a => + Empty + } + val tryFlattened: TryBox[String] = // should fail + otherBox.flatMap { a => + Failure("boom") + } + val paramTryFlattened: TryBox[String] = + thirdBox.flatMap { a => + Failure("boom") ~> "slam" + } + val paramTryQuiteFlattened: ParamTryBox[String, Int] = + thirdBox.flatMap { a => + Failure("boom") ~> 5 + } + val paramTryQuiteFlattened2: ParamTryBox[String, AnyVal] = + thirdBox.flatMap { a => + Failure("boom") ~> 2.3 + } + + // flatMap + // PresenceBox[T] + // flatMap PresenceBox[A] => PresenceBox[A] + // flatMap TryBox[A] => Full flatMap TryBox[A] => TryBox[A] --/-> Box[A] + // => Empty flatMap TryBox[A] => Empty ----/ + // flatMap ParamTryBox[A,E] => Full flatMap PTB[A,E] => PTB[A,E] --/-> Box[A] + // => Empty flatMap PTB[A,E] => Empty ---/ + // TryBox[T] + // flatMap PresenceBox[A] => Full flatMap PresenceBox[A] => PresenceBox[A] --/-> Box[A] + // => Failure flatMap PresenceBox[A] => Failure -----/ + // flatMap TryBox[A] => TryBox[A] + // flatMap ParamTryBox[A,E] => Full flatMap PTB[A,E] => PTB[A,E] -----/-> TryBox[A] + // => Failure flatMap PTB[A,E] => Failure --/ + // ParamTryBox[T,E] + // flatMap PresenceBox[A] => Full flatMap PresenceBox[A] => PresenceBox[A] ------------/-> Box[A] + // => ParamFailure[A,E] flatMap PresenceBox[A] => PF[A,E] -----/ + // flatMap TryBox[A] => TryBox[A] + // flatMap ParamTryBox[A,E] => Full flatMap PTB[A,E] => PTB[A,E] -------/-> PTB[A,E] + // => PTB[A,E] flatMap PTB[A,E] => PTB[A,E] --/ + // flatMap ParamTryBox[A,Z] => Full flatMap PTB[A,Z] => PTB[A,Z] -------/-> TryBox[A] + // => PTB[A,E] flatMap PTB[A,Z] => PTB[A,Z] --/ + } } From b1b3035ae429fd11a6e5fae874025f1fce7781d1 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 8 Jul 2017 11:32:25 -0400 Subject: [PATCH 02/16] Make pass and $ return self.type. This allows us to make sure that we don't lose the specific type of the Box when doing a pass operation. This was less important when the only supertype was `Box`, but now that we can have the more specific `PresenceBox`, `TryBox`, and `ParamTryBox`, preserving those supertypes is more important. --- core/common/src/main/scala/net/liftweb/common/Box.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index 0b56b9cf6b..7dc98295ef 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -667,12 +667,12 @@ sealed abstract class Box[+A] extends Product with Serializable{ * * @return This box. */ - def pass(f: Box[A] => Unit): Box[A] = {f(this) ; this} + def pass(f: Box[A] => Unit): self.type = {f(this) ; this} /** * Alias for `[[pass]]`. */ - def $(f: Box[A] => Unit): Box[A] = pass(f) + def $(f: Box[A] => Unit): self.type = pass(f) /** * For `Full` and `Empty`, this has the expected behavior. Equality in terms From 671a958fe085725b7f9a255d31d0b5855e70a3dc Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 8 Jul 2017 11:39:07 -0400 Subject: [PATCH 03/16] Tweak collect and collectFirst typing for PresenceBox. In particular, collect and collectFirst will always return a PresenceBox when invoked on a PresenceBox. This is not the case for the other subtypes. --- .../main/scala/net/liftweb/common/Box.scala | 43 ++++++++++++++----- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index 7dc98295ef..0ff5011172 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -773,10 +773,14 @@ sealed abstract class Box[+A] extends Product with Serializable{ * If the partial function is defined at the current Box's value, apply the * partial function. */ - final def collect[B](pf: PartialFunction[A, B]): Box[B] = { - flatMap(value => - if (pf.isDefinedAt(value)) Full(pf(value)) - else Empty) + def collect[B](pf: PartialFunction[A, B]): Box[B] = { + flatMap { value => + if (pf.isDefinedAt(value)) { + Full(pf(value)) + } else { + Empty + } + } } /** @@ -785,7 +789,7 @@ sealed abstract class Box[+A] extends Product with Serializable{ * Although this function is different for true collections, because `Box` is * really a collection of 1, the two functions are identical. */ - final def collectFirst[B](pf: PartialFunction[A, B]): Box[B] = { + def collectFirst[B](pf: PartialFunction[A, B]): Box[B] = { collect(pf) } } @@ -793,11 +797,13 @@ sealed abstract class Box[+A] extends Product with Serializable{ /** * A `Full` or `Empty` box (stands in trivially for `Option`). */ -sealed trait PresenceBox[+T] extends Box[T] { - def or[B >: T](alternative: => Full[B]): Full[B] - def or[B >: T](alternative: => PresenceBox[B])(implicit a: DummyImplicit): PresenceBox[B] - def or[B >: T](alternative: => TryBox[B])(implicit a: DummyImplicit, b: DummyImplicit): TryBox[B] - def or[B >: T, E](alternative: => ParamTryBox[B, E])(implicit a: DummyImplicit, b: DummyImplicit, c: DummyImplicit): ParamTryBox[B, E] +sealed trait PresenceBox[+A] extends Box[A] { + def or[B >: A](alternative: => Full[B]): Full[B] + def or[B >: A](alternative: => PresenceBox[B])(implicit a: DummyImplicit): PresenceBox[B] + def or[B >: A](alternative: => TryBox[B])(implicit a: DummyImplicit, b: DummyImplicit): TryBox[B] + def or[B >: A, E](alternative: => ParamTryBox[B, E])(implicit a: DummyImplicit, b: DummyImplicit, c: DummyImplicit): ParamTryBox[B, E] + + override def collect[B](pf: PartialFunction[A, B]): PresenceBox[B] = ??? def flatMap[B](f: T => PresenceBox[B]): PresenceBox[B] } @@ -859,6 +865,21 @@ final case class Full[+A](value: A) extends Box[A] override def flatMap[B](f: A => TryBox[B]): TryBox[B] = f(value) override def flatMap[B, E2 >: Nothing](f: A => ParamTryBox[B, E2]): ParamTryBox[B, E2] = f(value) + // Redefine these two with the more specific type information we have at this + // point. + override def collect[B](pf: PartialFunction[A, B]): PresenceBox[B] = { + flatMap { value => + if (pf.isDefinedAt(value)) { + Full(pf(value)) + } else { + Empty + } + } + } + override def collectFirst[B](pf: PartialFunction[A, B]): PresenceBox[B] = { + collect(pf) + } + override def elements: Iterator[A] = Iterator(value) override def toList: List[A] = List(value) @@ -914,6 +935,8 @@ case object Empty extends EmptyBox with PresenceBox[Nothing] { override def or[B >: Nothing, E](alternative: => ParamTryBox[B, E])(implicit a: DummyImplicit, b: DummyImplicit, c: DummyImplicit): ParamTryBox[B, E] = alternative override def flatMap[B](f: Nothing => PresenceBox[B]): PresenceBox[B] = this + + override def collect[B](pf: PartialFunction[Nothing, B]): PresenceBox[B] = Empty } /** From 592e94ec6582e2761b572eaf0a22e43874e262b5 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 8 Jul 2017 11:40:28 -0400 Subject: [PATCH 04/16] Tweak typing in flatMap, map, flatten, filter, filterNot, isA, and asA. In particular, cases where each of these can guarantee a subtype of Box have reimplementations that do so. PresenceBox also adds a specialization of withFilter, which is used in for comprehensions, that ensures that typing carries through. --- .../main/scala/net/liftweb/common/Box.scala | 67 ++++++++++++++----- 1 file changed, 49 insertions(+), 18 deletions(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index 0ff5011172..618e02f074 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -798,38 +798,66 @@ sealed abstract class Box[+A] extends Product with Serializable{ * A `Full` or `Empty` box (stands in trivially for `Option`). */ sealed trait PresenceBox[+A] extends Box[A] { + override def isA[B](clsOrg: Class[B]): PresenceBox[B] = Empty + override def asA[B](implicit m: Manifest[B]): PresenceBox[B] = Empty + def or[B >: A](alternative: => Full[B]): Full[B] def or[B >: A](alternative: => PresenceBox[B])(implicit a: DummyImplicit): PresenceBox[B] def or[B >: A](alternative: => TryBox[B])(implicit a: DummyImplicit, b: DummyImplicit): TryBox[B] def or[B >: A, E](alternative: => ParamTryBox[B, E])(implicit a: DummyImplicit, b: DummyImplicit, c: DummyImplicit): ParamTryBox[B, E] + override def map[B](f: A => B): PresenceBox[B] = Empty + def flatMap[B](f: A => PresenceBox[B]): PresenceBox[B] override def collect[B](pf: PartialFunction[A, B]): PresenceBox[B] = ??? - def flatMap[B](f: T => PresenceBox[B]): PresenceBox[B] + def flatten[B](implicit ev: A <:< PresenceBox[B]): PresenceBox[B] = this match { + case Full(internal) => ev(internal) + case Empty => Empty + } + + override def filter(p: A => Boolean): PresenceBox[A] = this + override def filterNot(f: A => Boolean): PresenceBox[A] = filter(a => !f(a)) + + override def withFilter(p: A => Boolean): WithPresenceFilter = new WithPresenceFilter(p) + + class WithPresenceFilter(p: A => Boolean) extends WithFilter(p) { + override def map[B](f: A => B): PresenceBox[B] = PresenceBox.this.filter(p).map(f) + def flatMap[B](f: A => PresenceBox[B]): PresenceBox[B] = PresenceBox.this.filter(p).flatMap(f) + override def foreach[U](f: A => U): Unit = PresenceBox.this.filter(p).foreach(f) + override def withFilter(q: A => Boolean): WithPresenceFilter = + new WithPresenceFilter(x => p(x) && q(x)) + } } /** * A `Full` or `Failure` box (somewhat similar to `Try`, but can carry an error * message without needing an exception, and a parameter as in `ParamFailure`). */ -sealed trait TryBox[+T] extends Box[T] { - def or[B >: T](alternative: => Full[B]): Full[B] - def or[B >: T](alternative: => PresenceBox[B])(implicit a: DummyImplicit): PresenceBox[B] // * - def or[B >: T](alternative: => TryBox[B])(implicit a: DummyImplicit, b: DummyImplicit): TryBox[B] - def or[B >: T, E](alternative: => ParamTryBox[B, E])(implicit a: DummyImplicit, b: DummyImplicit, c: DummyImplicit): ParamTryBox[B, E] +sealed trait TryBox[+A] extends Box[A] { + def or[B >: A](alternative: => Full[B]): Full[B] + def or[B >: A](alternative: => PresenceBox[B])(implicit a: DummyImplicit): PresenceBox[B] // * + def or[B >: A](alternative: => TryBox[B])(implicit a: DummyImplicit, b: DummyImplicit): TryBox[B] + def or[B >: A, E](alternative: => ParamTryBox[B, E])(implicit a: DummyImplicit, b: DummyImplicit, c: DummyImplicit): ParamTryBox[B, E] + + override def map[B](f: A => B): TryBox[B] = ??? + def flatMap[B](f: A => TryBox[B]): TryBox[B] - def flatMap[B](f: T => TryBox[B]): TryBox[B] + def flatten[B](implicit ev: A <:< TryBox[B]): TryBox[B] = this match { + case Full(internal) => ev(internal) + case failure: Failure => failure + } } /** - * A `Full` or `ParamFailure` box; `T` is the type in the box, `E` is the type + * A `Full` or `ParamFailure` box; `A` is the type in the box, `E` is the type * of the `ParamFailure`'s param. */ -sealed trait ParamTryBox[+T, +E] extends Box[T] with TryBox[T] { - def or[B >: T](alternative: => Full[B]): Full[B] - def or[B >: T](alternative: => PresenceBox[B])(implicit a: DummyImplicit): PresenceBox[B] // * - def or[B >: T](alternative: => TryBox[B])(implicit a: DummyImplicit, b: DummyImplicit): TryBox[B] - def or[B >: T, E2](alternative: => ParamTryBox[B, E2])(implicit a: DummyImplicit, b: DummyImplicit, c: DummyImplicit): ParamTryBox[B, E2] +sealed trait ParamTryBox[+A, +E] extends Box[A] with TryBox[A] { + def or[B >: A](alternative: => Full[B]): Full[B] + def or[B >: A](alternative: => PresenceBox[B])(implicit a: DummyImplicit): PresenceBox[B] // * + def or[B >: A](alternative: => TryBox[B])(implicit a: DummyImplicit, b: DummyImplicit): TryBox[B] + def or[B >: A, E2](alternative: => ParamTryBox[B, E2])(implicit a: DummyImplicit, b: DummyImplicit, c: DummyImplicit): ParamTryBox[B, E2] - def flatMap[B, E2 >: E](f: T => ParamTryBox[B, E2]): ParamTryBox[B, E2] + override def map[B](f: A => B): ParamTryBox[B, E] = ??? + def flatMap[B, E2 >: E](f: A => ParamTryBox[B, E2]): ParamTryBox[B, E2] } /** @@ -927,7 +955,7 @@ final case class Full[+A](value: A) extends Box[A] * failure information. */ case object Empty extends EmptyBox with PresenceBox[Nothing] { - override def filter(p: Nothing => Boolean): PresenceBox[Nothing] = this + override def filter(p: Nothing => Boolean) = this override def or[B >: Nothing](alternative: => Full[B]): Full[B] = alternative override def or[B >: Nothing](alternative: => PresenceBox[B])(implicit a: DummyImplicit): PresenceBox[B] = alternative @@ -954,8 +982,6 @@ sealed abstract class EmptyBox extends Box[Nothing] with Serializable { override def or[B >: Nothing](alternative: => Box[B]): Box[B] = alternative - override def filter(p: Nothing => Boolean): Box[Nothing] = this - override def ?~(msg: => String): Failure = Failure(msg, Empty, Empty) override def ?~!(msg: => String): Failure = Failure(msg, Empty, Empty) @@ -991,6 +1017,8 @@ sealed case class Failure(msg: String, exception: Box[Throwable], chain: Box[Fai override def or[B >: Nothing](alternative: => TryBox[B])(implicit a: DummyImplicit, b: DummyImplicit): TryBox[B] = alternative override def or[B >: Nothing, E](alternative: => ParamTryBox[B, E])(implicit a: DummyImplicit, b: DummyImplicit, c: DummyImplicit): ParamTryBox[B, E] = alternative + override def filter(p: Nothing => Boolean): Failure = this + override def map[B](f: A => B): Failure = this override def flatMap[B](f: A => Box[B]): Box[B] = this @@ -1140,7 +1168,10 @@ final class ParamFailure[T](override val msg: String, override def ~>[T](errorCode: => T): ParamFailure[T] = ParamFailure(msg, exception, Full(this), errorCode) - override def flatMap[B, E2 >: T](f: A => ParamTryBox[B, E2]): ParamTryBox[B, T] = this + override def map[B](f: A => B): ParamFailure[T] = this + override def flatMap[B, E2 >: T](f: A => ParamTryBox[B, E2]): ParamFailure[T] = this + + override def filter(p: Nothing => Boolean): ParamFailure[T] = this } /** From f22afbe9be346d283e22ee76facac7e40c6b6e13 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 8 Jul 2017 11:45:30 -0400 Subject: [PATCH 05/16] Adjust typing on a few Box singleton functions. These can now have more specific types. --- .../src/main/scala/net/liftweb/common/Box.scala | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index 618e02f074..8b8e0e7dc4 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -82,7 +82,7 @@ object Box extends BoxTrait with Tryo { * @param failureErrorMessage The string that should be placed in the message for the Failure. * @return A `Full[List[T]]` if no `Failure`s were present. `ParamFailure[List[Box[T]]]` otherwise. **/ - def toSingleBox(failureErrorMessage: String): Box[List[T]] = { + def toSingleBox(failureErrorMessage: String): ParamTryBox[List[T], List[Box[T]]] = { if (theListOfBoxes.exists(_.isInstanceOf[Failure])) { val failureChain = theListOfBoxes.collect { @@ -167,8 +167,9 @@ sealed trait BoxTrait { * @return A `Full` containing the transformed value if * `pf.isDefinedAt(value)` and `Empty` otherwise. */ - def apply[InType, OutType](pf: PartialFunction[InType, OutType])(value: InType): Box[OutType] = - if (pf.isDefinedAt(value)) Full(pf(value)) else Empty + def apply[InType, OutType](pf: PartialFunction[InType, OutType])(value: InType): PresenceBox[OutType] = { + Full(value).collect(pf) + } /** * Apply the specified `PartialFunction` to the specified `value` and return @@ -180,8 +181,9 @@ sealed trait BoxTrait { * @return A `Full` containing the transformed value if * `pf.isDefinedAt(value)` and `Empty` otherwise. */ - def apply[InType, OutType](value: InType)(pf: PartialFunction[InType, OutType]): Box[OutType] = - if (pf.isDefinedAt(value)) Full(pf(value)) else Empty + def apply[InType, OutType](value: InType)(pf: PartialFunction[InType, OutType]): PresenceBox[OutType] = { + Full(value).collect(pf) + } /** * This implicit transformation allows one to use a `Box` as an `Iterable` of @@ -244,7 +246,7 @@ sealed trait BoxTrait { * res1: net.liftweb.common.Box[Int] = Full(5) * }}} */ - def isA[A, B](in: A, clz: Class[B]): Box[B] = { + def isA[A, B](in: A, clz: Class[B]): PresenceBox[B] = { (Box !! in).isA(clz) } @@ -271,7 +273,7 @@ sealed trait BoxTrait { * res1: net.liftweb.common.Box[Int] = Full(5) * }}} */ - def asA[B](in: T forSome { type T })(implicit m: Manifest[B]): Box[B] = { + def asA[B](in: T forSome { type T })(implicit m: Manifest[B]): PresenceBox[B] = { (Box !! in).asA[B] } } From 9b906eb7364c8d015430e4411e5d610d06ef66d8 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 8 Jul 2017 11:45:50 -0400 Subject: [PATCH 06/16] Scaladoc updates for a few places in Box. Also added a blurb to the Box scaladocs regarding the Box subtypes. --- .../main/scala/net/liftweb/common/Box.scala | 47 +++++++++++++++---- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index 8b8e0e7dc4..e40f6dd9e6 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -234,8 +234,8 @@ sealed trait BoxTrait { * Create a `Full` box containing the specified value if `in` is an instance of * the specified class `clz` and `Empty` otherwise. * - * This is basically a Java-friendly version of `[[asA]]`, which you should - * prefer when using Scala. + * This is basically a Java-friendly version of `[[asA]]`; you should prefer + * `asA` when using Scala. * * For example: * {{{ @@ -291,14 +291,15 @@ final class DoNotCallThisMethod * (containing a single non-null value) or `[[EmptyBox]]`. An `EmptyBox`, * or empty, can be the `[[Empty]]` singleton, `[[Failure]]` or * `[[ParamFailure]]`. `Failure` and `ParamFailure` contain information about - * why the `Box` is empty including exception information, possibly chained - * `Failure`s and a `String` message. + * why the `Box` is empty including possible exception information, possibly + * chained `Failure`s and a `String` message. * * This serves a similar purpose to the `[[scala.Option Option]]` class from - * Scala standard library but adds several features: + * Scala standard library, combined with the `[[scala.Try Try]]` class from the + * same library, and adds several features to boot: * - You can transform it to a `Failure` object if it is `Empty` (with the * `[[?~]]` or `[[failMsg]]` method). - * - You can chain failure messages on `Failure`s (with the `?~!` or + * - You can chain failure messages on existing `Failure`s (with the `?~!` or * `[[compoundFailMsg]]` method). * - You can "run" a function on a `Box`, with a default to return if the box * is `Empty`: @@ -325,6 +326,12 @@ final class DoNotCallThisMethod * ) // doSomething gets a Box[Int] as well * }}} * + * You can also choose the first `Full` box of a set by using `or` (as with the + * `Option` equivalent, `orElse`) and provide an alternative to a `Box`'s value + * using `openOr` (as with the `Option` equivalent, `getOrElse`). Boxes can be + * ``filter``ed, ``map``ed, ``flatMap``ed, and converted to zero-or-one element + * ``Iterable``s or ``List``s as needed. + * * === Exceptions and Empty Box Handling === * * If you grew up on Java, you're used to `Exception`s as part of your program @@ -384,8 +391,29 @@ final class DoNotCallThisMethod * loggedInUser must_== Full(mockUser) * (loggedInUser === mockUser) must beTrue * }}} + * + * Additionally, when interacting with external APIs, you can look at using the + * `lift-util` helper `tryo`, which executes a function and catches exceptions, + * converting thrown exceptions into `Failure` boxes. + * + * === `Box` Subtypes and Clear Typing === + * + * In addition to the standard hierarchy above, `Box` lets you declare your + * type expectations in a little narrower fashion. If you're expecting to only + * deal with `Full[T]` or `Failure`, you can declare that you expect to return or + * want to receive a `TryBox[T]`. The compiler will then enforce that an `Empty` + * cannot be provided. If you're expecting only `Full[T]` or `Empty` instead, + * you can declare that you expect to return or want to receive a `PresenceBox[T]`. + * Here again, the compiler will enforce a `Failure` cannot be provided. Finally, + * if you're expecting specifically a `Full[T]` or a `ParamFailure[E]`, then you + * can declare that you expect to return or want to receive a `ParamTryBox[T, E]`. + * Here, the compiler will disallow both `Empty` and a bare `Failure`. + * + * The methods on `Box` itself generally return the correct subtypes, so that + * you can rely on transformations like `map` and `flatMap` to preserve the + * closest match to the expected types that are being sent through. */ -sealed abstract class Box[+A] extends Product with Serializable{ +sealed abstract class Box[+A] extends Product with Serializable { self => /** * Returns `true` if this `Box` contains no value (i.e., it is `Empty` or @@ -458,13 +486,16 @@ sealed abstract class Box[+A] extends Product with Serializable{ */ def flatMap[B](f: A => Box[B]): Box[B] = Empty + /** + * Flatten this `Box[Box[T]]` to be a `Box[T]`. The compiler will prevent + * calling this function if this is a `Box[T]` instead of a `Box[Box[T]]`. + */ def flatten[B](implicit ev: A <:< Box[B]): Box[B] = this match { case Full(internal) => ev(internal) case f: Failure => f case Empty => Empty } - /** * If this `Box` contains a value and it satisfies the specified `predicate`, * return the `Box` unchanged. Otherwise, return an `Empty`. From 90ad32f242f5dd28def1f2cf6cbcc1e7f2956247 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 8 Jul 2017 11:46:19 -0400 Subject: [PATCH 07/16] Add usage docs for CombinableBox. --- .../net/liftweb/common/CombinableBox.scala | 45 +++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/core/common/src/main/scala/net/liftweb/common/CombinableBox.scala b/core/common/src/main/scala/net/liftweb/common/CombinableBox.scala index 38e80c4a6b..030af3907e 100644 --- a/core/common/src/main/scala/net/liftweb/common/CombinableBox.scala +++ b/core/common/src/main/scala/net/liftweb/common/CombinableBox.scala @@ -14,7 +14,7 @@ * limitations under the License. */ -package net.liftweb +package net.liftweb package common import scala.language.implicitConversions @@ -22,6 +22,45 @@ import scala.language.implicitConversions /** * Via an `[[HLists.HList HList]]` containing a collection of `[[Box]]`, either generates an * `HList` of the things (unboxed) or a `List[Failure]`. + * + * Usage of this is in two parts: first, you import `CombinableBox._`; then, you + * can use the `:&:` operator to build up your `HList`. For example: + * + * {{{ + * scala> import net.liftweb.common._; import CombinableBox._ + * scala> Full("test") :&: Full("second test") + * res0: net.liftweb.common.CombinableBox.Result[String :+: String :+: net.liftweb.common.HLists.HNil] = Right(test :+: second test :+: HNil) + * scala> Full("test") :&: Failure("boom") + * res1: net.liftweb.common.CombinableBox.Result[String :+: Nothing :+: net.liftweb.common.HLists.HNil] = Left(List(Failure(boom,Empty,Empty))) + * scala> Failure("boom") :&: Failure("boom boom") + * res2: net.liftweb.common.CombinableBox.Result[Nothing :+: Nothing :+: net.liftweb.common.HLists.HNil] = Left(List(Failure(boom,Empty,Empty), Failure(boom boom,Empty,Empty))) + * }}} + * + * `CombinableBox` also contains some implicits to help deal with the outcome. + * By default, results are represented by an `Either[List[Failure], ]`. + * Instead, we can convert this back to a `Box` representation: + * + * {{{ + * scala> import net.liftweb.common.HLists._ + * scala> type ExpectedHList = String :+: String :+: HNil + * defined type alias ExpectedHList + * scala> val combinedFulls: Box[ExpectedHList] = Full("test") :&: Full("second test") + * combinedFulls: net.liftweb.common.Box[ExpectedHList] = Full(test :+: second test :+: HNil) + * scala> val combinedFailure: Box[ExpectedHList] = Full("test") :&: (Failure("boom"): Box[String]) + * combinedFailure: net.liftweb.common.Box[ExpectedHList] = Failure(boom,Empty,Empty) + * scala> val combinedFailures: Box[ExpectedHList] = (Failure("boom"): Box[String]) :&: (Failure("boom boom"): Box[String]) + * combinedFailures: net.liftweb.common.Box[ExpectedHList] = ParamFailure(Multiple Failures, Empty, Empty, FailureList(List(Failure(boom,Empty,Empty), Failure(boom boom,Empty,Empty)))) + * }}} + * + * Note that in case multiple failures are involved, we generate a `ParamFailure` + * whose parameter is a `FailureList` object, which contains the failures found + * along the way. + * + * In many cases, it is more straightforward to use `ListOfBoxes` to deal with a + * list of `Box` that needs to be a `Box` of `List` instead. It works in a very + * similar way, but works on regular lists instead of ``HList``s. `CombinableBox` + * is best reserved for when you want to preserve heterogeneous types out of a + * set of boxes. */ object CombinableBox { import HLists._ @@ -53,7 +92,7 @@ object CombinableBox { FailureList(f)) case Right(x) => Full(x) } - + /** * If the `[[Failure]]` is going to be condensed, a `FailureList` is generated * to allow type-safe pattern matches without worrying about erasure. @@ -73,6 +112,6 @@ object CombinableBox { case (Full(success), Right(successes)) => Right(success :+: successes) } } - + } From a8f140202f3e3ac19effeec5d0401ffdfae866e8 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sat, 5 Aug 2017 12:02:55 -0400 Subject: [PATCH 08/16] Drop attempt at flatten type specialization. Due to the way Scala resolves method overloads with implicit parameters, I haven't been able to find a good way of defining that a TryBox that contains a TryBox should flatten to a TryBox, and a PresenceBox that contains a PresenceBox should flatten to a PresenceBox. Unfortunately Scala seems to resolve the overload of flatten based solely on class hierarchy and number of parameters, and then fails the compile if it can't plug the proper implicits in, rather than using the available implicit evidence to choose between overloads. Since we rely entirely on implicit evidence to know what we can do with the contained type of the box, there's no way (that I could find) to express diverging return types based on the contained type :( Instead, TryBox and PresenceBox each have their own flattenTry and flattenPresence methods, respectively, which will only compile if their contents match the container type. This means the caller has to choose the appropriate one to call, but at least it ensures you *can* flatten that way if you really want to. --- core/common/src/main/scala/net/liftweb/common/Box.scala | 6 +++--- core/common/src/test/scala/net/liftweb/common/BoxSpec.scala | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index e40f6dd9e6..b4c7c2e2e2 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -843,7 +843,7 @@ sealed trait PresenceBox[+A] extends Box[A] { def flatMap[B](f: A => PresenceBox[B]): PresenceBox[B] override def collect[B](pf: PartialFunction[A, B]): PresenceBox[B] = ??? - def flatten[B](implicit ev: A <:< PresenceBox[B]): PresenceBox[B] = this match { + def flattenPresence[B](implicit ev: A <:< PresenceBox[B]): PresenceBox[B] = this match { case Full(internal) => ev(internal) case Empty => Empty } @@ -874,9 +874,9 @@ sealed trait TryBox[+A] extends Box[A] { override def map[B](f: A => B): TryBox[B] = ??? def flatMap[B](f: A => TryBox[B]): TryBox[B] - def flatten[B](implicit ev: A <:< TryBox[B]): TryBox[B] = this match { + def flattenTry[B](implicit ev: A <:< TryBox[B]): TryBox[B] = this match { case Full(internal) => ev(internal) - case failure: Failure => failure + case f: Failure => f } } /** diff --git a/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala b/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala index 6c43497aab..11fa18573b 100644 --- a/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala +++ b/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala @@ -144,12 +144,16 @@ class BoxSpec extends Specification with ScalaCheck with BoxGenerator { "define a 'flatten' method if it contains another Box." in { "If the inner box is a Full box, the final result is identical to that box" in { Full(Full(1)).flatten must_== Full(1) + (Full(Full(1)).flattenTry: TryBox[Int]) must_== Full(1) + (Full(Full(1)).flattenPresence: PresenceBox[Int]) must_== Full(1) } "If the inner box is a Failure, the final result is identical to that box" in { Full(Failure("error", Empty, Empty)).flatten must_== Failure("error", Empty, Empty) + (Full(Failure("error", Empty, Empty)).flattenTry: TryBox[Int]) must_== Failure("error", Empty, Empty) } "If the inner box is an Empty box, the final result is identical to that box" in { Full(Empty).flatten must_== Empty + (Full(Empty).flattenPresence: PresenceBox[Int]) must_== Empty } } "define an 'elements' method returning an iterator containing its value" in { From ccb507b6b925c1e13447b30185e6431bc46a0a13 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 17 Sep 2017 09:14:44 -0400 Subject: [PATCH 09/16] Tweak or to allow Full or Box => Full. --- .../main/scala/net/liftweb/common/Box.scala | 49 ++++++++++--------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index b4c7c2e2e2..fe04324d29 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -834,10 +834,10 @@ sealed trait PresenceBox[+A] extends Box[A] { override def isA[B](clsOrg: Class[B]): PresenceBox[B] = Empty override def asA[B](implicit m: Manifest[B]): PresenceBox[B] = Empty - def or[B >: A](alternative: => Full[B]): Full[B] - def or[B >: A](alternative: => PresenceBox[B])(implicit a: DummyImplicit): PresenceBox[B] - def or[B >: A](alternative: => TryBox[B])(implicit a: DummyImplicit, b: DummyImplicit): TryBox[B] - def or[B >: A, E](alternative: => ParamTryBox[B, E])(implicit a: DummyImplicit, b: DummyImplicit, c: DummyImplicit): ParamTryBox[B, E] + def or[B >: A](alternative: => Full[B])(implicit a: DummyImplicit): Full[B] + def or[B >: A](alternative: => PresenceBox[B])(implicit a: DummyImplicit, b: DummyImplicit): PresenceBox[B] + def or[B >: A](alternative: => TryBox[B])(implicit a: DummyImplicit, b: DummyImplicit, c: DummyImplicit): TryBox[B] + def or[B >: A, E](alternative: => ParamTryBox[B, E])(implicit a: DummyImplicit, b: DummyImplicit, c: DummyImplicit, d: DummyImplicit): ParamTryBox[B, E] override def map[B](f: A => B): PresenceBox[B] = Empty def flatMap[B](f: A => PresenceBox[B]): PresenceBox[B] @@ -866,10 +866,10 @@ sealed trait PresenceBox[+A] extends Box[A] { * message without needing an exception, and a parameter as in `ParamFailure`). */ sealed trait TryBox[+A] extends Box[A] { - def or[B >: A](alternative: => Full[B]): Full[B] - def or[B >: A](alternative: => PresenceBox[B])(implicit a: DummyImplicit): PresenceBox[B] // * - def or[B >: A](alternative: => TryBox[B])(implicit a: DummyImplicit, b: DummyImplicit): TryBox[B] - def or[B >: A, E](alternative: => ParamTryBox[B, E])(implicit a: DummyImplicit, b: DummyImplicit, c: DummyImplicit): ParamTryBox[B, E] + def or[B >: A](alternative: => Full[B])(implicit a: DummyImplicit): Full[B] + def or[B >: A](alternative: => PresenceBox[B])(implicit a: DummyImplicit, b: DummyImplicit): PresenceBox[B] // * + def or[B >: A](alternative: => TryBox[B])(implicit a: DummyImplicit, b: DummyImplicit, c: DummyImplicit): TryBox[B] + def or[B >: A, E](alternative: => ParamTryBox[B, E])(implicit a: DummyImplicit, b: DummyImplicit, c: DummyImplicit, d: DummyImplicit): ParamTryBox[B, E] override def map[B](f: A => B): TryBox[B] = ??? def flatMap[B](f: A => TryBox[B]): TryBox[B] @@ -884,10 +884,10 @@ sealed trait TryBox[+A] extends Box[A] { * of the `ParamFailure`'s param. */ sealed trait ParamTryBox[+A, +E] extends Box[A] with TryBox[A] { - def or[B >: A](alternative: => Full[B]): Full[B] - def or[B >: A](alternative: => PresenceBox[B])(implicit a: DummyImplicit): PresenceBox[B] // * - def or[B >: A](alternative: => TryBox[B])(implicit a: DummyImplicit, b: DummyImplicit): TryBox[B] - def or[B >: A, E2](alternative: => ParamTryBox[B, E2])(implicit a: DummyImplicit, b: DummyImplicit, c: DummyImplicit): ParamTryBox[B, E2] + def or[B >: A](alternative: => Full[B])(implicit a: DummyImplicit): Full[B] + def or[B >: A](alternative: => PresenceBox[B])(implicit a: DummyImplicit, b: DummyImplicit): PresenceBox[B] // * + def or[B >: A](alternative: => TryBox[B])(implicit a: DummyImplicit, b: DummyImplicit, c: DummyImplicit): TryBox[B] + def or[B >: A, E2](alternative: => ParamTryBox[B, E2])(implicit a: DummyImplicit, b: DummyImplicit, c: DummyImplicit, d: DummyImplicit): ParamTryBox[B, E2] override def map[B](f: A => B): ParamTryBox[B, E] = ??? def flatMap[B, E2 >: E](f: A => ParamTryBox[B, E2]): ParamTryBox[B, E2] @@ -906,10 +906,11 @@ final case class Full[+A](value: A) extends Box[A] override def openOr[B >: A](default: => B): B = value - override def or[B >: A](alternative: => Full[B]): Full[A] = this - override def or[B >: A](alternative: => PresenceBox[B])(implicit a: DummyImplicit): Full[A] = this - override def or[B >: A](alternative: => TryBox[B])(implicit a: DummyImplicit, b: DummyImplicit): Full[A] = this - override def or[B >: A, T](alternative: => ParamTryBox[B, T])(implicit a: DummyImplicit, b: DummyImplicit, c: DummyImplicit): Full[A] = this + override def or[B >: A](alternative: => Box[B]): Full[A] = this + override def or[B >: A](alternative: => Full[B])(implicit a: DummyImplicit): Full[A] = this + override def or[B >: A](alternative: => PresenceBox[B])(implicit a: DummyImplicit, b: DummyImplicit): Full[A] = this + override def or[B >: A](alternative: => TryBox[B])(implicit a: DummyImplicit, b: DummyImplicit, c: DummyImplicit): Full[A] = this + override def or[B >: A, T](alternative: => ParamTryBox[B, T])(implicit a: DummyImplicit, b: DummyImplicit, c: DummyImplicit, d: DummyImplicit): Full[A] = this override def exists(func: A => Boolean): Boolean = func(value) @@ -990,10 +991,10 @@ final case class Full[+A](value: A) extends Box[A] case object Empty extends EmptyBox with PresenceBox[Nothing] { override def filter(p: Nothing => Boolean) = this - override def or[B >: Nothing](alternative: => Full[B]): Full[B] = alternative - override def or[B >: Nothing](alternative: => PresenceBox[B])(implicit a: DummyImplicit): PresenceBox[B] = alternative - override def or[B >: Nothing](alternative: => TryBox[B])(implicit a: DummyImplicit, b: DummyImplicit): TryBox[B] = alternative - override def or[B >: Nothing, E](alternative: => ParamTryBox[B, E])(implicit a: DummyImplicit, b: DummyImplicit, c: DummyImplicit): ParamTryBox[B, E] = alternative + override def or[B >: Nothing](alternative: => Full[B])(implicit a: DummyImplicit): Full[B] = alternative + override def or[B >: Nothing](alternative: => PresenceBox[B])(implicit a: DummyImplicit, b: DummyImplicit): PresenceBox[B] = alternative + override def or[B >: Nothing](alternative: => TryBox[B])(implicit a: DummyImplicit, b: DummyImplicit, c: DummyImplicit): TryBox[B] = alternative + override def or[B >: Nothing, E](alternative: => ParamTryBox[B, E])(implicit a: DummyImplicit, b: DummyImplicit, c: DummyImplicit, d: DummyImplicit): ParamTryBox[B, E] = alternative override def flatMap[B](f: Nothing => PresenceBox[B]): PresenceBox[B] = this @@ -1045,10 +1046,10 @@ sealed case class Failure(msg: String, exception: Box[Throwable], chain: Box[Fai override def getCause() = exception openOr null } - override def or[B >: Nothing](alternative: => Full[B]): Full[B] = alternative - override def or[B >: Nothing](alternative: => PresenceBox[B])(implicit a: DummyImplicit): PresenceBox[B] = alternative - override def or[B >: Nothing](alternative: => TryBox[B])(implicit a: DummyImplicit, b: DummyImplicit): TryBox[B] = alternative - override def or[B >: Nothing, E](alternative: => ParamTryBox[B, E])(implicit a: DummyImplicit, b: DummyImplicit, c: DummyImplicit): ParamTryBox[B, E] = alternative + override def or[B >: Nothing](alternative: => Full[B])(implicit a: DummyImplicit): Full[B] = alternative + override def or[B >: Nothing](alternative: => PresenceBox[B])(implicit a: DummyImplicit, b: DummyImplicit): PresenceBox[B] = alternative + override def or[B >: Nothing](alternative: => TryBox[B])(implicit a: DummyImplicit, b: DummyImplicit, c: DummyImplicit): TryBox[B] = alternative + override def or[B >: Nothing, E](alternative: => ParamTryBox[B, E])(implicit a: DummyImplicit, b: DummyImplicit, c: DummyImplicit, d: DummyImplicit): ParamTryBox[B, E] = alternative override def filter(p: Nothing => Boolean): Failure = this From f20573cc3b28bfa16a7c782818394fd87db3f31d Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 17 Sep 2017 09:16:00 -0400 Subject: [PATCH 10/16] Add preliminary BoxTypingSpec. This spec is designed to test that Box's various methods produce the expected resulting types. --- .../scala/net/liftweb/common/BoxSpec.scala | 145 ------- .../net/liftweb/common/BoxTypingSpec.scala | 363 ++++++++++++++++++ 2 files changed, 363 insertions(+), 145 deletions(-) create mode 100644 core/common/src/test/scala/net/liftweb/common/BoxTypingSpec.scala diff --git a/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala b/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala index 11fa18573b..d2a53dc120 100644 --- a/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala +++ b/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala @@ -408,151 +408,6 @@ class BoxSpec extends Specification with ScalaCheck with BoxGenerator { Failure("", Empty, Empty) must be_!=("hello") } } - - "A List[Box[T]]" should { - "be convertable to a Box[List[T]] when all are Full" in { - val someBoxes: List[Box[String]] = List(Full("bacon"), Full("sammich")) - val singleBox = someBoxes.toSingleBox("Box failed!") - - singleBox must_== Full(List("bacon", "sammich")) - } - - "be convertable to a Box[List[T]] when some are Full and some are Empty" in { - val someBoxes: List[Box[String]] = List(Full("bacon"), Full("sammich"), Empty) - val singleBox = someBoxes.toSingleBox("Box failed!") - - singleBox must_== Full(List("bacon", "sammich")) - } - - "be convertable to a ParamFailure[Box[List[T]]] when any are Failure" in { - val someBoxes: List[Box[String]] = List(Full("bacon"), Full("sammich"), Failure("I HATE BACON")) - val singleBox = someBoxes.toSingleBox("This should be in the param failure.") - - singleBox must beLike { - case ParamFailure(message, _, _, _) => - message must_== "This should be in the param failure." - } - } - - "chain the ParamFailure to the failures in the list when any are Failure" in { - val someBoxes: List[Box[String]] = List(Full("bacon"), Failure("I HATE BACON"), Full("sammich"), Failure("MORE BACON FAIL"), Failure("BACON WHY U BACON")) - - val singleBox = someBoxes.toSingleBox("Failure.") - - val expectedChain = - Failure("I HATE BACON", Empty, - Full(Failure("MORE BACON FAIL", Empty, - Full(Failure("BACON WHY U BACON"))))) - - singleBox must beLike { - case ParamFailure(_, _, chain, _) => - chain must_== Full(expectedChain) - } - } - } - - def typingTest = { - val myBox: PresenceBox[String] = Full("hai") - val otherBox: TryBox[String] = Full("bai") - val thirdBox: ParamTryBox[String, Int] = Full("sai") - myBox match { - case Full(hai) => - val thingie: String = hai - case Empty => - val thingie = "" - /*case Failure("hai", _, _) => - println("Fail!")*/ - } - otherBox match { - case Full(hai) => - val thingie: String = hai - /*case Empty => - val thingie = ""*/ - case Failure(hai, _, _) => - println("Fail!" + hai) - } - thirdBox match { - case Full(hai) => - val thingie: String = hai - /*case Failure(hai, _, _) => - println("shai")*/ - /*case Empty => - println("boo")*/ - // FIXME We want the next two flipped, but the compiler won't properly - // FIXME resolve the overloaded unapplies :( - /*case ParamFailure(_, _, _, zem) => - val myThingie: Int = zem*/ - case ParamFailure(_, _, _, zem: String) => - println("bam") - case parammable: ParamFailure[Int] => - val myThingie: Int = parammable.param - } - - val myFailureBox: PresenceBox[String] = Full("bai").filter(_ != "boom") - val otherFailureBox: TryBox[String] = Full("bai").filter(_ != "boom") ?~ "Boom" - val otherOtherFailureBox: TryBox[String] = Full("bai").filter(_ != "boom") ?~ "Boom" ?~! "Asplode" - val thirdFailureBox: ParamTryBox[String, Int] = Full("sai") ?~ "yes" ~> 5 - - val seriousBoxes = - Seq(thirdFailureBox, thirdFailureBox).map { - case intParamFailure: ParamFailure[Int] => - intParamFailure - case a @ Full(stringyThing) => - a - } - - val newThing: Seq[ParamTryBox[String, Int]] = seriousBoxes - - val stringParamFailure = Failure("slap") ~> "boom" - - val orredPresence: PresenceBox[String] = myBox or Empty - val orredFailure: TryBox[String] = myBox or Failure("boom") - val orredParamFailure: ParamTryBox[String, Int] = myBox or thirdBox - val orredParamFailure2: TryBox[String] = myBox or thirdBox - - val flattened: PresenceBox[String] = - myBox.flatMap { a => - Empty - } - val tryFlattened: TryBox[String] = // should fail - otherBox.flatMap { a => - Failure("boom") - } - val paramTryFlattened: TryBox[String] = - thirdBox.flatMap { a => - Failure("boom") ~> "slam" - } - val paramTryQuiteFlattened: ParamTryBox[String, Int] = - thirdBox.flatMap { a => - Failure("boom") ~> 5 - } - val paramTryQuiteFlattened2: ParamTryBox[String, AnyVal] = - thirdBox.flatMap { a => - Failure("boom") ~> 2.3 - } - - // flatMap - // PresenceBox[T] - // flatMap PresenceBox[A] => PresenceBox[A] - // flatMap TryBox[A] => Full flatMap TryBox[A] => TryBox[A] --/-> Box[A] - // => Empty flatMap TryBox[A] => Empty ----/ - // flatMap ParamTryBox[A,E] => Full flatMap PTB[A,E] => PTB[A,E] --/-> Box[A] - // => Empty flatMap PTB[A,E] => Empty ---/ - // TryBox[T] - // flatMap PresenceBox[A] => Full flatMap PresenceBox[A] => PresenceBox[A] --/-> Box[A] - // => Failure flatMap PresenceBox[A] => Failure -----/ - // flatMap TryBox[A] => TryBox[A] - // flatMap ParamTryBox[A,E] => Full flatMap PTB[A,E] => PTB[A,E] -----/-> TryBox[A] - // => Failure flatMap PTB[A,E] => Failure --/ - // ParamTryBox[T,E] - // flatMap PresenceBox[A] => Full flatMap PresenceBox[A] => PresenceBox[A] ------------/-> Box[A] - // => ParamFailure[A,E] flatMap PresenceBox[A] => PF[A,E] -----/ - // flatMap TryBox[A] => TryBox[A] - // flatMap ParamTryBox[A,E] => Full flatMap PTB[A,E] => PTB[A,E] -------/-> PTB[A,E] - // => PTB[A,E] flatMap PTB[A,E] => PTB[A,E] --/ - // flatMap ParamTryBox[A,Z] => Full flatMap PTB[A,Z] => PTB[A,Z] -------/-> TryBox[A] - // => PTB[A,E] flatMap PTB[A,Z] => PTB[A,Z] --/ - } } diff --git a/core/common/src/test/scala/net/liftweb/common/BoxTypingSpec.scala b/core/common/src/test/scala/net/liftweb/common/BoxTypingSpec.scala new file mode 100644 index 0000000000..48ccfff9a8 --- /dev/null +++ b/core/common/src/test/scala/net/liftweb/common/BoxTypingSpec.scala @@ -0,0 +1,363 @@ +/* + * Copyright 2007-2011 WorldWide Conferencing, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.liftweb +package common + +import org.specs2.mutable.Specification + +/** + * Verifications for complex Box typing like PresenceBox flatMap T=>PresenceBox + * yields PresenceBox, etc. + */ +class BoxTypingSpec extends Specification { + // IMPORTANT + // We use a few implicits to help us do compile-time proofs of the inferred + // compiler types, but if you choose to edit this spec make sure you don't + // lose typing information during the checks! e.g., if we want to verify that + // fullBox.operation(...) is only a TryBox and nothing else, and we put that + // operation result in a List with other operation results, we're no longer + // checking that operation's result, but rather the supertype of all the + // results of the operations! + // + // As a result, you'll see a bit more repetition below than may seem ideal. + + // Implicit helpers to ask Scala to help us prove the compiler inferred the + // right type for method returns. + trait PlainBoxProof { def plainBox_?(): Boolean } + implicit class DisprovePlainBox[T](notSpecificBox: Box[T]) extends PlainBoxProof { + def plainBox_? = true + } + trait FullProof { def full_?(): Boolean } + implicit class ProveFull[T](full: Full[T]) extends FullProof with PlainBoxProof { + def full_? = true + def plainBox_? = false + } + implicit class DisproveFull[T](notFull: Box[T]) extends FullProof { + def full_? = false + } + trait EmptyProof { def empty_?(): Boolean } + implicit class ProveEmpty(empty: Empty.type) extends EmptyProof with PlainBoxProof { + def empty_? = true + def plainBox_? = false + } + implicit class DisproveEmpty[T](notEmpty: Box[T]) extends EmptyProof { + def empty_? = false + } + trait FailureProof { def failure_?(): Boolean } + implicit class ProveFailure(failure: Failure) extends FailureProof with PlainBoxProof { + def failure_? = true + def plainBox_? = false + } + implicit class DisproveFailure[T](notFailure: Box[T]) extends FailureProof { + def failure_? = false + } + trait ParamFailureProof { def paramFailure_?(): Boolean } + implicit class ProveParamFailure[A](paramFailure: ParamFailure[A]) extends ParamFailureProof with PlainBoxProof { + def paramFailure_? = true + def plainBox_? = false + } + implicit class DisproveParamFailure[T](notFailure: Box[T]) extends ParamFailureProof { + def paramFailure_? = false + } + trait TryProof { def try_?(): Boolean } + implicit class ProveTry[T](`try`: TryBox[T]) extends TryProof with PlainBoxProof { + def try_? = true + def plainBox_? = false + } + implicit class DisproveTry[T](notTry: Box[T]) extends TryProof { + def try_? = false + } + trait ParamTryProof { def paramTry_?(): Boolean } + implicit class ProveParamTry[T](`try`: ParamTryBox[T, Double]) extends ParamTryProof with PlainBoxProof { + def paramTry_? = true + def plainBox_? = false + } + implicit class DisproveParamTry[T](notParamTry: Box[T]) extends ParamTryProof { + def paramTry_? = false + } + trait PresenceProof { def presence_?(): Boolean } + implicit class ProvePresence[T](presence: PresenceBox[T]) extends PresenceProof with PlainBoxProof { + def presence_? = true + def plainBox_? = false + } + implicit class DisprovePresence[T](notPresence: Box[T]) extends PresenceProof { + def presence_? = false + } + + "Box when doing complex typing invocations" should { + val fullBox: Full[Int] = Full(5) + val fullParamTry: ParamTryBox[Int, Double] = fullBox + val fullTry: TryBox[Int] = fullBox + val fullPresence: PresenceBox[Int] = fullBox + val fullPlain: Box[Int] = fullBox + + val emptyPresence: PresenceBox[Int] = Empty + val emptyPlain: Box[Int] = Empty + + val failureBox: Failure = Failure("boom") + val failureTry: TryBox[Int] = failureBox + val failurePlain: Box[Int] = failureBox + + val paramFailureBox: ParamFailure[Double] = failureBox ~> 2.0 + val paramFailureParamTry: ParamTryBox[Int, Double] = paramFailureBox + val paramFailureTry: TryBox[Int] = paramFailureBox + val paramFailurePlain: Box[Int] = paramFailureBox + + val fullStringBox: Full[String] = Full("boom") + val fullStringParamTry: ParamTryBox[String, Double] = fullStringBox + val fullStringTry: TryBox[String] = fullStringBox + val fullStringPresence: PresenceBox[String] = fullStringBox + val fullStringPlain: Box[String] = fullStringBox + + val emptyStringPresence: PresenceBox[String] = Empty + val emptyStringPlain: Box[String] = Empty + + val failureStringTry: TryBox[String] = failureBox + val failureStringPlain: Box[String] = failureBox + + val paramFailureStringParamTry: ParamTryBox[String, Double] = paramFailureBox + val paramFailureStringTry: TryBox[String] = paramFailureBox + val paramFailureStringPlain: Box[String] = paramFailureBox + + "flatMap outer Full correctly" in { + fullBox.flatMap(_ => fullStringBox).try_? must beTrue + fullBox.flatMap(_ => fullStringBox).paramTry_? must beTrue + fullBox.flatMap(_ => fullStringBox).presence_? must beTrue + + fullParamTry.flatMap(_ => fullStringBox).try_? must beTrue + fullParamTry.flatMap(_ => fullStringBox).paramTry_? must beTrue + fullParamTry.flatMap(_ => fullStringBox).presence_? must beFalse + + fullTry.flatMap(_ => fullStringBox).try_? must beTrue + fullTry.flatMap(_ => fullStringBox).paramTry_? must beFalse + fullTry.flatMap(_ => fullStringBox).presence_? must beFalse + + fullPresence.flatMap(_ => fullStringBox).try_? must beFalse + fullPresence.flatMap(_ => fullStringBox).paramTry_? must beFalse + fullPresence.flatMap(_ => fullStringBox).presence_? must beTrue + + fullPlain.flatMap(_ => fullStringBox).try_? must beFalse + fullPlain.flatMap(_ => fullStringBox).paramTry_? must beFalse + fullPlain.flatMap(_ => fullStringBox).presence_? must beFalse + + fullBox.flatMap(_ => fullStringBox) must_== fullStringBox + fullParamTry.flatMap(_ => fullStringBox) must_== fullStringBox + fullTry.flatMap(_ => fullStringBox) must_== fullStringBox + fullPresence.flatMap(_ => fullStringBox) must_== fullStringBox + fullPlain.flatMap(_ => fullStringBox) must_== fullStringBox + } + + "flatMap outer Empty correctly" in { + Empty.flatMap(_ => fullStringBox).try_? must beFalse + Empty.flatMap(_ => fullStringBox).paramTry_? must beFalse + Empty.flatMap(_ => fullStringBox).presence_? must beTrue + + emptyPresence.flatMap(_ => fullStringBox).try_? must beFalse + emptyPresence.flatMap(_ => fullStringBox).paramTry_? must beFalse + emptyPresence.flatMap(_ => fullStringBox).presence_? must beTrue + + emptyPlain.flatMap(_ => fullStringBox).try_? must beFalse + emptyPlain.flatMap(_ => fullStringBox).paramTry_? must beFalse + emptyPlain.flatMap(_ => fullStringBox).presence_? must beFalse + + Empty.flatMap(_ => fullStringBox) must_== Empty + emptyPresence.flatMap(_ => fullStringBox) must_== Empty + emptyPlain.flatMap(_ => fullStringBox) must_== Empty + } + + "flatMap outer Failure correctly" in { + failureBox.flatMap(_ => fullStringBox).try_? must beTrue + failureBox.flatMap(_ => fullStringBox).paramTry_? must beFalse + failureBox.flatMap(_ => fullStringBox).presence_? must beFalse + + failureTry.flatMap(_ => fullStringBox).try_? must beTrue + failureTry.flatMap(_ => fullStringBox).paramTry_? must beFalse + failureTry.flatMap(_ => fullStringBox).presence_? must beFalse + + failurePlain.flatMap(_ => fullStringBox).try_? must beFalse + failurePlain.flatMap(_ => fullStringBox).paramTry_? must beFalse + failurePlain.flatMap(_ => fullStringBox).presence_? must beFalse + + failureBox.flatMap(_ => fullStringBox) must_== failureBox + failureTry.flatMap(_ => fullStringBox) must_== failureBox + failurePlain.flatMap(_ => fullStringBox) must_== failureBox + } + + "flatMap outer ParamFailure correctly" in { + paramFailureBox.flatMap(_ => fullStringBox).try_? must beTrue + paramFailureBox.flatMap(_ => fullStringBox).paramTry_? must beTrue + paramFailureBox.flatMap(_ => fullStringBox).presence_? must beFalse + + paramFailureParamTry.flatMap(_ => fullStringBox).try_? must beTrue + paramFailureParamTry.flatMap(_ => fullStringBox).paramTry_? must beTrue + paramFailureParamTry.flatMap(_ => fullStringBox).presence_? must beFalse + + paramFailureTry.flatMap(_ => fullStringBox).try_? must beTrue + paramFailureTry.flatMap(_ => fullStringBox).paramTry_? must beFalse + paramFailureTry.flatMap(_ => fullStringBox).presence_? must beFalse + + paramFailurePlain.flatMap(_ => fullStringBox).try_? must beFalse + paramFailurePlain.flatMap(_ => fullStringBox).paramTry_? must beFalse + paramFailurePlain.flatMap(_ => fullStringBox).presence_? must beFalse + + paramFailureBox.flatMap(_ => fullStringBox) must_== paramFailureBox + paramFailureParamTry.flatMap(_ => fullStringBox) must_== paramFailureBox + paramFailureTry.flatMap(_ => fullStringBox) must_== paramFailureBox + paramFailurePlain.flatMap(_ => fullStringBox) must_== paramFailureBox + } + + "filter Empty correctly" in { + emptyPresence.filter(_ == 5).presence_? must beTrue + emptyPresence.filter(_ == 5).paramTry_? must beFalse + emptyPresence.filter(_ == 5).try_? must beFalse + + emptyPlain.filter(_ == 5).presence_? must beFalse + emptyPlain.filter(_ == 5).paramTry_? must beFalse + emptyPlain.filter(_ == 5).try_? must beFalse + } + + "filter Failure correctly" in { + failureBox.filter(_ == 5).presence_? must beFalse + failureBox.filter(_ == 5).paramTry_? must beFalse + failureBox.filter(_ == 5).try_? must beTrue + } + + "filter ParamFailure correctly" in { + paramFailureBox.filter(_ == 5).presence_? must beFalse + paramFailureBox.filter(_ == 5).paramTry_? must beTrue + paramFailureBox.filter(_ == 5).try_? must beTrue + } + + "when filtering" in { + // For a Full/Empty, filter leads to PresenceBox, otherwise it's just Box. + "filter a known Full to PresenceBox" in { + fullBox.filter(_ == 7).presence_? must beTrue + fullBox.filter(_ == 7).paramTry_? must beFalse + fullBox.filter(_ == 7).try_? must beFalse + } + + "filter a PresenceBox to PresenceBox" in { + fullPresence.filter(_ == 7).presence_? must beTrue + fullPresence.filter(_ == 7).paramTry_? must beFalse + fullPresence.filter(_ == 7).try_? must beFalse + + emptyPresence.filter(_ == 7).presence_? must beTrue + emptyPresence.filter(_ == 7).paramTry_? must beFalse + emptyPresence.filter(_ == 7).try_? must beFalse + } + + // In order for Full to be able to return a PresenceBox for filter, the + // other types (TryBox, etc) need to return Box (since Full extends those + // as well). However, if we know we are looking at a Failure or + // ParamFailure, we can be specific. + "filter known Failures/ParamFailures to themselves" in { + paramFailureBox.filter(_ == 7).presence_? must beFalse + paramFailureBox.filter(_ == 7).paramTry_? must beTrue + paramFailureBox.filter(_ == 7).try_? must beTrue + + failureBox.filter(_ == 7).presence_? must beFalse + failureBox.filter(_ == 7).paramTry_? must beFalse + failureBox.filter(_ == 7).try_? must beTrue + } + + "filter all other types to Box" in { + fullParamTry.filter(_ == 7).plainBox_? must beTrue + fullTry.filter(_ == 7).plainBox_? must beTrue + fullPlain.filter(_ == 7).plainBox_? must beTrue + + emptyPlain.filter(_ == 7).plainBox_? must beTrue + + paramFailurePlain.filter(_ == 7).plainBox_? must beTrue + paramFailureParamTry.filter(_ == 7).plainBox_? + paramFailureTry.filter(_ == 7).plainBox_? + + failurePlain.filter(_ == 7).plainBox_? must beTrue + failureTry.filter(_ == 7).plainBox_? must beTrue + } + + "filter correctly regardless of type" in { + List(fullBox, fullParamTry, fullTry, fullPresence, fullPlain) + .map(_.filter(_ == 7)) + .forall(_ == Empty) must beTrue + + List(fullBox, fullParamTry, fullTry, fullPresence, fullPlain) + .map(_.filter(_ == 5)) + .exists(_ == Empty) must beFalse + } + } + + "when or-ing" should { + "or known Fulls with all other types preserving the Fullness" in { + (fullBox or failureBox).full_? must beTrue + (fullBox or failureTry).full_? must beTrue + (fullBox or paramFailureBox).full_? must beTrue + (fullBox or paramFailureParamTry).full_? must beTrue + (fullBox or emptyPresence).full_? must beTrue + (fullBox or emptyPlain).full_? must beTrue + } + + "or Empties with other types preserving the supertypes" in { + (Empty or fullBox).full_? must beTrue + (Empty or paramFailureParamTry).paramTry_? must beTrue + (Empty or failureBox).failure_? must beFalse + (Empty or failureBox).try_? must beTrue + (Empty or failureTry).try_? must beTrue + (Empty or paramFailureBox).paramFailure_? must beFalse + (Empty or paramFailureBox).paramTry_? must beTrue + } + + "or Failures with other types preserving the supertypes" in { + (failureBox or fullBox).full_? must beTrue + (failureBox or paramFailureParamTry).paramTry_? must beTrue + (failureBox or failureBox).failure_? must beFalse + (failureBox or failureBox).try_? must beTrue + (failureBox or failureTry).try_? must beTrue + (failureBox or paramFailureBox).paramFailure_? must beFalse + (failureBox or paramFailureBox).paramTry_? must beTrue + } + + "or ParamFailures with other types preserving the supertypes" in { + (paramFailureBox or fullBox).full_? must beTrue + (paramFailureBox or paramFailureParamTry).paramTry_? must beTrue + (paramFailureBox or failureBox).failure_? must beFalse + (paramFailureBox or failureBox).try_? must beTrue + (paramFailureBox or failureTry).try_? must beTrue + (paramFailureBox or paramFailureBox).paramFailure_? must beFalse + (paramFailureBox or paramFailureBox).paramTry_? must beTrue + } + + "or plain Box with other types to Box" in { + (emptyPlain or fullBox).plainBox_? must beTrue + (emptyPlain or Empty).plainBox_? must beTrue + (emptyPlain or failureBox).plainBox_? must beTrue + (emptyPlain or paramFailureBox).plainBox_? must beTrue + } + } + + "when isA/asAing" should { + // Full [ai]sA -> PresenceBox + // Empty [ai]sA -> PresenceBox + // Failure/ParamFailure [ai]sA -> Failure + // Box [ai]sA -> Box + 0 must_== 0 + } + + // collect/collectFirst go PresenceBox for Full/Empty, Box for everything else + // flattenPresence/flattenTry + // for comprehensions + } +} From 12012bd5fd0af01c4f9819a8f3274e97a842fa57 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 17 Sep 2017 09:16:32 -0400 Subject: [PATCH 11/16] Allow more specific flatMap return types for Box subtypes. --- core/common/src/main/scala/net/liftweb/common/Box.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index fe04324d29..79ecfe360d 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -891,6 +891,7 @@ sealed trait ParamTryBox[+A, +E] extends Box[A] with TryBox[A] { override def map[B](f: A => B): ParamTryBox[B, E] = ??? def flatMap[B, E2 >: E](f: A => ParamTryBox[B, E2]): ParamTryBox[B, E2] + def flatMap[B](f: A => Full[B])(implicit a: DummyImplicit): ParamTryBox[B, E] } /** @@ -926,11 +927,12 @@ final case class Full[+A](value: A) extends Box[A] override def flatMap[B](f: A => PresenceBox[B]): PresenceBox[B] = f(value) override def flatMap[B](f: A => TryBox[B]): TryBox[B] = f(value) override def flatMap[B, E2 >: Nothing](f: A => ParamTryBox[B, E2]): ParamTryBox[B, E2] = f(value) + override def flatMap[B](f: A => Full[B])(implicit a: DummyImplicit): Full[B] = f(value) // Redefine these two with the more specific type information we have at this // point. override def collect[B](pf: PartialFunction[A, B]): PresenceBox[B] = { - flatMap { value => + flatMap { value: A => if (pf.isDefinedAt(value)) { Full(pf(value)) } else { @@ -1204,6 +1206,7 @@ final class ParamFailure[T](override val msg: String, override def map[B](f: A => B): ParamFailure[T] = this override def flatMap[B, E2 >: T](f: A => ParamTryBox[B, E2]): ParamFailure[T] = this + override def flatMap[B](f: Nothing => Full[B])(implicit a: DummyImplicit): ParamFailure[T] = this override def filter(p: Nothing => Boolean): ParamFailure[T] = this } From 69bfa97c76ce169a3cb4081290d997fec191f931 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 24 Sep 2017 22:57:19 -0400 Subject: [PATCH 12/16] Fix Full flatMap for ParamTryBox. The existing definition wasn't actually catching for all ParamTryBoxes. --- core/common/src/main/scala/net/liftweb/common/Box.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index 79ecfe360d..7dffd81a7b 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -926,7 +926,7 @@ final case class Full[+A](value: A) extends Box[A] override def flatMap[B](f: A => Box[B]): Box[B] = f(value) override def flatMap[B](f: A => PresenceBox[B]): PresenceBox[B] = f(value) override def flatMap[B](f: A => TryBox[B]): TryBox[B] = f(value) - override def flatMap[B, E2 >: Nothing](f: A => ParamTryBox[B, E2]): ParamTryBox[B, E2] = f(value) + override def flatMap[B, E2](f: A => ParamTryBox[B, E2]): ParamTryBox[B, E2] = f(value) override def flatMap[B](f: A => Full[B])(implicit a: DummyImplicit): Full[B] = f(value) // Redefine these two with the more specific type information we have at this From 86f35038605a9764df81163fd947f466e17a9cd4 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 24 Sep 2017 22:58:01 -0400 Subject: [PATCH 13/16] Fix asA to return Failure for Failure. --- core/common/src/main/scala/net/liftweb/common/Box.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index 7dffd81a7b..a52dd86444 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -1062,7 +1062,7 @@ sealed case class Failure(msg: String, exception: Box[Throwable], chain: Box[Fai override def isA[B](cls: Class[B]): Failure = this - override def asA[B](implicit m: Manifest[B]): Box[B] = this + override def asA[B](implicit m: Manifest[B]): Failure = this private def chainList: List[Failure] = chain match { case Full(f) => f :: f.chainList From 8679ca20ff5a0d60aa93a20574fa7019ad158743 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 24 Sep 2017 22:55:04 -0400 Subject: [PATCH 14/16] Fill in remaining typing specs. --- .../net/liftweb/common/BoxTypingSpec.scala | 112 ++++++++++++++++-- 1 file changed, 104 insertions(+), 8 deletions(-) diff --git a/core/common/src/test/scala/net/liftweb/common/BoxTypingSpec.scala b/core/common/src/test/scala/net/liftweb/common/BoxTypingSpec.scala index 48ccfff9a8..9074b10895 100644 --- a/core/common/src/test/scala/net/liftweb/common/BoxTypingSpec.scala +++ b/core/common/src/test/scala/net/liftweb/common/BoxTypingSpec.scala @@ -349,15 +349,111 @@ class BoxTypingSpec extends Specification { } "when isA/asAing" should { - // Full [ai]sA -> PresenceBox - // Empty [ai]sA -> PresenceBox - // Failure/ParamFailure [ai]sA -> Failure - // Box [ai]sA -> Box - 0 must_== 0 + "preserve PresenceBoxiness for Full, Empty, and PresenceBox" in { + fullBox.asA[String].presence_? must beTrue + fullBox.isA(classOf[String]).presence_? must beTrue + fullBox.asA[Int].presence_? must beTrue + fullBox.isA(classOf[Int]).presence_? must beTrue + + Empty.asA[String].presence_? must beTrue + Empty.isA(classOf[String]).presence_? must beTrue + Empty.asA[Int].presence_? must beTrue + Empty.isA(classOf[Int]).presence_? must beTrue + + fullPresence.asA[String].presence_? must beTrue + fullPresence.isA(classOf[String]).presence_? must beTrue + fullPresence.asA[Int].presence_? must beTrue + fullPresence.isA(classOf[Int]).presence_? must beTrue + + emptyPresence.asA[String].presence_? must beTrue + emptyPresence.isA(classOf[String]).presence_? must beTrue + emptyPresence.asA[Int].presence_? must beTrue + emptyPresence.isA(classOf[Int]).presence_? must beTrue + } + + "preserve Failureness for Failure and ParamFailure" in { + failureBox.asA[String].failure_? must beTrue + failureBox.isA(classOf[String]).failure_? must beTrue + paramFailureBox.asA[String].failure_? must beTrue + paramFailureBox.isA(classOf[String]).failure_? must beTrue + + failureBox.asA[String].presence_? must beFalse + failureBox.isA(classOf[String]).presence_? must beFalse + paramFailureBox.asA[String].presence_? must beFalse + paramFailureBox.isA(classOf[String]).presence_? must beFalse + } + + "preserve Boxiness for Box" in { + fullPlain.asA[String].plainBox_? must beTrue + fullPlain.isA(classOf[String]).plainBox_? must beTrue + emptyPlain.asA[String].plainBox_? must beTrue + emptyPlain.isA(classOf[String]).plainBox_? must beTrue + failurePlain.asA[String].plainBox_? must beTrue + failurePlain.isA(classOf[String]).plainBox_? must beTrue + paramFailurePlain.asA[String].plainBox_? must beTrue + paramFailurePlain.isA(classOf[String]).plainBox_? must beTrue + } + } + + "when collecting and collectFirsting" should { + "preserve PresenceBoxiness for Full, Empty, and PresenceBox" in { + fullBox.collect { case _ => "a" }.presence_? must beTrue + Empty.collect { case _ => "a" }.presence_? must beTrue + + fullPresence.collect { case _ => "a" }.presence_? must beTrue + emptyPresence.collect { case _ => "a" }.presence_? must beTrue + } + + "lose Failureness for Failure and ParamFailure" in { + failureBox.collect { case _ => "a" }.failure_? must beFalse + paramFailureBox.collect { case _ => "a" }.failure_? must beFalse + + failureBox.collect { case _ => "a" }.presence_? must beFalse + paramFailureBox.collect { case _ => "a" }.presence_? must beFalse + + failureBox.collect { case _ => "a" }.plainBox_? must beTrue + paramFailureBox.collect { case _ => "a" }.plainBox_? must beTrue + } + + "preserve Boxiness for Box" in { + fullPlain.collect { case _ => "a" }.plainBox_? must beTrue + emptyPlain.collect { case _ => "a" }.plainBox_? must beTrue + failurePlain.collect { case _ => "a" }.plainBox_? must beTrue + paramFailurePlain.collect { case _ => "a" }.plainBox_? must beTrue + } } - // collect/collectFirst go PresenceBox for Full/Empty, Box for everything else - // flattenPresence/flattenTry - // for comprehensions + "when used in for comprehensions" should { + "preserve closest common type for mapping for comprehensions" in { + (for (a <- fullBox ; b <- failureBox ; c <- fullBox) yield "a").try_? must beTrue + (for (a <- fullBox ; b <- failureTry ; c <- fullBox) yield "a").try_? must beTrue + (for (a <- fullBox ; b <- failurePlain ; c <- fullBox) yield "a").plainBox_? must beTrue + + (for (a <- fullBox ; b <- Empty ; c <- fullBox) yield "a").presence_? must beTrue + (for (a <- fullBox ; b <- emptyPresence ; c <- fullBox) yield "a").presence_? must beTrue + (for (a <- fullBox ; b <- emptyPlain ; c <- fullBox) yield "a").plainBox_? must beTrue + + (for (a <- fullBox ; b <- paramFailureBox ; c <- fullBox) yield "a").paramTry_? must beTrue + (for (a <- fullBox ; b <- paramFailureParamTry ; c <- fullBox) yield "a").paramTry_? must beTrue + (for (a <- fullBox ; b <- paramFailureTry ; c <- fullBox) yield "a").paramTry_? must beFalse + (for (a <- fullBox ; b <- paramFailureTry ; c <- fullBox) yield "a").try_? must beTrue + (for (a <- fullBox ; b <- paramFailurePlain ; c <- fullBox) yield "a").plainBox_? must beTrue + } + + "preserve closest common type to PresenceBox for filtering for comprehensions" in { + (for (a <- fullBox ; b <- failureBox ; c <- fullBox if c == 5) yield "a").plainBox_? must beTrue + (for (a <- fullBox ; b <- failureTry ; c <- fullBox if c == 5) yield "a").plainBox_? must beTrue + (for (a <- fullBox ; b <- failurePlain ; c <- fullBox if c == 5) yield "a").plainBox_? must beTrue + + (for (a <- fullBox ; b <- Empty ; c <- fullBox if c == 5) yield "a").presence_? must beTrue + (for (a <- fullBox ; b <- emptyPresence ; c <- fullBox if c == 5) yield "a").presence_? must beTrue + (for (a <- fullBox ; b <- emptyPlain ; c <- fullBox if c == 5) yield "a").plainBox_? must beTrue + + (for (a <- fullBox ; b <- paramFailureBox ; c <- fullBox if c == 5) yield "a").plainBox_? must beTrue + (for (a <- fullBox ; b <- paramFailureParamTry ; c <- fullBox if c == 5) yield "a").plainBox_? must beTrue + (for (a <- fullBox ; b <- paramFailureTry ; c <- fullBox if c == 5) yield "a").plainBox_? must beTrue + (for (a <- fullBox ; b <- paramFailurePlain ; c <- fullBox if c == 5) yield "a").plainBox_? must beTrue + } + } } } From 28d5d570ab9fbddd6d817947382879293dfe26ef Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 24 Sep 2017 22:55:54 -0400 Subject: [PATCH 15/16] Move flattenPresence/flattenTry specs into their own examples. Also add some scaladocs to those two methods. --- .../main/scala/net/liftweb/common/Box.scala | 12 ++++++++++++ .../scala/net/liftweb/common/BoxSpec.scala | 18 +++++++++++++++--- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index a52dd86444..dc3314d1d5 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -843,6 +843,12 @@ sealed trait PresenceBox[+A] extends Box[A] { def flatMap[B](f: A => PresenceBox[B]): PresenceBox[B] override def collect[B](pf: PartialFunction[A, B]): PresenceBox[B] = ??? + /** + * Variant of `flatten` that will only work on a `PresenceBox`, and returns a + * `PresenceBox`. `flatten` is not straightforward to specialize due to `Full` + * participating in both `TryBox` and `PresenceBox`, so we introduce this and + * `[[flattenTry TryBox.flattenTry]]` as a workaround. + */ def flattenPresence[B](implicit ev: A <:< PresenceBox[B]): PresenceBox[B] = this match { case Full(internal) => ev(internal) case Empty => Empty @@ -874,6 +880,12 @@ sealed trait TryBox[+A] extends Box[A] { override def map[B](f: A => B): TryBox[B] = ??? def flatMap[B](f: A => TryBox[B]): TryBox[B] + /** + * Variant of `flatten` that will only work on a `TryBox`, and returns a + * `PresenceBox`. `flatten` is not straightforward to specialize due to `Full` + * participating in both `TryBox` and `PresenceBox`, so we introduce this and + * `[[flattenPresence PresenceBox.flattenPresence]]` as a workaround. + */ def flattenTry[B](implicit ev: A <:< TryBox[B]): TryBox[B] = this match { case Full(internal) => ev(internal) case f: Failure => f diff --git a/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala b/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala index d2a53dc120..0671b3c6f1 100644 --- a/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala +++ b/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala @@ -144,15 +144,27 @@ class BoxSpec extends Specification with ScalaCheck with BoxGenerator { "define a 'flatten' method if it contains another Box." in { "If the inner box is a Full box, the final result is identical to that box" in { Full(Full(1)).flatten must_== Full(1) - (Full(Full(1)).flattenTry: TryBox[Int]) must_== Full(1) - (Full(Full(1)).flattenPresence: PresenceBox[Int]) must_== Full(1) } "If the inner box is a Failure, the final result is identical to that box" in { Full(Failure("error", Empty, Empty)).flatten must_== Failure("error", Empty, Empty) - (Full(Failure("error", Empty, Empty)).flattenTry: TryBox[Int]) must_== Failure("error", Empty, Empty) } "If the inner box is an Empty box, the final result is identical to that box" in { Full(Empty).flatten must_== Empty + } + } + "define a 'flattenPresence' method if it contains another PresenceBox" in { + "If the inner box is a Full box, the final result is identical to that box" in { + (Full(Full(1)).flattenPresence: PresenceBox[Int]) must_== Full(1) + } + "If the inner box is an Empty box, the final result is identical to that box" in { + (Full(Empty).flattenPresence: PresenceBox[Int]) must_== Empty + } + } + "define a 'flattenTry' method if it contains another TryBox" in { + "If the inner box is a Full box, the final result is identical to that box" in { + (Full(Full(1)).flattenTry: TryBox[Int]) must_== Full(1) + } + "If the inner box is a Failure, the final result is identical to that box" in { (Full(Empty).flattenPresence: PresenceBox[Int]) must_== Empty } } From 4b8fb17669694f538313672283e5c209c6ee6b75 Mon Sep 17 00:00:00 2001 From: Antonio Salazar Cardozo Date: Sun, 24 Sep 2017 22:58:21 -0400 Subject: [PATCH 16/16] Drop ??? definitions of collect and map. We do this by dropping the concrete definitions of these two methods in Box, preferring instead to lean 100% on late binding to resolve the correct concrete definition of the method based on the concrete type it is being invoked on. --- .../main/scala/net/liftweb/common/Box.scala | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/core/common/src/main/scala/net/liftweb/common/Box.scala b/core/common/src/main/scala/net/liftweb/common/Box.scala index dc3314d1d5..f477f961f5 100644 --- a/core/common/src/main/scala/net/liftweb/common/Box.scala +++ b/core/common/src/main/scala/net/liftweb/common/Box.scala @@ -474,7 +474,7 @@ sealed abstract class Box[+A] extends Product with Serializable { * @note This means that using `map` with a `Failure` will preserve the * `Failure.` */ - def map[B](f: A => B): Box[B] = Empty + def map[B](f: A => B): Box[B] /** * Apply a function returning a `Box` to the value contained in this `Box` if @@ -806,15 +806,7 @@ sealed abstract class Box[+A] extends Product with Serializable { * If the partial function is defined at the current Box's value, apply the * partial function. */ - def collect[B](pf: PartialFunction[A, B]): Box[B] = { - flatMap { value => - if (pf.isDefinedAt(value)) { - Full(pf(value)) - } else { - Empty - } - } - } + def collect[B](pf: PartialFunction[A, B]): Box[B] /** * An alias for `collect`. @@ -841,7 +833,7 @@ sealed trait PresenceBox[+A] extends Box[A] { override def map[B](f: A => B): PresenceBox[B] = Empty def flatMap[B](f: A => PresenceBox[B]): PresenceBox[B] - override def collect[B](pf: PartialFunction[A, B]): PresenceBox[B] = ??? + override def collect[B](pf: PartialFunction[A, B]): PresenceBox[B] /** * Variant of `flatten` that will only work on a `PresenceBox`, and returns a @@ -877,7 +869,7 @@ sealed trait TryBox[+A] extends Box[A] { def or[B >: A](alternative: => TryBox[B])(implicit a: DummyImplicit, b: DummyImplicit, c: DummyImplicit): TryBox[B] def or[B >: A, E](alternative: => ParamTryBox[B, E])(implicit a: DummyImplicit, b: DummyImplicit, c: DummyImplicit, d: DummyImplicit): ParamTryBox[B, E] - override def map[B](f: A => B): TryBox[B] = ??? + override def map[B](f: A => B): TryBox[B] def flatMap[B](f: A => TryBox[B]): TryBox[B] /** @@ -901,7 +893,7 @@ sealed trait ParamTryBox[+A, +E] extends Box[A] with TryBox[A] { def or[B >: A](alternative: => TryBox[B])(implicit a: DummyImplicit, b: DummyImplicit, c: DummyImplicit): TryBox[B] def or[B >: A, E2](alternative: => ParamTryBox[B, E2])(implicit a: DummyImplicit, b: DummyImplicit, c: DummyImplicit, d: DummyImplicit): ParamTryBox[B, E2] - override def map[B](f: A => B): ParamTryBox[B, E] = ??? + override def map[B](f: A => B): ParamTryBox[B, E] def flatMap[B, E2 >: E](f: A => ParamTryBox[B, E2]): ParamTryBox[B, E2] def flatMap[B](f: A => Full[B])(implicit a: DummyImplicit): ParamTryBox[B, E] } @@ -1069,6 +1061,8 @@ sealed case class Failure(msg: String, exception: Box[Throwable], chain: Box[Fai override def map[B](f: A => B): Failure = this + def collect[B](pf: PartialFunction[A, B]): Box[B] = this + override def flatMap[B](f: A => Box[B]): Box[B] = this override def flatMap[B](f: A => TryBox[B]): TryBox[B] = this