Connecting...

W1siziisimnvbxbpbgvkx3rozw1lx2fzc2v0cy9zawduawz5lxrly2hub2xvz3kvanbnl2jhbm5lci1kzwzhdwx0lmpwzyjdxq

Shapeless and Applicative Functors for Server side validations by Afsal Thaj

W1siziisijiwmtkvmdevmzavmtuvndmvntuvodgzl3blegvscy1wag90by01ndy4mtkuanblzyjdlfsiccisinrodw1iiiwiotawedkwmfx1mdazzsjdxq

Dive into shapeless and Scalaz with Software Engineer, Afsal Thaj.

If you know type classes then this article is for you, look at how validation behaviour can be encoded as a type class and so much more. Happy coding!

 

'This is a quick deep dive into shapeless and Scalaz applicative builder pattern working together and solving a use-case for better understanding. You may well be tweaking further to get your stuff done based on what is presented here.

 

Audience

This is targeted for those who know type classes (Haskell type class taken to Scala), Generic Repr in shapelessapplicative functorsapplicative builder pattern in Scalaz/Cats (and optionally tagged type)

 

Problem(s)

A typical example of using applicative builders is when you need to validate an entity where the validation of each field in the entity returns an applicative functor. And when you have dozens of such entities (never dealt with it until yesterday as I am writing this) sharing fields across each other, you finally have a bloated boilerplate of Advanced (for some) Scala.

Ok, so how many times have you hacked around validating fields each one returning an applicative and then write long boilerplates of using applicative builder pattern for each of your aggregates in the app? Something like this:

(column1.validate|@| column2.validate|@| 
column3.successNel[ValidationError]) {Entity.apply}

How many times have you re-written the validation logic for fields that are shared across aggregates, and then write applicative builders explicitly every time?

Or at the very least, how many times you were confused on where to put all of the boilerplates of applicative builders in your application?

 

Solution

What do you think of a solution that provides the following as an alternative?

When you provide validations (only) for every field values in your app that you use in your application:

* You get validation for all your entity objects, value objects and aggregates for FREE
* Return the list of errors or the validated entity for all your entities in your application with just one '|@|' in your entire app.
* You get a compile time if you try to use a column that doesn’t have a validation instance in any entity or value objects (Well, this is not a big deal you know but sounds good though)

 

Let’s write some code

The validation behaviour is encoded as a type class, with a validate function returning an applicative functor.

1   import scalaz.ValidationNel
2
3   trait Validation[A] {
4     def validate(a: A): ValidationNel[ValidationError, A]
5   }
6
7   object Validation {
8     implicit def validation[A](implicit a: Validation[A]): Validation[A] = a
9 
10     // Inspired from shapeless docs
11     def createInstance[A](f: A => ValidationNel[ValidationError, A]): Validation[A]  = {
12       a => f(a)
13     }
14
15     implicit class Validator[A](a: A) {
16       def validate(implicit validator: Validation[A]): ValidationNel[ValidationError, A] =
17         validator validate a
18     }
19 
20     sealed trait ValidationError
21 
22     object ValidationError {
23       case class InvalidValue(msg: String) extends ValidationError
24     // Bla
25     }
26   }

Let’s use tagged types instead of primitives just to differentiate your types are your own types.

1 import scalaz.syntax.std.option._
2 import scalaz.{@@, Show, Tag}
3 import scalaz.syntax.show._
4
5 trait Tagger[A] {
6   sealed trait Marker
7   final type Type = A @@ Marker
8   def apply(a: A): Type = Tag[A, Marker](a)
9   def unapply(tagged: Type): Option[A] = unwrapped(tagged).some
10   def unwrapped(tagged: Type): A = Tag.unwrap(tagged)
11 }

Let’s define an aggregate (may not make much sense, doesn’t matter)

1     import SuperExchange._
2     import scalaz.std.string._
3
4     import scalaz.syntax.validation._
5     import SuperExchange._
6
7     case class SuperExchange(id: ExchangeId, name: Name)
8
9     object SuperExchange {
10       type ExchangeId = ExchangeId.Type
11       object ExchangeId extends Tagger[String]
12 
13       type Name = Name.Type
14       object Name extends Tagger[String]
15     }

Let’s define instances for each field

