SlideShare a Scribd company logo
Writing DSL with 
Applicative Functors 
David Galichet 
Freelance functional programmer 
! 
twitter: @dgalichet
Content normalization 
• We want to parse heterogeneous data formats 
(CSV, XML …) and transform them to a pivot format 
(Scala object in our case) 
• The transformation should be described as a DSL 
• This DSL must be simple to use, and enable any 
kinds of data transformations or verifications
Expected DSL format 
val reader = ( ! 
Pick(0).as[String].map(_.capitalize) and ! 
Pick(1).as[Date].check(_.after(now())) ! 
).reduce(FutureEvent)! 
! 
reader("PSUG; 21/08/2014") // returns Success(FutureEvent("PSUG", 
date)) 
Inspired by Play2 Json API
tag: step1 
Conventions & material 
• Code is available on Github : https://github.com/ 
dgalichet/PsugDSLWritingWithApplicative 
• Code revision is define on the top of any slides 
including source code (just checkout the specified 
tag)
tag: step1 
Reading single entry 
• We have a CSV line and we want to read one column 
• We will introduce several abstractions: 
• Picker: fetch a data from CSV or XML 
• Result: either a Success or a Failure 
• Converter: convert value from Picker to Reader 
• Reader: container with methods to process its 
content
tag: step1 
Introducing Picker 
case class Picker(p: String => Result[String]) {! 
def as[T](implicit c: Converter[T]): Reader[T] = 
c.convert(p)! 
}! 
! 
object CsvPicker {! 
def apply[T](i: Int)(implicit separator: Char): Picker = 
Picker { s: String =>! 
val elems = s.trim.split(separator)! 
if (i > 0 && elems.size > i) Success(elems(i).trim)! 
else Failure(s"No column ${i} for ${s}")! 
}! 
}
tag: step1 
Introducing Picker 
case class Picker(p: String => Result[String]) {! 
def as[T](implicit c: Converter[T]): Reader[T] = 
c.convert(p)! 
}! 
! 
object CsvPicker {! 
def apply[T](i: Int)(implicit separator: Char): Picker = 
Picker { s: String =>! 
val elems = s.trim.split(separator)! 
if (i > 0 && elems.size > i) Success(elems(i).trim)! 
else Failure(s"No column ${i} for ${s}")! 
}! 
} 
Picker wraps a function from String to Result
The Result 
tag: step1 
sealed trait Result[+T] 
case class Success[T](t: T) extends Result[T] 
case class Failure(error: String) extends Result[Nothing]!
The Converter 
tag: step1 
trait Converter[T] { 
def convert(p: String => Result[String]): Reader[T] 
}! 
! 
object Converter {! 
implicit val string2StringConverter = new Converter[String] {! 
override def convert(p: String => Result[String]) = Reader[String] 
(p)! 
// See code on Github for more converters! 
}
The Converter 
tag: step1 
trait Converter[T] { 
def convert(p: String => Result[String]): Reader[T] 
}! 
! 
Convert the content of the Picker to a Reader 
! 
object Converter {! 
implicit val string2StringConverter = new Converter[String] {! 
override def convert(p: String => Result[String]) = Reader[String] 
(p)! 
// See code on Github for more converters! 
}
The Reader 
tag: step1 
case class Reader[O](p: String => Result[O]) { 
def apply(s: String): Result[O] = p(s) 
} 
A Reader doesn’t contain a value but a process to 
transform original data (CSV line or XML) to a 
Result
Usage sample 
tag: step1 
import Converter._ // import implicit converters! 
implicit val separator = ‘;’! 
! 
CsvPicker(1).as[String].apply("foo;bar") === "bar"
tag: step2 
Enhancing the Reader 
• The first defines a very simple Reader. We must 
add a method to combine two instances of Reader 
• We will also enhance Failure to store multiple 
error messages
tag: step2 
Enhancing the Reader 
case class Reader[O](p: String => Result[O]) {! 
def apply(s: String): Result[O] = p(s)! 
! 
def and[O2](r2: Reader[O2]): Reader[(O, O2)] = Reader { s: String =>! 
(p(s), r2.p(s)) match {! 
case (Success(s1), Success(s2)) => Success((s1, s2))! 
case (Success(_), Failure(f)) => Failure(f)! 
case (Failure(f), Success(_)) => Failure(f)! 
case (Failure(f1), Failure(f2)) => Failure(f1 ++ f2)! 
}! 
}! 
def map[T](f: O => T): Reader[T] = Reader { s: String =>! 
p(s) match {! 
case Success(o) => Success(f(o))! 
case f: Failure => f! 
}! 
}! 
def reduce[T] = map[T] _ // alias for map! 
}
tag: step2 
Enhancing Result type 
sealed trait Result[+T]! 
case class Success[T](t: T) extends Result[T]! 
case class Failure(error: NonEmptyList[String]) extends 
Result[Nothing]! 
! 
object Failure {! 
def apply(s: String): Failure = Failure(NEL(s))! 
}! 
! 
case class NonEmptyList[T](head: T, tail: List[T]) { 
def toList = head::tail 
def ++(l2: NonEmptyList[T]): NonEmptyList[T] = NonEmptyList(head, 
tail ++ l2.toList) 
} 
object NEL { 
def apply[T](h: T, t: T*) = NonEmptyList(h, t.toList) 
}
Usage sample 
tag: step2 
implicit val separator = ';' 
implicit val dtFormatter = new SimpleDateFormat("dd/MM/yyyy") 
import Converter.string2StringConverter 
import Converter.string2DateConverter! 
! 
val reader = ( 
CsvPicker(1).as[String] and 
CsvPicker(2).as[Date] 
).reduce { case (n, d) => FutureEvent(n, d) }! 
reader("foo;bar;12/10/2014") === Success(FutureEvent("bar", 
dtFormatter.parse("12/10/2014")))! 
! 
case class FutureEvent(name: String, dt: Date)
tag: step2 
Usability problem 
• The use of reduce (or map) method to transform a 
Reader[(0, 02)] into an instance of 
Reader[FutureEvent] for example is quite 
verbose 
• This will be even more verbose for instances of 
Reader[(0, (02, 03))] 
• We want the API to automatically bind tuple 
elements to a constructor as we can encounter in 
Play2 Json API
tag: step3 
Applicative functors 
• To tackle our problem, we will use Applicative 
Functors and play2 functional library (and 
especially FunctionalBuilder) 
• This approach is inspired by @sadache (Sadek 
Drobi) article https://gist.github.com/sadache/ 
3646092 
• An Applicative Functor is a Type Class relying on 
ad-hoc polymorphism to extends a Class with some 
properties 
• Play2 functional library (or Scalaz) provides 
mechanism to compose Applicatives in a smart way
tag: step3 
Applicative functors 
M is an Applicative Functor if there exists the following methods : 
def pure[A](a: A): M[A] 
def map[A, B](m: M[A], f: A => B): M[B] 
def apply[A, B](mf: M[A => B], ma: M[A]): M[B]! 
with the following Laws : 
• Identity: apply(pure(identity), ma) === ma where ma is an Applicative M[A] 
• Homomorphism: apply(pure(f), pure(a)) === pure(f(a)) where f: A => 
B and a an instance of A 
• Interchange: mf if an instance of M[A => B] 
apply(mf, pure(a)) === apply(pure {(g: A => B) => g(a)}, mf)! 
• Composition: map(ma, f) === apply(pure(f), ma)
tag: step3 
Applicative functors 
trait Applicative[M[_]] { 
def pure[A](a: A): M[A] 
def map[A, B](m: M[A], f: A => B): M[B] 
def apply[A, B](mf: M[A => B], ma: M[A]): M[B] 
} Applicative is an Higher Kinded type 
(parameterized with M that take a single type parameter)
tag: step3 
Reader is an Applicative 
case class Reader[O](p: String => Result[O]) { 
def apply(s: String): Result[O] = p(s)! 
def map[T](f: O => T): Reader[T] = Reader { s: String => 
p(s) match { 
case Success(o) => Success(f(o)) 
case f: Failure => f 
} 
}! 
}! 
object Reader { 
def map2[O, O1, O2](r1: Reader[O1], r2: Reader[O2])(f: (O1, O2) => 
O): Reader[O] = Reader { s: String => 
(r1.p(s), r2.p(s)) match { 
case (Success(s1), Success(s2)) => Success(f(s1, s2)) 
case (Success(_), Failure(e)) => Failure(e) 
case (Failure(e), Success(_)) => Failure(e) 
case (Failure(e1), Failure(e2)) => Failure(e1 ++ e2) 
} 
} …! 
}
tag: step3 
Reader is an Applicative 
object Reader { 
… // map2 
implicit val readerIsAFunctor: Functor[Reader] = new 
Functor[Reader] { 
override def fmap[A, B](m: Reader[A], f: (A) => B) = m.map(f) 
} 
implicit val readerIsAnApplicative: Applicative[Reader] = new 
Applicative[Reader] { 
override def pure[A](a: A) = Reader { _ => Success(a) } 
override def apply[A, B](mf: Reader[A => B], ma: Reader[A]) = 
map2(mf, ma)((f, a) => f(a)) 
override def map[A, B](m: Reader[A], f: A => B) = m.map(f) 
} 
}
Usage sample 
tag: step3 
import Converter.string2StringConverter 
import Converter.string2DateConverter! 
import play.api.libs.functional.syntax._ 
import Reader.readerIsAnApplicative! 
! 
implicit val separator = ';' 
implicit val dtFormatter = new SimpleDateFormat("dd/MM/yyyy")! 
! 
val reader = ( 
CsvPicker(1).as[String] and 
CsvPicker(2).as[Date] 
)(FutureEvent) // here we use CanBuild2.apply 
reader("foo;bar;12/10/2014") === Success(FutureEvent("bar", 
dtFormatter.parse("12/10/2014")))!
Usage sample 
tag: step3 
(errors accumulation) 
import Converter.string2StringConverter 
import Converter.string2DateConverter! 
import play.api.libs.functional.syntax._ 
import Reader.readerIsAnApplicative! 
! 
implicit val separator = ';' 
implicit val dtFormatter = new SimpleDateFormat("dd/MM/yyyy")! 
! 
val reader = ( 
CsvPicker(1).as[Int] and 
CsvPicker(2).as[Date] 
)((_, _)) 
reader(List("foo", "not a number", "not a date")) === Failure(NEL(! 
"Unable to format 'not a number' as Int", ! 
"Unable to format 'not a date' as Date"))!
Benefits 
tag: step3 
• Making Reader an Applicative Functor give ability 
to combine efficiently instances of Reader 
• Due to Applicative properties, we still accumulate 
errors 
• Play2 functional builder give us a clean syntax to 
define our DSL
tag: step4 
Introducing XML Picker 
case class Picker(p: String => Result[String]) { 
def as[T](implicit c: Converter[T]): Reader[T] = c.convert(p) 
}! 
! 
object XmlPicker { 
def apply[T](query: Elem => NodeSeq): Picker = Picker { s: String => 
try { 
val xml = XML.loadString(s) 
Success(query(xml).text) 
} catch { 
case e: Exception => Failure(e.getMessage) 
} 
} 
}!
Usage sample 
tag: step4 
import play.api.libs.functional.syntax._ 
import Reader.readerIsAnApplicative! 
import Converter._ 
implicit val dF = new SimpleDateFormat("dd/MM/yyyy")! 
val xml = """ 
<company name="Dupont and Co"> 
<owner> 
<person firstname="jean" lastname="dupont" birthdate="11/03/1987"/> 
</owner> 
</company>""" 
val r = ( 
XmlPicker(_  "person"  "@firstname").as[String] and 
XmlPicker(_  "person"  "@lastname").as[String] and 
XmlPicker(_  "person"  "@birthdate").as[Date] 
)(Person)! 
r(xml) === Success(Person("jean","dupont",dF.parse("11/03/1987"))) 
case class Person(firstname: String, lastname: String, birthDt: Date)
tag: step4 
Implementation problem 
• The Reader[O] takes a type argument for the 
output. The input is always a String 
• With this implementation, an XML content will be 
parsed (with XML.load) as many times as we use 
XmlPicker. This will cause unnecessary overhead 
• We will have the same issue (with lower overhead) 
with our CsvPicker
tag: step5 
Introducing Reader[I, 0] 
To resolve this problem, we will modify Reader to 
take a type parameter for the input
tag: step5 
Introducing Reader[I, 0] 
case class Reader[I, O](p: I => Result[O]) { 
def apply(s: I): Result[O] = p(s) 
def map[T](f: O => T): Reader[I, T] = Reader { s: I => 
p(s) match { 
case Success(o) => Success(f(o)) 
case f: Failure => f 
} 
}! 
}! 
object Reader { 
def map2[I, O, O1, O2](r1: Reader[I, O1], r2: Reader[I, O2])(f: (O1, 
O2) => O): Reader[I, O] = Reader { s: I => 
(r1.p(s), r2.p(s)) match { 
case (Success(s1), Success(s2)) => Success(f(s1, s2)) 
case (Success(_), Failure(e)) => Failure(e) 
case (Failure(e), Success(_)) => Failure(e) 
case (Failure(e1), Failure(e2)) => Failure(e1 ++ e2) 
} 
}
tag: step5 
Introducing Reader[I, 0] 
object Reader {! 
implicit def readerIsAFunctor[I] = new Functor[({type λ[A] = 
Reader[I, A]})#λ] { 
override def fmap[A, B](m: Reader[I, A], f: (A) => B) = m.map(f) 
} 
implicit def readerIsAnApplicative[I] = new Applicative[({type λ[A] = 
Reader[I, A]})#λ] { 
override def pure[A](a: A) = Reader { _ => Success(a) } 
override def apply[A, B](mf: Reader[I, A => B], ma: Reader[I, A]) = 
map2(mf, ma)((f, a) => f(a)) 
override def map[A, B](m: Reader[I, A], f: (A) => B) = m.map(f) 
}
tag: step5 
What are Type lambdas ? 
If we go back to Applicative definition, we can see that it’s 
an Higher Kinded type (same with Functor) : 
! 
trait Applicative[M[_]] { … } // Applicative accept 
parameter M that take itself any type as parameter! 
! 
Our problem is that Reader[I, 0] takes two parameters 
but Applicative[M[_]] accept types M with only one 
parameter. We use Type Lambdas to resolve this issue: 
! 
new Applicative[({type λ[A] = Reader[I, A]})#λ]!
tag: step5 
Go back to Reader[I, 0] 
object Reader {! 
implicit def readerIsAFunctor[I] = new Functor[({type λ[A] = Reader[I, 
A]})#λ] { 
override def fmap[A, B](m: Reader[I, A], f: A => B) = m.map(f) 
} 
implicit def readerIsAnApplicative[I] = new Applicative[({type λ[A] = 
Reader[I, A]})#λ] { 
override def pure[A](a: A) = Reader { _ => Success(a) } 
override def apply[A, B](mf: Reader[I, A => B], ma: Reader[I, A]) = 
map2(mf, ma)((f, a) => f(a)) 
override def map[A, B](m: Reader[I, A], f: A => B) = m.map(f) 
}
tag: step5 
Go back to Reader[I, 0] 
object Reader {! 
import scala.language.implicitConversions 
// Here we help the compiler a bit. Thanks @skaalf (Julien Tournay) ! 
// and https://github.com/jto/validation 
implicit def fcbReads[I] = functionalCanBuildApplicative[({type λ[A] = 
Reader[I, A]})#λ] 
implicit def fboReads[I, A](a: Reader[I, A])(implicit fcb: 
FunctionalCanBuild[({type λ[x] = Reader[I, x]})#λ]) = new 
FunctionalBuilderOps[({type λ[x] = Reader[I, x]})#λ, A](a)(fcb)
Converter[I, T] 
tag: step5 
trait Converter[I, T] { 
def convert(p: I => Result[String]): Reader[I, T] 
}! 
object Converter { 
implicit def stringConverter[I] = new Converter[I, String] { 
override def convert(p: I => Result[String]) = Reader[I, String](p) 
}! 
! 
implicit def dateConverter[I](implicit dtFormat: DateFormat) = new 
Converter[I, Date] { 
override def convert(p: I => Result[String]) = Reader[I, Date] { s: 
I => 
p(s) match { 
case Success(dt) => try { ! 
Success(dtFormat.parse(dt)) 
} catch { case e: ParseException => Failure(s"...") } 
case f: Failure => f 
}}}
Picker[I] 
tag: step5 
case class Picker[I](p: I => Result[String]) { 
def as[T](implicit c: Converter[I, T]): Reader[I, T] = c.convert(p) 
} 
object CsvPicker { 
def apply[T](i: Int): Picker[List[String]] = Picker { elems: 
List[String] => 
if (i > 0 && elems.size > i) Success(elems(i).trim) 
else Failure(s"No column ${i} found in ${elems.mkString(";")}") 
}} 
object XmlPicker { 
def apply[T](query: Elem => NodeSeq): Picker[Elem] = Picker { elem: 
Elem => 
try { 
Success(query(elem).text) 
} catch { 
case e: Exception => Failure(e.getMessage) 
}}}!
Usage sample 
tag: step5 
import play.api.libs.functional.syntax._ 
import Reader._! 
import Converter._! 
implicit val dF = new SimpleDateFormat("dd/MM/yyyy")! 
val xml = XML.loadString(""" 
<company name="Dupont and Co"> 
<owner> 
<person firstname="jean" lastname="dupont" birthdate="11/03/1987"/> 
</owner> 
</company>""")! 
! 
val r = ( 
XmlPicker(_  "person"  "@firstname").as[String] and 
XmlPicker(_  "person"  "@lastname").as[String] and 
XmlPicker(_  "person"  "@birthdate").as[Date] 
)(Person)! 
r(xml) === Success(Person("jean", "dupont", dF.parse("11/03/1987")))
tag: step6 
Adding combinators 
• We now add new abilities to Reader 
• We especially want a method to validate content
tag: step6 
Adding combinators 
case class Reader[I, O](p: I => Result[O]) {! 
! 
def flatMap[T](f: O => Reader[I, T]): Reader[I, T] = Reader { s: I => 
p(s) match { 
case Success(o) => f(o)(s) 
case f: Failure => f 
} 
} 
def verify(f: O => Result[O]): Reader[I, O] = flatMap { o: O => 
Reader( _ => f(o)) }! 
}
Usage sample 
tag: step6 
val r: Reader[String, String] = Reader { Success(_) } 
r.verify { x => if (x == "OK") Success(x) else Failure("KO") }("OK") 
=== Success("OK")
Conclusion 
• We have created a simple and powerful DSL for 
processing CSV and XML content 
• This DSL give us ability to Pick data, transform and 
verify it and also accumulate encountered errors 
• We have seen that making Reader an instance of the 
Applicative Functor Type Class add it new capabilities 
• Using ad-hoc polymorphism using Type Classes gives 
us ability to extends Reader without altering it
Follow-up 
• Type Classes are defined by functions that must be 
implemented with regards to their laws (left/right 
identity …) 
• Proving the correctness of Type Class Laws can 
be a bit tricky we usual approach 
• I will introduce the framework ScalaCheck at 
scala.io 2014, and show how to test them
Follow-up 
• In the roadmap that has been announced by 
@typesafe (http://scala-lang.org/news/roadmap-next), 
it seems that Scala 2.14 (aka « Don 
Giovanni ») will clean up lambda types syntax

More Related Content

PDF
Introducing Monads and State Monad at PSUG
PDF
Playing with State Monad
PPTX
Practical scalaz
KEY
Scalaz
KEY
Deriving Scalaz
PDF
Functor, Apply, Applicative And Monad
PDF
The Death of Final Tagless
PDF
Scalaz 8: A Whole New Game
Introducing Monads and State Monad at PSUG
Playing with State Monad
Practical scalaz
Scalaz
Deriving Scalaz
Functor, Apply, Applicative And Monad
The Death of Final Tagless
Scalaz 8: A Whole New Game

What's hot (20)

PDF
Principled Error Handling with FP
PDF
Oh, All the things you'll traverse
PDF
A Prelude of Purity: Scaling Back ZIO
PDF
Monad Transformers In The Wild
PDF
First-Class Patterns
PDF
Testing in the World of Functional Programming
PDF
Refactoring Functional Type Classes
PDF
Side by Side - Scala and Java Adaptations of Martin Fowler’s Javascript Refac...
PDF
Monoids, monoids, monoids
PDF
Kotlin Basics - Apalon Kotlin Sprint Part 2
PDF
Post-Free: Life After Free Monads
PDF
Quark: A Purely-Functional Scala DSL for Data Processing & Analytics
PDF
One Monad to Rule Them All
PDF
Why The Free Monad isn't Free
PDF
Exploring type level programming in Scala
PDF
Exploring ZIO Prelude: The game changer for typeclasses in Scala
PDF
Atomically { Delete Your Actors }
PDF
Advanced Tagless Final - Saying Farewell to Free
PDF
Blazing Fast, Pure Effects without Monads — LambdaConf 2018
PDF
Hammurabi
Principled Error Handling with FP
Oh, All the things you'll traverse
A Prelude of Purity: Scaling Back ZIO
Monad Transformers In The Wild
First-Class Patterns
Testing in the World of Functional Programming
Refactoring Functional Type Classes
Side by Side - Scala and Java Adaptations of Martin Fowler’s Javascript Refac...
Monoids, monoids, monoids
Kotlin Basics - Apalon Kotlin Sprint Part 2
Post-Free: Life After Free Monads
Quark: A Purely-Functional Scala DSL for Data Processing & Analytics
One Monad to Rule Them All
Why The Free Monad isn't Free
Exploring type level programming in Scala
Exploring ZIO Prelude: The game changer for typeclasses in Scala
Atomically { Delete Your Actors }
Advanced Tagless Final - Saying Farewell to Free
Blazing Fast, Pure Effects without Monads — LambdaConf 2018
Hammurabi
Ad

Viewers also liked (20)

PPTX
stenen aja
PPT
Eidn 6-simobe
PDF
The BIG Event - Canadian Mining Expo
PPT
Еремурус про екоосвіту_GreenDrinks 25.12.2012
PPTX
Assignment 11 similar products, conventions and channel- my part
PPTX
11 Telephone Phrases
PPT
Fobba 2011 keynote handout
PPTX
Projects
PPTX
Planning for draft 3
PDF
2012 aug 28 issue 60
PDF
10remarkableentrepreneurshipthoughts 131008125313-phpapp01
PDF
Essays on economic analysis of competition law: theory and practice
PDF
36kr no.94
PPT
About iv network hawaii 2
PDF
10remarkableentrepreneurshipthoughts 131008125313-phpapp01
PPTX
Business Model Test
PDF
Corporate Presentation - BMO 2015 Global Metals & Mining Conference
PPTX
Media studies mark_scheme
DOCX
Tabel analis resiko hiradc peledakan tambang
DOCX
Verb to be
stenen aja
Eidn 6-simobe
The BIG Event - Canadian Mining Expo
Еремурус про екоосвіту_GreenDrinks 25.12.2012
Assignment 11 similar products, conventions and channel- my part
11 Telephone Phrases
Fobba 2011 keynote handout
Projects
Planning for draft 3
2012 aug 28 issue 60
10remarkableentrepreneurshipthoughts 131008125313-phpapp01
Essays on economic analysis of competition law: theory and practice
36kr no.94
About iv network hawaii 2
10remarkableentrepreneurshipthoughts 131008125313-phpapp01
Business Model Test
Corporate Presentation - BMO 2015 Global Metals & Mining Conference
Media studies mark_scheme
Tabel analis resiko hiradc peledakan tambang
Verb to be
Ad

Similar to Writing DSL with Applicative Functors (20)

PPT
Extractors & Implicit conversions
PDF
Applicative Functor - Part 3
PDF
Generic Functional Programming with Type Classes
PDF
Scala Validation with Functional Programming
PDF
Scala or functional programming from a python developer's perspective
PDF
TI1220 Lecture 9: Parsing & interpretation
PDF
여자개발자모임터 6주년 개발 세미나 - Scala Language
PDF
Meet scala
PDF
From OOP To FP Through A Practical Case
PDF
Scala for Java Developers
PPTX
Improving Correctness with Types
PDF
Talk - Query monad
PDF
Scala Paradigms
PPTX
Practically Functional
PDF
PDF
Introduction To Scala
PDF
Practical cats
PDF
Fp in scala part 2
PPTX
Monads and friends demystified
PPT
Scala presentation by Aleksandar Prokopec
Extractors & Implicit conversions
Applicative Functor - Part 3
Generic Functional Programming with Type Classes
Scala Validation with Functional Programming
Scala or functional programming from a python developer's perspective
TI1220 Lecture 9: Parsing & interpretation
여자개발자모임터 6주년 개발 세미나 - Scala Language
Meet scala
From OOP To FP Through A Practical Case
Scala for Java Developers
Improving Correctness with Types
Talk - Query monad
Scala Paradigms
Practically Functional
Introduction To Scala
Practical cats
Fp in scala part 2
Monads and friends demystified
Scala presentation by Aleksandar Prokopec

Recently uploaded (20)

PPTX
Machine Learning_overview_presentation.pptx
PDF
Approach and Philosophy of On baking technology
PDF
Profit Center Accounting in SAP S/4HANA, S4F28 Col11
PDF
Diabetes mellitus diagnosis method based random forest with bat algorithm
PDF
MIND Revenue Release Quarter 2 2025 Press Release
PPT
Teaching material agriculture food technology
PDF
Encapsulation_ Review paper, used for researhc scholars
PDF
Machine learning based COVID-19 study performance prediction
PPTX
Cloud computing and distributed systems.
PDF
Assigned Numbers - 2025 - Bluetooth® Document
PPTX
Digital-Transformation-Roadmap-for-Companies.pptx
PPTX
ACSFv1EN-58255 AWS Academy Cloud Security Foundations.pptx
PDF
Unlocking AI with Model Context Protocol (MCP)
PDF
Network Security Unit 5.pdf for BCA BBA.
PDF
Encapsulation theory and applications.pdf
PDF
Blue Purple Modern Animated Computer Science Presentation.pdf.pdf
PDF
Per capita expenditure prediction using model stacking based on satellite ima...
PDF
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
PPTX
Spectroscopy.pptx food analysis technology
PPTX
Big Data Technologies - Introduction.pptx
Machine Learning_overview_presentation.pptx
Approach and Philosophy of On baking technology
Profit Center Accounting in SAP S/4HANA, S4F28 Col11
Diabetes mellitus diagnosis method based random forest with bat algorithm
MIND Revenue Release Quarter 2 2025 Press Release
Teaching material agriculture food technology
Encapsulation_ Review paper, used for researhc scholars
Machine learning based COVID-19 study performance prediction
Cloud computing and distributed systems.
Assigned Numbers - 2025 - Bluetooth® Document
Digital-Transformation-Roadmap-for-Companies.pptx
ACSFv1EN-58255 AWS Academy Cloud Security Foundations.pptx
Unlocking AI with Model Context Protocol (MCP)
Network Security Unit 5.pdf for BCA BBA.
Encapsulation theory and applications.pdf
Blue Purple Modern Animated Computer Science Presentation.pdf.pdf
Per capita expenditure prediction using model stacking based on satellite ima...
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
Spectroscopy.pptx food analysis technology
Big Data Technologies - Introduction.pptx

Writing DSL with Applicative Functors

  • 1. Writing DSL with Applicative Functors David Galichet Freelance functional programmer ! twitter: @dgalichet
  • 2. Content normalization • We want to parse heterogeneous data formats (CSV, XML …) and transform them to a pivot format (Scala object in our case) • The transformation should be described as a DSL • This DSL must be simple to use, and enable any kinds of data transformations or verifications
  • 3. Expected DSL format val reader = ( ! Pick(0).as[String].map(_.capitalize) and ! Pick(1).as[Date].check(_.after(now())) ! ).reduce(FutureEvent)! ! reader("PSUG; 21/08/2014") // returns Success(FutureEvent("PSUG", date)) Inspired by Play2 Json API
  • 4. tag: step1 Conventions & material • Code is available on Github : https://github.com/ dgalichet/PsugDSLWritingWithApplicative • Code revision is define on the top of any slides including source code (just checkout the specified tag)
  • 5. tag: step1 Reading single entry • We have a CSV line and we want to read one column • We will introduce several abstractions: • Picker: fetch a data from CSV or XML • Result: either a Success or a Failure • Converter: convert value from Picker to Reader • Reader: container with methods to process its content
  • 6. tag: step1 Introducing Picker case class Picker(p: String => Result[String]) {! def as[T](implicit c: Converter[T]): Reader[T] = c.convert(p)! }! ! object CsvPicker {! def apply[T](i: Int)(implicit separator: Char): Picker = Picker { s: String =>! val elems = s.trim.split(separator)! if (i > 0 && elems.size > i) Success(elems(i).trim)! else Failure(s"No column ${i} for ${s}")! }! }
  • 7. tag: step1 Introducing Picker case class Picker(p: String => Result[String]) {! def as[T](implicit c: Converter[T]): Reader[T] = c.convert(p)! }! ! object CsvPicker {! def apply[T](i: Int)(implicit separator: Char): Picker = Picker { s: String =>! val elems = s.trim.split(separator)! if (i > 0 && elems.size > i) Success(elems(i).trim)! else Failure(s"No column ${i} for ${s}")! }! } Picker wraps a function from String to Result
  • 8. The Result tag: step1 sealed trait Result[+T] case class Success[T](t: T) extends Result[T] case class Failure(error: String) extends Result[Nothing]!
  • 9. The Converter tag: step1 trait Converter[T] { def convert(p: String => Result[String]): Reader[T] }! ! object Converter {! implicit val string2StringConverter = new Converter[String] {! override def convert(p: String => Result[String]) = Reader[String] (p)! // See code on Github for more converters! }
  • 10. The Converter tag: step1 trait Converter[T] { def convert(p: String => Result[String]): Reader[T] }! ! Convert the content of the Picker to a Reader ! object Converter {! implicit val string2StringConverter = new Converter[String] {! override def convert(p: String => Result[String]) = Reader[String] (p)! // See code on Github for more converters! }
  • 11. The Reader tag: step1 case class Reader[O](p: String => Result[O]) { def apply(s: String): Result[O] = p(s) } A Reader doesn’t contain a value but a process to transform original data (CSV line or XML) to a Result
  • 12. Usage sample tag: step1 import Converter._ // import implicit converters! implicit val separator = ‘;’! ! CsvPicker(1).as[String].apply("foo;bar") === "bar"
  • 13. tag: step2 Enhancing the Reader • The first defines a very simple Reader. We must add a method to combine two instances of Reader • We will also enhance Failure to store multiple error messages
  • 14. tag: step2 Enhancing the Reader case class Reader[O](p: String => Result[O]) {! def apply(s: String): Result[O] = p(s)! ! def and[O2](r2: Reader[O2]): Reader[(O, O2)] = Reader { s: String =>! (p(s), r2.p(s)) match {! case (Success(s1), Success(s2)) => Success((s1, s2))! case (Success(_), Failure(f)) => Failure(f)! case (Failure(f), Success(_)) => Failure(f)! case (Failure(f1), Failure(f2)) => Failure(f1 ++ f2)! }! }! def map[T](f: O => T): Reader[T] = Reader { s: String =>! p(s) match {! case Success(o) => Success(f(o))! case f: Failure => f! }! }! def reduce[T] = map[T] _ // alias for map! }
  • 15. tag: step2 Enhancing Result type sealed trait Result[+T]! case class Success[T](t: T) extends Result[T]! case class Failure(error: NonEmptyList[String]) extends Result[Nothing]! ! object Failure {! def apply(s: String): Failure = Failure(NEL(s))! }! ! case class NonEmptyList[T](head: T, tail: List[T]) { def toList = head::tail def ++(l2: NonEmptyList[T]): NonEmptyList[T] = NonEmptyList(head, tail ++ l2.toList) } object NEL { def apply[T](h: T, t: T*) = NonEmptyList(h, t.toList) }
  • 16. Usage sample tag: step2 implicit val separator = ';' implicit val dtFormatter = new SimpleDateFormat("dd/MM/yyyy") import Converter.string2StringConverter import Converter.string2DateConverter! ! val reader = ( CsvPicker(1).as[String] and CsvPicker(2).as[Date] ).reduce { case (n, d) => FutureEvent(n, d) }! reader("foo;bar;12/10/2014") === Success(FutureEvent("bar", dtFormatter.parse("12/10/2014")))! ! case class FutureEvent(name: String, dt: Date)
  • 17. tag: step2 Usability problem • The use of reduce (or map) method to transform a Reader[(0, 02)] into an instance of Reader[FutureEvent] for example is quite verbose • This will be even more verbose for instances of Reader[(0, (02, 03))] • We want the API to automatically bind tuple elements to a constructor as we can encounter in Play2 Json API
  • 18. tag: step3 Applicative functors • To tackle our problem, we will use Applicative Functors and play2 functional library (and especially FunctionalBuilder) • This approach is inspired by @sadache (Sadek Drobi) article https://gist.github.com/sadache/ 3646092 • An Applicative Functor is a Type Class relying on ad-hoc polymorphism to extends a Class with some properties • Play2 functional library (or Scalaz) provides mechanism to compose Applicatives in a smart way
  • 19. tag: step3 Applicative functors M is an Applicative Functor if there exists the following methods : def pure[A](a: A): M[A] def map[A, B](m: M[A], f: A => B): M[B] def apply[A, B](mf: M[A => B], ma: M[A]): M[B]! with the following Laws : • Identity: apply(pure(identity), ma) === ma where ma is an Applicative M[A] • Homomorphism: apply(pure(f), pure(a)) === pure(f(a)) where f: A => B and a an instance of A • Interchange: mf if an instance of M[A => B] apply(mf, pure(a)) === apply(pure {(g: A => B) => g(a)}, mf)! • Composition: map(ma, f) === apply(pure(f), ma)
  • 20. tag: step3 Applicative functors trait Applicative[M[_]] { def pure[A](a: A): M[A] def map[A, B](m: M[A], f: A => B): M[B] def apply[A, B](mf: M[A => B], ma: M[A]): M[B] } Applicative is an Higher Kinded type (parameterized with M that take a single type parameter)
  • 21. tag: step3 Reader is an Applicative case class Reader[O](p: String => Result[O]) { def apply(s: String): Result[O] = p(s)! def map[T](f: O => T): Reader[T] = Reader { s: String => p(s) match { case Success(o) => Success(f(o)) case f: Failure => f } }! }! object Reader { def map2[O, O1, O2](r1: Reader[O1], r2: Reader[O2])(f: (O1, O2) => O): Reader[O] = Reader { s: String => (r1.p(s), r2.p(s)) match { case (Success(s1), Success(s2)) => Success(f(s1, s2)) case (Success(_), Failure(e)) => Failure(e) case (Failure(e), Success(_)) => Failure(e) case (Failure(e1), Failure(e2)) => Failure(e1 ++ e2) } } …! }
  • 22. tag: step3 Reader is an Applicative object Reader { … // map2 implicit val readerIsAFunctor: Functor[Reader] = new Functor[Reader] { override def fmap[A, B](m: Reader[A], f: (A) => B) = m.map(f) } implicit val readerIsAnApplicative: Applicative[Reader] = new Applicative[Reader] { override def pure[A](a: A) = Reader { _ => Success(a) } override def apply[A, B](mf: Reader[A => B], ma: Reader[A]) = map2(mf, ma)((f, a) => f(a)) override def map[A, B](m: Reader[A], f: A => B) = m.map(f) } }
  • 23. Usage sample tag: step3 import Converter.string2StringConverter import Converter.string2DateConverter! import play.api.libs.functional.syntax._ import Reader.readerIsAnApplicative! ! implicit val separator = ';' implicit val dtFormatter = new SimpleDateFormat("dd/MM/yyyy")! ! val reader = ( CsvPicker(1).as[String] and CsvPicker(2).as[Date] )(FutureEvent) // here we use CanBuild2.apply reader("foo;bar;12/10/2014") === Success(FutureEvent("bar", dtFormatter.parse("12/10/2014")))!
  • 24. Usage sample tag: step3 (errors accumulation) import Converter.string2StringConverter import Converter.string2DateConverter! import play.api.libs.functional.syntax._ import Reader.readerIsAnApplicative! ! implicit val separator = ';' implicit val dtFormatter = new SimpleDateFormat("dd/MM/yyyy")! ! val reader = ( CsvPicker(1).as[Int] and CsvPicker(2).as[Date] )((_, _)) reader(List("foo", "not a number", "not a date")) === Failure(NEL(! "Unable to format 'not a number' as Int", ! "Unable to format 'not a date' as Date"))!
  • 25. Benefits tag: step3 • Making Reader an Applicative Functor give ability to combine efficiently instances of Reader • Due to Applicative properties, we still accumulate errors • Play2 functional builder give us a clean syntax to define our DSL
  • 26. tag: step4 Introducing XML Picker case class Picker(p: String => Result[String]) { def as[T](implicit c: Converter[T]): Reader[T] = c.convert(p) }! ! object XmlPicker { def apply[T](query: Elem => NodeSeq): Picker = Picker { s: String => try { val xml = XML.loadString(s) Success(query(xml).text) } catch { case e: Exception => Failure(e.getMessage) } } }!
  • 27. Usage sample tag: step4 import play.api.libs.functional.syntax._ import Reader.readerIsAnApplicative! import Converter._ implicit val dF = new SimpleDateFormat("dd/MM/yyyy")! val xml = """ <company name="Dupont and Co"> <owner> <person firstname="jean" lastname="dupont" birthdate="11/03/1987"/> </owner> </company>""" val r = ( XmlPicker(_ "person" "@firstname").as[String] and XmlPicker(_ "person" "@lastname").as[String] and XmlPicker(_ "person" "@birthdate").as[Date] )(Person)! r(xml) === Success(Person("jean","dupont",dF.parse("11/03/1987"))) case class Person(firstname: String, lastname: String, birthDt: Date)
  • 28. tag: step4 Implementation problem • The Reader[O] takes a type argument for the output. The input is always a String • With this implementation, an XML content will be parsed (with XML.load) as many times as we use XmlPicker. This will cause unnecessary overhead • We will have the same issue (with lower overhead) with our CsvPicker
  • 29. tag: step5 Introducing Reader[I, 0] To resolve this problem, we will modify Reader to take a type parameter for the input
  • 30. tag: step5 Introducing Reader[I, 0] case class Reader[I, O](p: I => Result[O]) { def apply(s: I): Result[O] = p(s) def map[T](f: O => T): Reader[I, T] = Reader { s: I => p(s) match { case Success(o) => Success(f(o)) case f: Failure => f } }! }! object Reader { def map2[I, O, O1, O2](r1: Reader[I, O1], r2: Reader[I, O2])(f: (O1, O2) => O): Reader[I, O] = Reader { s: I => (r1.p(s), r2.p(s)) match { case (Success(s1), Success(s2)) => Success(f(s1, s2)) case (Success(_), Failure(e)) => Failure(e) case (Failure(e), Success(_)) => Failure(e) case (Failure(e1), Failure(e2)) => Failure(e1 ++ e2) } }
  • 31. tag: step5 Introducing Reader[I, 0] object Reader {! implicit def readerIsAFunctor[I] = new Functor[({type λ[A] = Reader[I, A]})#λ] { override def fmap[A, B](m: Reader[I, A], f: (A) => B) = m.map(f) } implicit def readerIsAnApplicative[I] = new Applicative[({type λ[A] = Reader[I, A]})#λ] { override def pure[A](a: A) = Reader { _ => Success(a) } override def apply[A, B](mf: Reader[I, A => B], ma: Reader[I, A]) = map2(mf, ma)((f, a) => f(a)) override def map[A, B](m: Reader[I, A], f: (A) => B) = m.map(f) }
  • 32. tag: step5 What are Type lambdas ? If we go back to Applicative definition, we can see that it’s an Higher Kinded type (same with Functor) : ! trait Applicative[M[_]] { … } // Applicative accept parameter M that take itself any type as parameter! ! Our problem is that Reader[I, 0] takes two parameters but Applicative[M[_]] accept types M with only one parameter. We use Type Lambdas to resolve this issue: ! new Applicative[({type λ[A] = Reader[I, A]})#λ]!
  • 33. tag: step5 Go back to Reader[I, 0] object Reader {! implicit def readerIsAFunctor[I] = new Functor[({type λ[A] = Reader[I, A]})#λ] { override def fmap[A, B](m: Reader[I, A], f: A => B) = m.map(f) } implicit def readerIsAnApplicative[I] = new Applicative[({type λ[A] = Reader[I, A]})#λ] { override def pure[A](a: A) = Reader { _ => Success(a) } override def apply[A, B](mf: Reader[I, A => B], ma: Reader[I, A]) = map2(mf, ma)((f, a) => f(a)) override def map[A, B](m: Reader[I, A], f: A => B) = m.map(f) }
  • 34. tag: step5 Go back to Reader[I, 0] object Reader {! import scala.language.implicitConversions // Here we help the compiler a bit. Thanks @skaalf (Julien Tournay) ! // and https://github.com/jto/validation implicit def fcbReads[I] = functionalCanBuildApplicative[({type λ[A] = Reader[I, A]})#λ] implicit def fboReads[I, A](a: Reader[I, A])(implicit fcb: FunctionalCanBuild[({type λ[x] = Reader[I, x]})#λ]) = new FunctionalBuilderOps[({type λ[x] = Reader[I, x]})#λ, A](a)(fcb)
  • 35. Converter[I, T] tag: step5 trait Converter[I, T] { def convert(p: I => Result[String]): Reader[I, T] }! object Converter { implicit def stringConverter[I] = new Converter[I, String] { override def convert(p: I => Result[String]) = Reader[I, String](p) }! ! implicit def dateConverter[I](implicit dtFormat: DateFormat) = new Converter[I, Date] { override def convert(p: I => Result[String]) = Reader[I, Date] { s: I => p(s) match { case Success(dt) => try { ! Success(dtFormat.parse(dt)) } catch { case e: ParseException => Failure(s"...") } case f: Failure => f }}}
  • 36. Picker[I] tag: step5 case class Picker[I](p: I => Result[String]) { def as[T](implicit c: Converter[I, T]): Reader[I, T] = c.convert(p) } object CsvPicker { def apply[T](i: Int): Picker[List[String]] = Picker { elems: List[String] => if (i > 0 && elems.size > i) Success(elems(i).trim) else Failure(s"No column ${i} found in ${elems.mkString(";")}") }} object XmlPicker { def apply[T](query: Elem => NodeSeq): Picker[Elem] = Picker { elem: Elem => try { Success(query(elem).text) } catch { case e: Exception => Failure(e.getMessage) }}}!
  • 37. Usage sample tag: step5 import play.api.libs.functional.syntax._ import Reader._! import Converter._! implicit val dF = new SimpleDateFormat("dd/MM/yyyy")! val xml = XML.loadString(""" <company name="Dupont and Co"> <owner> <person firstname="jean" lastname="dupont" birthdate="11/03/1987"/> </owner> </company>""")! ! val r = ( XmlPicker(_ "person" "@firstname").as[String] and XmlPicker(_ "person" "@lastname").as[String] and XmlPicker(_ "person" "@birthdate").as[Date] )(Person)! r(xml) === Success(Person("jean", "dupont", dF.parse("11/03/1987")))
  • 38. tag: step6 Adding combinators • We now add new abilities to Reader • We especially want a method to validate content
  • 39. tag: step6 Adding combinators case class Reader[I, O](p: I => Result[O]) {! ! def flatMap[T](f: O => Reader[I, T]): Reader[I, T] = Reader { s: I => p(s) match { case Success(o) => f(o)(s) case f: Failure => f } } def verify(f: O => Result[O]): Reader[I, O] = flatMap { o: O => Reader( _ => f(o)) }! }
  • 40. Usage sample tag: step6 val r: Reader[String, String] = Reader { Success(_) } r.verify { x => if (x == "OK") Success(x) else Failure("KO") }("OK") === Success("OK")
  • 41. Conclusion • We have created a simple and powerful DSL for processing CSV and XML content • This DSL give us ability to Pick data, transform and verify it and also accumulate encountered errors • We have seen that making Reader an instance of the Applicative Functor Type Class add it new capabilities • Using ad-hoc polymorphism using Type Classes gives us ability to extends Reader without altering it
  • 42. Follow-up • Type Classes are defined by functions that must be implemented with regards to their laws (left/right identity …) • Proving the correctness of Type Class Laws can be a bit tricky we usual approach • I will introduce the framework ScalaCheck at scala.io 2014, and show how to test them
  • 43. Follow-up • In the roadmap that has been announced by @typesafe (http://scala-lang.org/news/roadmap-next), it seems that Scala 2.14 (aka « Don Giovanni ») will clean up lambda types syntax