SlideShare ist ein Scribd-Unternehmen logo
1 von 43
Downloaden Sie, um offline zu lesen
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

Weitere ähnliche Inhalte

Was ist angesagt?

Monad Transformers In The Wild
Monad Transformers In The WildMonad Transformers In The Wild
Monad Transformers In The Wild
StackMob Inc
 
Refactoring Functional Type Classes
Refactoring Functional Type ClassesRefactoring Functional Type Classes
Refactoring Functional Type Classes
John De Goes
 
Post-Free: Life After Free Monads
Post-Free: Life After Free MonadsPost-Free: Life After Free Monads
Post-Free: Life After Free Monads
John De Goes
 

Was ist angesagt? (20)

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

Andere mochten auch

Assignment 11 similar products, conventions and channel- my part
Assignment 11  similar products, conventions and channel- my partAssignment 11  similar products, conventions and channel- my part
Assignment 11 similar products, conventions and channel- my part
debbie14
 
Planning for draft 3
Planning for draft 3Planning for draft 3
Planning for draft 3
debbie14
 
10remarkableentrepreneurshipthoughts 131008125313-phpapp01
10remarkableentrepreneurshipthoughts 131008125313-phpapp0110remarkableentrepreneurshipthoughts 131008125313-phpapp01
10remarkableentrepreneurshipthoughts 131008125313-phpapp01
Gina Gu
 
36kr no.94
36kr no.9436kr no.94
36kr no.94
Gina Gu
 
10remarkableentrepreneurshipthoughts 131008125313-phpapp01
10remarkableentrepreneurshipthoughts 131008125313-phpapp0110remarkableentrepreneurshipthoughts 131008125313-phpapp01
10remarkableentrepreneurshipthoughts 131008125313-phpapp01
Gina Gu
 
Media studies mark_scheme
Media studies mark_schemeMedia studies mark_scheme
Media studies mark_scheme
debbie14
 
Tabel analis resiko hiradc peledakan tambang
Tabel analis resiko hiradc peledakan tambangTabel analis resiko hiradc peledakan tambang
Tabel analis resiko hiradc peledakan tambang
Sylvester Saragih
 

Andere mochten auch (20)

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

Ähnlich wie Writing DSL with Applicative Functors

Presentation 5th
Presentation 5thPresentation 5th
Presentation 5th
Connex
 

Ähnlich wie Writing DSL with Applicative Functors (20)

PyData NYC 2019
PyData NYC 2019PyData NYC 2019
PyData NYC 2019
 
Go 1.10 Release Party - PDX Go
Go 1.10 Release Party - PDX GoGo 1.10 Release Party - PDX Go
Go 1.10 Release Party - PDX Go
 
Monads in Swift
Monads in SwiftMonads in Swift
Monads in Swift
 
The Swift Compiler and Standard Library
The Swift Compiler and Standard LibraryThe Swift Compiler and Standard Library
The Swift Compiler and Standard Library
 
A Recovering Java Developer Learns to Go
A Recovering Java Developer Learns to GoA Recovering Java Developer Learns to Go
A Recovering Java Developer Learns to Go
 
"Scala in Goozy", Alexey Zlobin
"Scala in Goozy", Alexey Zlobin "Scala in Goozy", Alexey Zlobin
"Scala in Goozy", Alexey Zlobin
 
Introduction to Client-Side Javascript
Introduction to Client-Side JavascriptIntroduction to Client-Side Javascript
Introduction to Client-Side Javascript
 
Angular2 for Beginners
Angular2 for BeginnersAngular2 for Beginners
Angular2 for Beginners
 
Domain Specific Languages In Scala Duse3
Domain Specific Languages In Scala Duse3Domain Specific Languages In Scala Duse3
Domain Specific Languages In Scala Duse3
 
Replace OutputIterator and Extend Range
Replace OutputIterator and Extend RangeReplace OutputIterator and Extend Range
Replace OutputIterator and Extend Range
 
20230721_OKC_Meetup_MuleSoft.pptx
20230721_OKC_Meetup_MuleSoft.pptx20230721_OKC_Meetup_MuleSoft.pptx
20230721_OKC_Meetup_MuleSoft.pptx
 
Scala in Places API
Scala in Places APIScala in Places API
Scala in Places API
 
Introduction to Scalding and Monoids
Introduction to Scalding and MonoidsIntroduction to Scalding and Monoids
Introduction to Scalding and Monoids
 
Java gets a closure
Java gets a closureJava gets a closure
Java gets a closure
 
Clojure And Swing
Clojure And SwingClojure And Swing
Clojure And Swing
 
The Java Fx Platform – A Java Developer’S Guide
The Java Fx Platform – A Java Developer’S GuideThe Java Fx Platform – A Java Developer’S Guide
The Java Fx Platform – A Java Developer’S Guide
 
Scala 2 + 2 > 4
Scala 2 + 2 > 4Scala 2 + 2 > 4
Scala 2 + 2 > 4
 
Value Objects, Full Throttle (to be updated for spring TC39 meetings)
Value Objects, Full Throttle (to be updated for spring TC39 meetings)Value Objects, Full Throttle (to be updated for spring TC39 meetings)
Value Objects, Full Throttle (to be updated for spring TC39 meetings)
 
Dartprogramming
DartprogrammingDartprogramming
Dartprogramming
 
Presentation 5th
Presentation 5thPresentation 5th
Presentation 5th
 

Kürzlich hochgeladen

Architecting Cloud Native Applications
Architecting Cloud Native ApplicationsArchitecting Cloud Native Applications
Architecting Cloud Native Applications
WSO2
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Safe Software
 
Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Finding Java's Hidden Performance Traps @ DevoxxUK 2024Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Victor Rentea
 
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
?#DUbAI#??##{{(☎️+971_581248768%)**%*]'#abortion pills for sale in dubai@
 
Why Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire businessWhy Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire business
panagenda
 

Kürzlich hochgeladen (20)

Architecting Cloud Native Applications
Architecting Cloud Native ApplicationsArchitecting Cloud Native Applications
Architecting Cloud Native Applications
 
[BuildWithAI] Introduction to Gemini.pdf
[BuildWithAI] Introduction to Gemini.pdf[BuildWithAI] Introduction to Gemini.pdf
[BuildWithAI] Introduction to Gemini.pdf
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
 
DBX First Quarter 2024 Investor Presentation
DBX First Quarter 2024 Investor PresentationDBX First Quarter 2024 Investor Presentation
DBX First Quarter 2024 Investor Presentation
 
Elevate Developer Efficiency & build GenAI Application with Amazon Q​
Elevate Developer Efficiency & build GenAI Application with Amazon Q​Elevate Developer Efficiency & build GenAI Application with Amazon Q​
Elevate Developer Efficiency & build GenAI Application with Amazon Q​
 
CNIC Information System with Pakdata Cf In Pakistan
CNIC Information System with Pakdata Cf In PakistanCNIC Information System with Pakdata Cf In Pakistan
CNIC Information System with Pakdata Cf In Pakistan
 
Introduction to Multilingual Retrieval Augmented Generation (RAG)
Introduction to Multilingual Retrieval Augmented Generation (RAG)Introduction to Multilingual Retrieval Augmented Generation (RAG)
Introduction to Multilingual Retrieval Augmented Generation (RAG)
 
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
 
Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Finding Java's Hidden Performance Traps @ DevoxxUK 2024Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Finding Java's Hidden Performance Traps @ DevoxxUK 2024
 
Understanding the FAA Part 107 License ..
Understanding the FAA Part 107 License ..Understanding the FAA Part 107 License ..
Understanding the FAA Part 107 License ..
 
MINDCTI Revenue Release Quarter One 2024
MINDCTI Revenue Release Quarter One 2024MINDCTI Revenue Release Quarter One 2024
MINDCTI Revenue Release Quarter One 2024
 
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
 
Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...
 
Strategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a FresherStrategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a Fresher
 
"I see eyes in my soup": How Delivery Hero implemented the safety system for ...
"I see eyes in my soup": How Delivery Hero implemented the safety system for ..."I see eyes in my soup": How Delivery Hero implemented the safety system for ...
"I see eyes in my soup": How Delivery Hero implemented the safety system for ...
 
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin WoodPolkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
 
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
 
Platformless Horizons for Digital Adaptability
Platformless Horizons for Digital AdaptabilityPlatformless Horizons for Digital Adaptability
Platformless Horizons for Digital Adaptability
 
Why Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire businessWhy Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire business
 
Navigating the Deluge_ Dubai Floods and the Resilience of Dubai International...
Navigating the Deluge_ Dubai Floods and the Resilience of Dubai International...Navigating the Deluge_ Dubai Floods and the Resilience of Dubai International...
Navigating the Deluge_ Dubai Floods and the Resilience of Dubai International...
 

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