Checking method parameters in Scala, for understanding and monads

I am trying to check method parameters for nullity, but I did not find a solution ...

Can someone tell me how to do this?

I am 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) ) } } 
+29
scala monads for-comprehension either
Sep 06 '12 at 20:35
source share
4 answers

If you want to use Scalaz , it has several tools that make this task more convenient, including the new Validation class and some useful instances of the direct-biased class class for the plain old scala.Either . I will give an example of each of them.

Accumulating errors with Validation

First for our Scalaz import (note that we need to hide scalaz.Category in order to avoid name conflict):

 import scalaz.{ Category => _, _ } import syntax.apply._, syntax.std.option._, syntax.validation._ 

I am using Scalaz 7 for this example. You will need to make some minor changes to use 6.

I assume we have this simplified model:

 case class User(name: String) case class Category(user: User, parent: Category, name: String, desc: String) 

Next, I define the following verification method, which you can easily adapt if you go to an approach that does not include checking for null values:

 def nonNull[A](a: A, msg: String): ValidationNel[String, A] = Option(a).toSuccess(msg).toValidationNel 

The Nel part means a non-empty list, and the ValidationNel[String, A] is essentially the same as Either[List[String], A] .

Now we use this method to test 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) 

Please note that Validation[Whatever, _] not a monad (for example, for the reasons discussed here ), but ValidationNel[String, _] is an application functor, and we will use this fact here when we β€œraise” Category.apply to 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 will get a crash with accumulated errors:

 Failure( NonEmptyList( Parent category is mandatory for a normal category, Name is mandatory for a normal category ) ) 

If all the arguments have been checked, instead, we will have Success with Category .

Failed fast with Either

One convenient way to use applicative functors to check is the ease with which you can change your approach to error handling. If you want to dump the first rather than accumulate them, you can simply change your nonNull method.

We need a slightly different set of imports:

 import scalaz.{ Category => _, _ } import syntax.apply._, std.either._ 

But there is no need to change the case classes above.

Here is our new validation method:

 def nonNull[A](a: A, msg: String): Either[String, A] = Option(a).toRight(msg) 

It is almost identical to the above, except that we use Either instead of ValidationNEL , and the example of the default application functionality that Scalaz provides for Either does not accumulate errors.

This is all we need to do to get the desired fast execution - no changes are needed in 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) 

Just the way we wanted.

Appendix: A Brief Introduction to Applicative Functors

Suppose we have a method with a single argument:

 def incremented(i: Int): Int = i + 1 

Suppose also that we want to apply this method to some x: Option[Int] and get Option[Int] . The fact that Option is a functor and therefore provides a map method makes this simple:

 val xi = x map incremented 

We "raised" incremented into the Option functor; that is, we essentially changed the mapping of the Int function to Int by one mapping of Option[Int] to Option[Int] (although the syntax mutates a bit up - the β€œlifting” metaphor is much clearer in a language such as Haskell).

Now suppose we want to apply the following add method to x and y similar way.

 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 is not enough. The fact that this is a monad, however, 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 way, Option monad is redundant for this operation. There, a simpler abstraction called an application functor is an intermediate functor and monad, which provides all the necessary equipment.

Note that this is intermediate in the formal sense: each monad is an applied functor, each applied functor is a functor, but not every applied functor is a monad, etc.

Scalaz gives us an instance of the application functor for Option , so we can write the following:

 import scalaz._, std.option._, syntax.apply._ val xy = (x |@| y)(add) 

The syntax is a little strange, but the concept is no more complicated than the examples of the functor or monad above - we just raise add to 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 when we have monads? First, it is simply not possible to provide monad instances for some of the abstractions we want to work with - Validation is a great example.

The second (and related) is simply solid development practice to use the least powerful abstraction that does its job. In principle, this may allow optimization that would otherwise be impossible, but more importantly, it makes the code we write more reusable.

+84
06 Sep '12 at 22:00
source share

I fully support Ben James's suggestion of making a wrapper for a zero producing api. But you will have the same problem when writing this wrapper. So here are my suggestions.

Why monads why for understanding? IMO implementation. Here is how you could do it:

 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 that the error message keep the parameter name, you can do the following, which will require a slightly larger template:

 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(", ") ) ) } 
+6
Sep 06
source share

If you like @Travis Brown's applicative functor approach, but don't like Scalaz syntax, or else just don't want to use Scalaz, here is a simple library that enriches the standard library. Any class should act as a confirmation of the applicative functor: 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 Eithers were rights, or it will return Left containing a list of all errors if one or more were left.

Pay attention to the stronger Scala -ish syntax and less Haskell-ish and a smaller library;)

+3
Feb 27 '13 at 23:47
source share

Suppose you are finished with either the following quick and dirty things:

 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 that returns either:

  def whenNone (value: Option[String],msg:String): Either[String,String] = if (value isEmpty) Left(msg) else Right(value.get) 

curryfied constructor returning a tuple:

  val me = ((user:String,parent:String,name:String)=> (user,parent,name)) curried 

You can check it with:

  whenNone(None,"bad user") .fapply( whenNone(Some("parent"), "bad parent") .fapply( whenNone(None,"bad name") .fmap(me ) )) 

Never mind.

0
Nov 12 '14 at 15:37
source share



All Articles