1 implicit val validateName: Validation[Name] =
2   _.successNel[ValidationError]
3
4 implicit val validateExchangeId: Validation[ExchangeId]=
5   _.successNel[ValidationError]

Ok, now we have instances of Validation for each field in SuperExchange. Now our intention is to get an instance for SuperExchange for free.

For that, first, let’s create an instance for any HList, provided we have the validation instance for its the head and tail (which in turns has a validation instance for its head and tail and it goes on recursively until HNil).

1 import shapeless.{HList, ::, HNil }
2 import scalaz.syntax.applicative._
3 import scalaz.syntax.either._
4 import scalaz.syntax.validation._
5
6 implicit val hnilEncode: Validation[HNil] =
7   Validation.createInstance( _.successNel[ValidationError]) //Yea, that's it
8
9 implicit def hlistEncoder[H, T <: HList] (
10   implicit
11     hEncoder: Validation[H],
12     tEncoder: Validation[T]
13   ): Validation[H :: T] =
14    Validation.createInstance {
15      case h :: t => (hEncoder.validate(h) |@| tEncoder.validate(t)){_ :: _}
16    }

Once we have the instance for 'HList' (that means we have a validation instance for 'Generic.Repr'), and once we have a Generic instance for SuperExchange (which you get for Free from shapeless), we can derive the validation instance for 'SuperExchange'. 
Let’s have that in place:

1 implicit def genericValidation[A, R](
2   implicit gen: Generic.Aux[A, R], env: Validation[R]
3 ): Validation[A] =
4   new Validation[A] { 
5     override def validate(b: A) = 
6       env.validate(gen.to(b)).map(gen.from) 
7   }
8
9 // Please note that Generic.Aux[A, R] === Generic[A] {type Repr = R} 
10 // as we cannot do env: Validation[gen.Repr]

You may note that there is a bit going on with respect to 'gen.to' and 'gen.from', but that is just us trying to satisfy the compiler work and it is not something far from the docs of shapeless. When you do 'env.validate(gen.to(b))' what you get in return is a 'Validation[Generic.Repr]' and not for 'A' (which is, in fact, going to be our aggregate or entity). To get the validation instance for A you map it further on the returned applicative and get 'A' from 'Repr' using 'gen.from'

That’s it. Now that you have the instances freely available for any entities that use (and only use) ExchangeId and Name types. Let’s test this:

1 @ //This is ammonite configured for shapeless
2
3 @ implicitly[Validation[SuperExchange]].validate(SuperExchange(ExchangeId("id"), Name("name")))
4 res48: ValidationNel[ValidationError, SuperExchange] = Success(SuperExchange("id", "name"))

Let’s work it for other entities that re-use some of the entities, and see if we can forget about applicative builders and be in a nice world.

1 @ //This is ammonite configured for shapeless
2 
3 @ object NewNumber extends Tagger[Int]
4 defined object NewNumber
5
6 @ type NewNumber = NewNumber.Type
7 defined type NewNumber
8
9 @ case class World(name: Name, exchangId: ExchangeId, newNumber: NewNumber)
10 defined class World
11
12 @ implicit val newNumberValidation: Validation[NewNumber] =
13    a =>
14     if (Tag.unwrap(a) > 10)
15       (ValidationError.InvalidValue("NewNumber is greater than 10"): ValidationError).failureNel[NewNumber]
16     else
17       a.successNel[ValidationError]
18 newNumberValidation: Validation[Int] = ammonite.$sess.cmd50$$$Lambda$2743/796603018@4e1984de
19 
20 @ implicitly[Validation[World]].validate(World(Name("name"), ExchangeId("id"), 1))
21 res51: ValidationNel[ValidationError, World] = Success(World("name", "id", 1))
22 
23 @ implicitly[Validation[World]].validate(World(Name("name"), ExchangeId("id"), 11))
24 res52: ValidationNel[ValidationError, World] = Failure(NonEmpty[InvalidValue(NewNumber is greater than 10)])

Please note that we have given validation instance for that extra field 'newNumber', else we get a compile-time error.

Conceptually it is very pleasing: “Provide validations for all the fields, and any entities that use them are automatically validated”.

Stare at these codes for a while, and I will probably come up with a better explanation of the concepts later on.

Thanks and have fun!'

 

This article was written by Afsal Thaj and posted originally on Medium