DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
  1. DZone
  2. Coding
  3. Languages
  4. Validation With Tagless Final

Validation With Tagless Final

This article will discuss a validation framework with Tagless Final and demonstrate how the proposed validation framework can improve the quality of encoding.

Michael Wang user avatar by
Michael Wang
·
Dec. 27, 22 · Analysis
Like (2)
Save
Tweet
Share
3.03K Views

Join the DZone community and get the full member experience.

Join For Free

In one of our previous articles, Validation for Free in Scala, we discussed a validation framework by lifting validators to monad “for free”. In this article, we will discuss a validation framework with Tagless Final. Through examples, we demonstrate how the proposed validation framework can improve the quality of encoding.

Validation

Validation falls into two categories: Fail Fast and Error Accumulation. In Fail Fast scenario, validators return monadic validation results; a for-comprehension can be used to complete the validation. The following code returns the first error it encounters without executing the rest of the validation:

Scala
 
for { name <- UserValidator.validateName(name) 
	phoneNumber <- UserValidator.validatePhoneNumber(phone) 
    age <- UserValidator.validateAge(age) 
} yield User(name, phoneNumber, age)


Note that as long as the wrapper of the validation result is monadic, this code always works. There are a number of monadic wrappers to wrap the validation result to represent the error case, for instance, None to represent the error if Option is the wrapper; Throwable to represent the error if Try is a wrapper, or customized error object to represent the error using Either.

In Error Accumulation, by using applicatives instead of monads, validation is carried out, and errors are accumulated even if intermediate evaluation fails, such that all the errors can be collected in one evaluation. However, Option, Try, or Either can only hold one result; they only work in fast fail scenarios but not in error accumulation validation. 

Cats is a library that provides abstractions for functional programming in the Scala programming language. Cats provide a very effective wrapper called Validated for error accumulation validation.  Errors can be modeled as ApplicativeError, and customized errors can be accumulated in Validated in a NonEmptyList.

In many cases, we do not want to commit to a container too early. Now the question is, how can we encode a validation that is agnostic to the container of the validation result? 

Here comes Tagless Final.

Tagless Final

Tagless Final style is a method of embedding domain-specific languages (DSLs) in a typed functional host language. It is a functional pattern that allows to compose of a set of functions and ensures type safety. It is centered around interpreters; for instance, a validator of a validation framework is an interpreter in the Tagless Final context. A higher kind type F[_] is used to annotate the operators and the operands such that type checking is performed at compile time and evaluation maintains type safety at run time. F[_] is an abstraction of the wrapper in which the structure of the program is preserved. There is a rich literature on Tagless Final. In this article, we do not intend to elaborate on this coding pattern. Instead, we will discuss how Tagless Final can be used to improve our validation. 

Our validation framework contains the following parts:

  • A domain to describe the problem;
  • Validation Algebra or DSL, an abstraction of the validation rules;
  • An interpreter that implements the validation rules;
  • A program to execute the validation rules.

Domain 

Suppose a User has Name, Phone, Email, and Age attributes::

 Our validation is to validate each attribute and report the following errors:

Scala
 
case class Name(value:String) 
case class Phone(value:String) 
case class Email(value:String) 
case class Age(value:Int) 
case class User(name: Name, phone:Phone, email:Email, age:Age)


Scala
 
sealed trait UserError 
case object InvalidName extends UserError 
case object InvalidAge extends UserError 
case object InvalidPhoneNumber extends UserError 
case object InvalidEmail extends UserError


Above, our validation is to validate each attribute and report the following errors.

Algebra

Our DSL has a simple operation:

Scala
 
trait UserValidator[F[_]] extends  Validator[F, User] 
{   
    def validate:F[User] 
}


Where F[_] is the type constructor as a container of validation result. 

Interpreter

The interpreter is a generic function that takes a user and returns the validation result in the container F[_].  The interpreter uses an applicative in cats that takes the result of validation results and wraps it in higher-kind type F.

Scala
 
def interpreter: User => F[User] = { user => {
     (User.apply _).curried.pure[F] <*>
	validateName(user.name) <*>
	validatePhone(user.phone) <*>
	validateEmail(user.email) <*>
	validateAge(user.age)
}


Validation errors are wrapped in F[_] and modeled as ApplicativeError to support error accumulation.  The interpreter can be easily extended in the Tagless Final solution, as we demonstrated to support Option, Try, Either, and Validated (Cats) as typeclasses.

Program

Finally, a program that supplies the user instance to the interpreter and executes it. The interpreter typeclass is implicitly imported into the scope such that various containers are tested. For instance:


Scala
 
import com.taglessfinal.validator.userValidatorOptionInterpreter
user1.validate.tap(println)

import com.taglessfinal.validator.userValidatorTryInterpreter
user1.validate.tap(println)

import com.taglessfinal.validator.userValidatorEitherInterpreter
user1.validate.tap(println)

import com.taglessfinal.validator.userValidatorValidatedInterpreter
user1.validate.tap(println)


The implementation of validating rules is trivial.

Summary

Tagless Final pattern, coupled with type classes, provides an elegant solution to validation framework implementation.  The solution is type-safe, extensible, and agnostic to the container of validation results; the solution works both in a fail-fast fashion and in error accumulation. It can allow the project to avoid committing to validation scenarios too early and be flexible and extensible.

In this article, we discussed the specifics of the Validation and the Tagless Final pattern and discussed how Tagless Final can be used to implement an extensible validation DSL, and finally, provided an implementation of a validation framework in Tagless Final that supports four different containers.

Container Functional programming Implementation Reference implementation Type safety CATS (software) Framework Object (computer science) Scala (programming language) Domain-Specific Language Data validation

Published at DZone with permission of Michael Wang. See the original article here.

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • How To Use Terraform to Provision an AWS EC2 Instance
  • Microservices Discovery With Eureka
  • How Do the Docker Client and Docker Servers Work?
  • The 12 Biggest Android App Development Trends in 2023

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: