say, I have a bunch of "validation" functions that return None if there is no error, otherwise it return Some(String) specifying the error message. Something like the following ...
def validate1:Option[String]
def validate2:Option[String]
def validate3:Option[String]
I am going to call them in a sequence and as soon as one returns Some(String), I stop and return the same. If it returns None, I go to the next until the sequence is over. If all of them return None, I return None.
I would like to glue them together in a "for expression". Something like ...
for( a <- validate1; b <- validate2; c <- validate3) yield None;
However, Option flows exactly the opposite what I want here. It stops at None and follows with Some(String).
How can I achieve something like that?
You could just chain the calls together with the orElse method on Option
validate1 orElse validate2 orElse validate3
or you could run a fold over a collection of validate methods converted to functions
val vlist= List(validate1 _, validate2 _, validate3 _)
vlist.foldLeft(None: Option[String]) {(a, b) => if (a == None) b() else a}
The scalaz library has a type called Validation which allows for some incredible gymnastics with building both errors and success. For example, suppose you have a few methods which can either return a failure message or some successful outcome (A/B/C):
import scalaz._; import Scalaz._
def fooA : ValidationNEL[String, A]
def fooB : ValidationNEL[String, B]
def fooC : ValidationNEL[String, C]
These can be used with the applicative functor to chain the calls together:
(foo1 <|**|> (foo2, foo3)) match {
case Success( (a, b, c) ) => //woot
case Failure(msgs) => //erk
}
Note that if any one of foo1/2/3 fails, then the whole composition fails with a non-empty list (NEL) of failure messages. If more than one fails, you get all failure messages.
It's a killer app. Examples of how tor return a success and failure are as follows
def foo1 : ValidationNEL[String, Int] = 1.success
def foo2 : ValidationNEL[String, Double] = "some error msg".failNel
Can't you just combine the iterators together and then take the first element? Something like:
scala> def validate1: Option[String] = {println("1"); None}
scala> def validate2: Option[String] = {println("2"); Some("error")}
scala> def validate3: Option[String] = {println("3"); None}
scala> (validate1.iterator ++ validate2.iterator ++ validate3.iterator).next
1
2
res5: String = error
I think you might benefit from using Lift's Box, which has Full (i.e. Some), Empty (i.e. None) and Failure (an Empty with a reason why it's empty and that can be chained). David Pollak has a good blog post introducing it. In short, you might do something like this (not tested):
def validate1: Box[String]
def validate2: Box[String]
def validate3: Box[String]
val validation = for (
validation1 <- validate1 ?~ "error message 1"
validation2 <- validate2 ?~ "error message 2"
validation3 <- validate3 ?~ "error message 3"
) yield "overall success message"
This isn't any shorter than the original example but it's, in my opinion, a bit more logical, with the result of a successful validation in a Full and a failed validation in Failure.
However, we can get smaller. First, since our validation function return Box[String], they can return Failures themselves and we don't need to transform Empty to Failure ourselves:
val validation = for (
validation1 <- validate1
validation2 <- validate2
validation3 <- validate3
) yield "overall success message"
But, Box also has an or method that returns the same Box if it is Full or the other Box if it is not. This would give us:
val validation = validate1 or validate2 or validate3
However, that line stops at the first validation success, not the first failure. It might make sense to make another method that does what you want (perhaps called unless?) though I can't say that it would really be much more useful than the for comprehension approach.
However, here's a little library pimping that does it:
scala> class Unless[T](a: Box[T]) {
| def unless(b: Box[T]) = {
| if (a.isEmpty) { a }
| else b
| }
| }
defined class Unless
scala> implicit def b2U[T](b: Box[T]): Unless[T] = new Unless(b)
b2U: [T](b: net.liftweb.common.Box[T])Unless[T]
scala> val a = Full("yes")
a: net.liftweb.common.Full[java.lang.String] = Full(yes)
scala> val b = Failure("no")
b: net.liftweb.common.Failure = Failure(no,Empty,Empty)
scala> val c = Full("yes2")
c: net.liftweb.common.Full[java.lang.String] = Full(yes2)
scala> a unless b
res1: net.liftweb.common.Box[java.lang.String] = Failure(no,Empty,Empty)
scala> a unless b unless c
res2: net.liftweb.common.Box[java.lang.String] = Failure(no,Empty,Empty)
scala> a unless c unless b
res3: net.liftweb.common.Box[java.lang.String] = Failure(no,Empty,Empty)
scala> a unless c
res4: net.liftweb.common.Box[java.lang.String] = Full(yes2)
This is a quick hack based upon my limited understanding of Scala's type system, as you can see in the following error:
scala> b unless a
<console>:13: error: type mismatch;
found : net.liftweb.common.Full[java.lang.String]
required: net.liftweb.common.Box[T]
b unless a
^
However, that should be enough to get you on the right track.
Of course the Lift ScalaDocs have more information on Box.
Related
How to conveniently convert Seq[Try[Option[String, Any]]] into Try[Option[Map[String, Any]]].
If any Try before convert throws an exception, the converted Try should throw as well.
Assuming that the input type has a tuple inside the Option then this should give you the result you want:
val in: Seq[Try[Option[(String, Any)]]] = ???
val out: Try[Option[Map[String,Any]]] = Try(Some(in.flatMap(_.get).toMap))
If any of the Trys is Failure then the outer Try will catch the exception raised by the get and return Failure
The Some is there to give the correct return type
The get extracts the Option from the Try (or raises an exception)
Using flatMap rather than map removes the Option wrapper, keeping all Some values and discaring None values, giving Seq[(String, Any)]
The toMap call converts the Seq to a Map
Here is something that's not very clean but may help get you started. It assumes Option[(String,Any)], returns the first Failure if there are any in the input Seq and just drops None elements.
foo.scala
package foo
import scala.util.{Try,Success,Failure}
object foo {
val x0 = Seq[Try[Option[(String, Any)]]]()
val x1 = Seq[Try[Option[(String, Any)]]](Success(Some(("A",1))), Success(None))
val x2 = Seq[Try[Option[(String, Any)]]](Success(Some(("A",1))), Success(Some(("B","two"))))
val x3 = Seq[Try[Option[(String, Any)]]](Success(Some(("A",1))), Success(Some(("B","two"))), Failure(new Exception("bad")))
def f(x: Seq[Try[Option[(String, Any)]]]) =
x.find( _.isFailure ).getOrElse( Success(Some(x.map( _.get ).filterNot( _.isEmpty ).map( _.get ).toMap)) )
}
Example session
bash-3.2$ scalac foo.scala
bash-3.2$ scala -classpath .
Welcome to Scala 2.13.1 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_66).
Type in expressions for evaluation. Or try :help.
scala> import foo.foo._
import foo.foo._
scala> f(x0)
res0: scala.util.Try[Option[Equals]] = Success(Some(Map()))
scala> f(x1)
res1: scala.util.Try[Option[Equals]] = Success(Some(Map(A -> 1)))
scala> f(x2)
res2: scala.util.Try[Option[Equals]] = Success(Some(Map(A -> 1, B -> two)))
scala> f(x3)
res3: scala.util.Try[Option[Equals]] = Failure(java.lang.Exception: bad)
scala> :quit
If you're willing to use a functional support library like Cats then there are two tricks that can help this along:
Many things like List and Try are traversable, which means that (if Cats's implicits are in scope) they have a sequence method that can swap two types, for example converting List[Try[T]] to Try[List[T]] (failing if any of the items in the list are failure).
Almost all of the container types support a map method that can operate on the contents of a container, so if you have a function from A to B then map can convert a Try[A] to a Try[B]. (In Cats language they are functors but the container-like types in the standard library generally have map already.)
Cats doesn't directly support Seq, so this answer is mostly in terms of List instead.
Given that type signature, you can iteratively sequence the item you have to in effect push the list type down one level in the type chain, then map over that container to work on its contents. That can look like:
import cats.implicits._
import scala.util._
def convert(listTryOptionPair: List[Try[Option[(String, Any)]]]): Try[
Option[Map[String, Any]]
] = {
val tryListOptionPair = listTryOptionPair.sequence
tryListOptionPair.map { listOptionPair =>
val optionListPair = listOptionPair.sequence
optionListPair.map { listPair =>
Map.from(listPair)
}
}
}
https://scastie.scala-lang.org/xbQ8ZbkoRSCXGDJX0PgJAQ has a slightly more complete example.
One way to approach this is by using a foldLeft:
// Let's say this is the object you're trying to convert
val seq: Seq[Try[Option[(String, Any)]]] = ???
seq.foldLeft(Try(Option(Map.empty[String, Any]))) {
case (acc, e) =>
for {
accOption <- acc
elemOption <- e
} yield elemOption match {
case Some(value) => accOption.map(_ + value)
case None => accOption
}
}
You start off with en empty Map. You then use a for comprehension to go through the current map and element and finally you add a new tuple in the map if present.
The following solutions is based on this answer to the point that almost makes the question a duplicate.
Method 1: Using recursion
def trySeqToMap1[X,Y](trySeq : Seq[Try[Option[(X, Y)]]]) : Try[Option[Map[X,Y]]] = {
def helper(it : Iterator[Try[Option[(X,Y)]]], m : Map[X,Y] = Map()) : Try[Option[Map[X,Y]]] = {
if(it.hasNext) {
val x = it.next()
if(x.isFailure)
Failure(x.failed.get)
else if(x.get.isDefined)
helper(it, m + (x.get.get._1-> x.get.get._2))
else
helper(it, m)
} else Success(Some(m))
}
helper(trySeq.iterator)
}
Method 2: directly pattern matching in case you are able to get a stream or a List instead:
def trySeqToMap2[X,Y](trySeq : LazyList[Try[Option[(X, Y)]]], m : Map[X,Y]= Map.empty[X,Y]) : Try[Option[Map[X,Y]]] =
trySeq match {
case Success(Some(h)) #:: tail => trySeqToMap2(tail, m + (h._1 -> h._2))
case Success(None) #:: tail => tail => trySeqToMap2(tail, m)
case Failure(f) #:: _ => Failure(f)
case _ => Success(Some(m))
}
note: this answer was previously using different method signatures. It has been updated to conform to the signature given in the question.
I often want to log or to print something without changing it.
It looks like this:
val result = myResult // this could be an Option or a Future
.map{r =>
info(s"the result is $r")
r
}
These three lines are always the same.
In a for comprehension, this can be done a bit nicer.
But I look for a solution for the first declarative version. It should look like:
val result = myResult
.log(info(s"the result is ${_}"))
This one-liner could be put in every place in the chain where there could be a map, like:
val result = myResult
.log(info(s"1. ${_}"))
.filter(_ > 1)
.log(info(s"2. ${_}"))
...
How could this be achieved? If possible, without a functional library.
Ok so I decided to take a swing at this and I would like to retract my comment of
Maybe you can define a implicit class that has a method "log" acting on a Product ?
I was confident that Future and all the monads (collections, options) shared a common ancestor, turns out I was wrong. I have the following solution without using Cats. This can be done in a much prettier way in cats, besides the aforementioned "flatTap", and embelished with possibly cats.Ref or something.
Future is the obvious outliner here but as more exceptions come along you might need to expand this object.
import scala.concurrent._
import ExecutionContext.Implicits.global
object MonadicConv {
implicit class MonadicLog[+B <: Product, A] (val u: B){
def log(l: String, args: List[A] = List()): B = {
println("logging")
println(u)
println(l)
u
}
}
implicit class FutureLog[T, A](val u: Future[T]){
def log(l: String, args: List[A] = List()) : Future[T] = {
println("logging")
println(u)
println(l)
u
}
}
}
1) You will need to modify this with you own logging logic, I am just printing
2) I am not super proud of this as this is no longer a pure function. I am not sure if there is a work around this in Scala without using Cats. (There might be)
3) The args can be removed, just added them in case you want to pass in extra info
4) If you really want to combine these, you could try defining your own product, some leads: Implement product type in Scala with generic update function working on its parts
You can use this with
import MonadicConv._
val x = Some(5).log("").get
val lx = List(Some(5), Some(10), Some(1)).log("list").flatten.log("x").filter(_ > 1).log("")
val ff = Future.successful(List(Some(5), Some(10), Some(1))).log("fff").map(_.flatten.filter(_ > 1).log("inner")).log("")
This prints out
logging
Some(5)
option test
logging
List(Some(5), Some(10), Some(1))
list test
logging
List(5, 10, 1)
flat test
logging
List(5, 10)
filter test
logging
Future(Success(List(Some(5), Some(10), Some(1))))
future test
logging
Future(<not completed>)
incomplete test
logging
List(5, 10)
inner future test
Scastie version here
As I have mentioned, this is really the Cats land at this point. This is the best I could come up with in core Scala
For your purpose, it is best to use treelog. It turns the logging process and values into a Monad of DescribedComputation :
import treelog.LogTreeSyntaxWithoutAnnotations._
val result: DescribedComputation[YourValueType] = myResult ~> (_.fold("The result is empty")(r => s"The result is $r")
And usually to subtract the value from a DescribedComputation, use for comprehension:
for {
res <- result
} {
doSomethingTo(res)
}
See details from https://github.com/lancewalton/treelog
The whole example will look like:
val compRes = "Logging Result" ~< {
for {
r <- myResult ~> (_.fold("The result is empty")(r => s"The result is $r")
} yield r
}
}
for (res <- compRes) {
doSomethingTo(res)
}
logger.info(logging.run.written.shows)
The output will look like:
2019-11-18 00:00:00,000 INFO Logging Result
The result is XXX
Just for reference. ZIO provides this functionality nicely.
/**
* Returns an effect that effectfully "peeks" at the success of this effect.
*
* {{{
* readFile("data.json").tap(putStrLn)
* }}}
*/
final def tap[R1 <: R, E1 >: E](f: A => ZIO[R1, E1, Any]): ZIO[R1, E1, A] = self.flatMap(new ZIO.TapFn(f))
There is even a version for the error case:
/**
* Returns an effect that effectfully "peeks" at the failure of this effect.
* {{{
* readFile("data.json").tapError(logError(_))
* }}}
*/
final def tapError[R1 <: R, E1 >: E](f: E => ZIO[R1, E1, Any]): ZIO[R1, E1, A]
This makes debugging really easy:
myDangerousZioFunction
.tapError(e => putStrLn(s"Server Exception: $e"))
.tap(r => putStrLn(s"Result is $r"))
....
Is it possible to handle Either in similar way to Option? In Option, I have a getOrElse function, in Either I want to return Left or process Right. I'm looking for the fastest way of doing this without any boilerplate like:
val myEither:Either[String, Object] = Right(new Object())
myEither match {
case Left(leftValue) => value
case Right(righValue) =>
"Success"
}
In Scala 2.12,
Either is right-biased, which means that Right is assumed to be the default case to operate on. If it is Left, operations like map, flatMap, ... return the Left value unchanged
so you can do
myEither.map(_ => "Success").merge
if you find it more readable than fold.
You can use .fold:
scala> val r: Either[Int, String] = Right("hello")
r: Either[Int,String] = Right(hello)
scala> r.fold(_ => "got a left", _ => "Success")
res7: String = Success
scala> val l: Either[Int, String] = Left(1)
l: Either[Int,String] = Left(1)
scala> l.fold(_ => "got a left", _ => "Success")
res8: String = got a left
Edit:
Re-reading your question it's unclear to me whether you want to return the value in the Left or another one (defined elsewhere)
If it is the former, you can pass identity to .fold, however this might change the return type to Any:
scala> r.fold(identity, _ => "Success")
res9: Any = Success
Both cchantep's and Marth's are good solutions to your immediate problem. But more broadly, it's difficult to treat Either as something fully analogous to Option, particularly in letting you express sequences of potentially failable computations for comprehensions. Either has a projection API (used in cchantep's solution), but it is a bit broken. (Either's projections break in for comprehensions with guards, pattern matching, or variable assignment.)
FWIW, I've written a library to solve this problem. It augments Either with this API. You define a "bias" for your Eithers. "Right bias" means that ordinary flow (map, get, etc) is represented by a Right object while Left objects represent some kind of problem. (Right bias is conventional, although you can also define a left bias if you prefer.) Then you can treat the Either like an Option; it offers a fully analogous API.
import com.mchange.leftright.BiasedEither
import BiasedEither.RightBias._
val myEither:Either[String, Object] = ...
val o = myEither.getOrElse( "Substitute" )
More usefully, you can now treat Either like a true scala monad, i.e. use flatMap, map, filter, and for comprehensions:
val myEither : Either[String, Point] = ???
val nextEither = myEither.map( _.x ) // Either[String,Int]
or
val myEither : Either[String, Point] = ???
def findGalaxyAtPoint( p : Point ) : Either[String,Galaxy] = ???
val locPopPair : Either[String, (Point, Long)] = {
for {
p <- myEither
g <- findGalaxyAtPoint( p )
} yield {
(p, g.population)
}
}
If all processing steps succeeded, locPopPair will be a Right[Long]. If anything went wrong, it will be the first Left[String] encountered.
It's slightly more complex, but a good idea to define an empty token. Let's look at a slight variation on the for comprehension above:
val locPopPair : Either[String, (Point, Long)] = {
for {
p <- myEither
g <- findGalaxyAtPoint( p ) if p.x > 1000
} yield {
(p, g.population)
}
}
What would happen if the test p.x > 1000 failed? We'd want to return some Left that signifies "empty", but there is no universal appropriate value (not all Left's are Left[String]. As of now, what would happen is the code would throw a NoSuchElementException. But we can specify an empty token ourselves, as below:
import com.mchange.leftright.BiasedEither
val RightBias = BiasedEither.RightBias.withEmptyToken[String]("EMPTY")
import RightBias._
val myEither : Either[String, Point] = ???
def findGalaxyAtPoint( p : Point ) : Either[String,Galaxy] = ???
val locPopPair : Either[String, (Point, Long)] = {
for {
p <- myEither
g <- findGalaxyAtPoint( p ) if p.x > 1000
} yield {
(p, g.population)
}
}
Now, if the p.x > 1000 test fails, there will be no Exception, locPopPair will just be Left("EMPTY").
I guess you can do as follows.
def foo(myEither: Either[String, Object]) =
myEither.right.map(rightValue => "Success")
In scala 2.13, you can use myEither.getOrElse
Right(12).getOrElse(17) // 12
Left(12).getOrElse(17) // 17
Suppose I have few case classes and functions to test them:
case class PersonName(...)
case class Address(...)
case class Phone(...)
def testPersonName(pn: PersonName): Either[String, PersonName] = ...
def testAddress(a: Address): Either[String, Address] = ...
def testPhone(p: Phone): Either[String, Phone] = ...
Now I define a new case class Person and a test function, which fails fast.
case class Person(name: PersonName, address: Address, phone: Phone)
def testPerson(person: Person): Either[String, Person] = for {
pn <- testPersonName(person.name).right
a <- testAddress(person.address).right
p <- testPhone(person.phone).right
} yield person;
Now I would like function testPerson to accumulate the errors rather than just fail fast.
I would like testPerson to always execute all those test* functions and return Either[List[String], Person]. How can I do that ?
You want to isolate the test* methods and stop using a comprehension!
Assuming (for whatever reason) that scalaz isn't an option for you... it can be done without having to add the dependency.
Unlike a lot of scalaz examples, this is one where the library doesn't reduce verbosity much more than "regular" scala can:
def testPerson(person: Person): Either[List[String], Person] = {
val name = testPersonName(person.name)
val addr = testAddress(person.address)
val phone = testPhone(person.phone)
val errors = List(name, addr, phone) collect { case Left(err) => err }
if(errors.isEmpty) Right(person) else Left(errors)
}
Scala's for-comprehensions (which desugar to a combination of calls to flatMap and map) are designed to allow you to sequence monadic computations in such a way that you have access to the result of earlier computations in subsequent steps. Consider the following:
def parseInt(s: String) = try Right(s.toInt) catch {
case _: Throwable => Left("Not an integer!")
}
def checkNonzero(i: Int) = if (i == 0) Left("Zero!") else Right(i)
def inverse(s: String): Either[String, Double] = for {
i <- parseInt(s).right
v <- checkNonzero(i).right
} yield 1.0 / v
This won't accumulate errors, and in fact there's no reasonable way that it could. Suppose we call inverse("foo"). Then parseInt will obviously fail, which means there's no way we can have a value for i, which means there's no way we could move on to the checkNonzero(i) step in the sequence.
In your case your computations don't have this kind of dependency, but the abstraction you're using (monadic sequencing) doesn't know that. What you want is an Either-like type that isn't monadic, but that is applicative. See my answer here for some details about the difference.
For example, you could write the following with Scalaz's Validation without changing any of your individual validation methods:
import scalaz._, syntax.apply._, syntax.std.either._
def testPerson(person: Person): Either[List[String], Person] = (
testPersonName(person.name).validation.toValidationNel |#|
testAddress(person.address).validation.toValidationNel |#|
testPhone(person.phone).validation.toValidationNel
)(Person).leftMap(_.list).toEither
Although of course this is more verbose than necessary and is throwing away some information, and using Validation throughout would be a little cleaner.
As #TravisBrown is telling you, for comprehensions don't really mix with error accumulations. In fact, you generally use them when you don't want fine grained error control.
A for comprehension will "short-circuit" itself on the first error found, and this is almost always what you want.
The bad thing you are doing is using String to do flow control of exceptions. You should at all times use Either[Exception, Whatever] and fine tune logging with scala.util.control.NoStackTrace and scala.util.NonFatal.
There are much better alternatives, specifically:
scalaz.EitherT and scalaz.ValidationNel.
Update:(this is incomplete, I don't know exactly what you want). You have better options than matching, such as getOrElse and recover.
def testPerson(person: Person): Person = {
val attempt = Try {
val pn = testPersonName(person.name)
val a = testAddress(person.address)
testPhone(person.phone)
}
attempt match {
case Success(person) => //..
case Failure(exception) => //..
}
}
Starting in Scala 2.13, we can partitionMap a List of Eithers in order to partition elements based on their Either's side.
// def testName(pn: Name): Either[String, Name] = ???
// def testAddress(a: Address): Either[String, Address] = ???
// def testPhone(p: Phone): Either[String, Phone] = ???
List(testName(Name("name")), testAddress(Address("address")), testPhone(Phone("phone")))
.partitionMap(identity) match {
case (Nil, List(name: Name, address: Address, phone: Phone)) =>
Right(Person(name, address, phone))
case (left, _) =>
Left(left)
}
// Either[List[String], Person] = Left(List("wrong name", "wrong phone"))
// or
// Either[List[String], Person] = Right(Person(Name("name"), Address("address"), Phone("phone")))
If the left side is empty, then no elements were Left and thus we can build a Person out of the Right elements.
Otherwise, we return a Left List of the Left values.
Details of the intermediate step (partitionMap):
List(Left("bad name"), Right(Address("addr")), Left("bad phone"))
.partitionMap(identity)
// (List[String], List[Any]) = (List("bad name", "bad phone"), List[Any](Address("addr")))
I'm trying to validate the parameters of a method for nullity but i don't find the solution...
Can someone tell me how to do?
I'm trying something like this:
def buildNormalCategory(user: User, parent: Category, name: String, description: String): Either[Error,Category] = {
val errors: Option[String] = for {
_ <- Option(user).toRight("User is mandatory for a normal category").right
_ <- Option(parent).toRight("Parent category is mandatory for a normal category").right
_ <- Option(name).toRight("Name is mandatory for a normal category").right
errors : Option[String] <- Option(description).toRight("Description is mandatory for a normal category").left.toOption
} yield errors
errors match {
case Some(errorString) => Left( Error(Error.FORBIDDEN,errorString) )
case None => Right( buildTrashCategory(user) )
}
}
If you're willing to use Scalaz, it has a handful of tools that make this kind of task more convenient, including a new Validation class and some useful right-biased type class instances for plain old scala.Either. I'll give an example of each here.
Accumulating errors with Validation
First for our Scalaz imports (note that we have to hide scalaz.Category to avoid the name conflict):
import scalaz.{ Category => _, _ }
import syntax.apply._, syntax.std.option._, syntax.validation._
I'm using Scalaz 7 for this example. You'd need to make some minor changes to use 6.
I'll assume we have this simplified model:
case class User(name: String)
case class Category(user: User, parent: Category, name: String, desc: String)
Next I'll define the following validation method, which you can easily adapt if you move to an approach that doesn't involve checking for null values:
def nonNull[A](a: A, msg: String): ValidationNel[String, A] =
Option(a).toSuccess(msg).toValidationNel
The Nel part stands for "non-empty list", and a ValidationNel[String, A] is essentially the same as an Either[List[String], A].
Now we use this method to check our arguments:
def buildCategory(user: User, parent: Category, name: String, desc: String) = (
nonNull(user, "User is mandatory for a normal category") |#|
nonNull(parent, "Parent category is mandatory for a normal category") |#|
nonNull(name, "Name is mandatory for a normal category") |#|
nonNull(desc, "Description is mandatory for a normal category")
)(Category.apply)
Note that Validation[Whatever, _] isn't a monad (for reasons discussed here, for example), but ValidationNel[String, _] is an applicative functor, and we're using that fact here when we "lift" Category.apply into it. See the appendix below for more information on applicative functors.
Now if we write something like this:
val result: ValidationNel[String, Category] =
buildCategory(User("mary"), null, null, "Some category.")
We'll get a failure with the accumulated errors:
Failure(
NonEmptyList(
Parent category is mandatory for a normal category,
Name is mandatory for a normal category
)
)
If all of the arguments had checked out, we'd have a Success with a Category value instead.
Failing fast with Either
One of the handy things about using applicative functors for validation is the ease with which you can swap out your approach to handling errors. If you want to fail on the first instead of accumulating them, you can essentially just change your nonNull method.
We do need a slightly different set of imports:
import scalaz.{ Category => _, _ }
import syntax.apply._, std.either._
But there's no need to change the case classes above.
Here's our new validation method:
def nonNull[A](a: A, msg: String): Either[String, A] = Option(a).toRight(msg)
Almost identical to the one above, except that we're using Either instead of ValidationNEL, and the default applicative functor instance that Scalaz provides for Either doesn't accumulate errors.
That's all we need to do to get the desired fail-fast behavior—no changes are necessary to our buildCategory method. Now if we write this:
val result: Either[String, Category] =
buildCategory(User("mary"), null, null, "Some category.")
The result will contain only the first error:
Left(Parent category is mandatory for a normal category)
Exactly as we wanted.
Appendix: Quick introduction to applicative functors
Suppose we have a method with a single argument:
def incremented(i: Int): Int = i + 1
And suppose also that we want to apply this method to some x: Option[Int] and get an Option[Int] back. The fact that Option is a functor and therefore provides a map method makes this easy:
val xi = x map incremented
We've "lifted" incremented into the Option functor; that is, we've essentially changed a function mapping Int to Int into one mapping Option[Int] to Option[Int] (although the syntax muddies that up a bit—the "lifting" metaphor is much clearer in a language like Haskell).
Now suppose we want to apply the following add method to x and y in a similar fashion.
def add(i: Int, j: Int): Int = i + j
val x: Option[Int] = users.find(_.name == "John").map(_.age)
val y: Option[Int] = users.find(_.name == "Mary").map(_.age) // Or whatever.
The fact that Option is a functor isn't enough. The fact that it's a monad, however, is, and we can use flatMap to get what we want:
val xy: Option[Int] = x.flatMap(xv => y.map(add(xv, _)))
Or, equivalently:
val xy: Option[Int] = for { xv <- x; yv <- y } yield add(xv, yv)
In a sense, though, the monadness of Option is overkill for this operation. There's a simpler abstraction—called an applicative functor—that's in-between a functor and a monad and that provides all the machinery we need.
Note that it's in-between in a formal sense: every monad is an applicative functor, every applicative functor is a functor, but not every applicative functor is a monad, etc.
Scalaz gives us an applicative functor instance for Option, so we can write the following:
import scalaz._, std.option._, syntax.apply._
val xy = (x |#| y)(add)
The syntax is a little odd, but the concept isn't any more complicated than the functor or monad examples above—we're just lifting add into the applicative functor. If we had a method f with three arguments, we could write the following:
val xyz = (x |#| y |#| z)(f)
And so on.
So why bother with applicative functors at all, when we've got monads? First of all, it's simply not possible to provide monad instances for some of the abstractions we want to work with—Validation is the perfect example.
Second (and relatedly), it's just a solid development practice to use the least powerful abstraction that will get the job done. In principle this may allow optimizations that wouldn't otherwise be possible, but more importantly it makes the code we write more reusable.
I completely support Ben James' suggestion to make a wrapper for the null-producing api. But you'll still have the same problem when writing that wrapper. So here are my suggestions.
Why monads why for comprehension? An overcomplication IMO. Here's how you could do that:
def buildNormalCategory
( user: User, parent: Category, name: String, description: String )
: Either[ Error, Category ]
= Either.cond(
!Seq(user, parent, name, description).contains(null),
buildTrashCategory(user),
Error(Error.FORBIDDEN, "null detected")
)
Or if you insist on having the error message store the name of the parameter, you could do the following, which would require a bit more boilerplate:
def buildNormalCategory
( user: User, parent: Category, name: String, description: String )
: Either[ Error, Category ]
= {
val nullParams
= Seq("user" -> user, "parent" -> parent,
"name" -> name, "description" -> description)
.collect{ case (n, null) => n }
Either.cond(
nullParams.isEmpty,
buildTrashCategory(user),
Error(
Error.FORBIDDEN,
"Null provided for the following parameters: " +
nullParams.mkString(", ")
)
)
}
If you like the applicative functor approach of #Travis Brown's answer, but you don't like the Scalaz syntax or otherwise just don't want to use Scalaz, here is a simple library which enriches the standard library Either class to act as an applicative functor validation: https://github.com/youdevise/eithervalidation
For example:
import com.youdevise.eithervalidation.EitherValidation.Implicits._
def buildNormalCategory(user: User, parent: Category, name: String, description: String): Either[List[Error], Category] = {
val validUser = Option(user).toRight(List("User is mandatory for a normal category"))
val validParent = Option(parent).toRight(List("Parent category is mandatory for a normal category"))
val validName = Option(name).toRight(List("Name is mandatory for a normal category"))
Right(Category)(validUser, validParent, validName).
left.map(_.map(errorString => Error(Error.FORBIDDEN, errorString)))
}
In other words, this function will return a Right containing your Category if all of the Eithers were Rights, or it will return a Left containing a List of all the Errors, if one or more were Lefts.
Notice the arguably more Scala-ish and less Haskell-ish syntax, and a smaller library ;)
Lets suppose you have completed Either with the following quick and dirty stuff:
object Validation {
var errors = List[String]()
implicit class Either2[X] (x: Either[String,X]){
def fmap[Y](f: X => Y) = {
errors = List[String]()
//println(s"errors are $errors")
x match {
case Left(s) => {errors = s :: errors ; Left(errors)}
case Right(x) => Right(f(x))
}
}
def fapply[Y](f: Either[List[String],X=>Y]) = {
x match {
case Left(s) => {errors = s :: errors ; Left(errors)}
case Right(v) => {
if (f.isLeft) Left(errors) else Right(f.right.get(v))
}
}
}
}}
consider a validation function returning an Either:
def whenNone (value: Option[String],msg:String): Either[String,String] =
if (value isEmpty) Left(msg) else Right(value.get)
an a curryfied constructor returning a tuple:
val me = ((user:String,parent:String,name:String)=> (user,parent,name)) curried
You can validate it with :
whenNone(None,"bad user")
.fapply(
whenNone(Some("parent"), "bad parent")
.fapply(
whenNone(None,"bad name")
.fmap(me )
))
Not a big deal.