Sunteți pe pagina 1din 54

In Haskell, Less is More

Ollie Charles (@acid2)

May 26, 2016

Introduction

Introduction

In this talk Id like to briefly discuss a few ways in which


Haskell allows us to write less code:
I
I
I

Type classes
Generic programming/structural polymorphism
Declarative programming and bidirectional parsers

Why Care?

Generic programming is beneficial for many reasons.


I
I
I
I

Developer productivity
Code readability
Program correctness
Reusability

Type Classes

A Quick Primer

In Haskell, we can write functions that are polymorphic


over types

map :: (a -> b) -> [a] -> [b]


I

Type classes allow us to quantify over a subset of types


with common operations
This approach gives us tools similar to operator
overloading or interfaces

A Quick Primer
We introduce type classes by assosciating a set of methods
with types.
class Num a where
(+), (-), (*) :: a -> a -> a

A Quick Primer
We introduce type classes by assosciating a set of methods
with types.
class Num a where
(+), (-), (*) :: a -> a -> a

We use type classes just like functions


hardSums :: Num a => a -> a
hardSums x = y + (x - (y * x))
where y = x * x

A Quick Primer
We introduce type classes by assosciating a set of methods
with types.
class Num a where
(+), (-), (*) :: a -> a -> a

We use type classes just like functions


hardSums :: Num a => a -> a
hardSums x = y + (x - (y * x))
where y = x * x

We instantiate type classes by providing instances


instance Num Int where
(+) = primAddition
...

Return Type Polymorphism


A very unique feature of type classes is the ability to have
values within the type class:
class Monoid a where
mappend :: a -> a -> a
mempty :: a
^- a value!
summarise :: Monoid a => [a] -> a
summarise (a : as) = a mappend summarise as
summarise [] = mempty

Return Type Polymorphism


Examples of summarisation
instance Monoid Int where
mappend x y = x + y
mempty = 0
> summarise (map (const 1) [a,b,c,d])
4
> summarise [] :: Int
0

Examples of summarisation

Ordering
If the types have an ordering and bounds, we can compute the
greatest and least elements of a list.
newtype Min a = Min a
newtype Max a = Max a
instance (Ord a, Bounded a) => Monoid (Min a) where
mappend (Min a) (Min b) = Min (min a b)
mempty = Min maxBound

Examples of summarisation
Monoids Compose
If we want to find the maximum and minimum, we can
summarise with two monoids at once:
instance (Monoid a, Monoid b) => Monoid (a, b) where
(a,b) mappend (c, d) =
(mappend a mappend c, mappend b mappend d)
mempty = (mempty, mempty)
> summarise (map (\x -> (Min x, Max x)) [1..10])
(Min 1, Max 10)

Type Classes and Constraint Solving

When we use type classes, type class requirements are


added to the context of the function.
Providing instances teaches the compiler about possible
implementations.
The solving and resolution can be thought of as either
constraint solving or a form of automatic proof search.
By providing instances that depend on other instances,
we can have the compile perform a process similar to
mathematical induction.

Num, Newtons Method & ad

Controlling computers with bicycles

A while ago, I was working on building a little 3D simulation


that is driven by a real physical bicycle (with appropriate
sensors).
https://youtube.com/watch?v=tMcmZtlXaCU

Example: Num, Newtons Method & ad


First, a suitable model based on classical mechanics.
P = Fv
Fresistance = Fgravity + Fdrag
Fgravity = sin(tan1 (grade))mg
1
Fdrag = Cd Av 2
2

Example: Num, Newtons Method & ad


In Haskell. . .
calculatePowerOutput
:: Floating a => a -> a -> a -> a
calculatePowerOutput grade weight velocity =
resistingForces grade weight velocity * velocity
resistingForces :: Floating a => a -> a -> a -> a
resistingForces grade weight velocity =
fGravity + fDrag where
fGravity = g * sin (atan grade) * weight
fDrag = 0.5 * dragCoeff * frontalArea * rho *
velocity * velocity
g = 9.8067
(dragCoeff, frontalArea, rho) =
(0.63, 0.509, 1.226)

Example: Num, Newtons Method & ad


I
I

We need to solve the power equation for velocity.


We could use our existing model with a numerical solver
to find an approximation for v .
Newtons method allows us to find the roots (zeros) of
real valued functions
xn+1 = xn

f (xn )
f 0 (xn )

Example: Num, Newtons Method & ad


I
I

We need to solve the power equation for velocity.


We could use our existing model with a numerical solver
to find an approximation for v .
Newtons method allows us to find the roots (zeros) of
real valued functions
xn+1 = xn

f (xn )
f 0 (xn )

f :: Floating a => a -> a


f v = calculatePowerOutput 0.05 62 v - knownPower
I

But what about the derivative of f?

Example: Num, Newtons Method & ad

I
I

ad is a library for automatic differentiation in Haskell


Calculates the value of the derivative for any real-valued
function at arbitrary points:

> diff (\x -> x * x) 4


(16, 8)
| \--- first derivative (at x = 4)
\------ result (at x = 4)

Example: Num, Newtons Method & ad


Newtons Method. . .
xn+1 = xn

f (xn )
f 0 (xn )

In Haskell!
findZero f = iterate go
where go xn = let (fxn,fxn) = diff f xn
in xn - fxn / fxn

Example: Num, Newtons Method & ad

Putting it all together


velocityForPower grade weight p =
head $ drop 5 $
findZero (\v -> calculatePowerOutput grade weight v - p)
5
> velocityForPower 0.05 61 200
5.56247 m/s = 20.02km/h

Matches real world data recorded from my bike rides!

Whats Happening?
data ForwardDouble = FD (Double, Double)
instance Num ForwardDouble where
FD (a,da) + FD (b,db) = FD (a + b, da + db)
FD (a,da) * FD (b,db) = FD (a * b, a*db + b*da)
...

Whats Happening?
data ForwardDouble = FD (Double, Double)
instance Num ForwardDouble where
FD (a,da) + FD (b,db) = FD (a + b, da + db)
FD (a,da) * FD (b,db) = FD (a * b, a*db + b*da)
...
calculatePowerOutput :: Floating a => a -> a -> a -> a
-- We can choose a ~ ForwardDouble
calculatePowerOutput
:: ForwardDouble
-> ForwardDouble
-> ForwardDouble
-> ForwardDouble

Much More
Type classes can do a lot, much more than I can cover in one
talk.
I
I

Finally tagless encodings


Witnessing relations between types
I

Especially with MultiParamTypeClasses and


FunctionalDependencies

Type classes can also contain functions on types with


TypeFamilies
More extensions to the instance resolution system through
OverlappingInstances and UndecidableInstances

GHC Generics

GHC Generics
I

In GHC 7.2 (November 2011) a new extension was added


to derive generic representations for data types.
Any data type can be viewed as a list of alternative
constructors, where each constructor is a list of possible
fields.
GHC.Generics gives us a "kit" to build data types from:
I

I
I

Fields: K1 for constants, Par0 for paremeters, Rec0 for


recursive applications
a :*: b to create lists of fields
a :+: b to create lists of constructors.

Also supports metadata

Example derivation
Maybe
data Maybe a = Nothing | Just a

Rep (Maybe a)

:+:

U1

Rec0 a

U1 indicates a unary constructor


- a constructor with no fields
Rec0 indicates a reference to the
type parameter of Maybe for the
Just constructor
:+: introduces a choice between
combinators.

Example for records


Without Metadata
data Talk = Talk { talkTitle :: String
, talkAuthor :: String
, talkAbstract :: String
}

Rec0 String
:*:
Rep Talk

:*:

Rec0 String
Rec0 String

Example for records


With Metadata
Rep Talk

D1 ('MetaData "Talk" ...)

C1 ('MetaCons "Talk" ...)

:*:

:*:

S1 ('MetaSel ('Just "talkTitle") ...)

S1 ('MetaSel ('Just "talkAuthor") ...)

S1 ('MetaSel ('Just "talkAbstract") ...)

Rec0 String

Rec0 String

Rec0 String

Using Generic Functions

I
I

Lets write a basic JSON encoder for record types.


The plan. . .
I
I

Encode the entire record into a JSON object


Each field is a key/value pair in the object, where the
field name is the key name.
Well require that all field types have a canonical JSON
encoding.

Using Generic Functions


Our generic function traverse the shape of the record,
collecting key/value pairs:
class RecordFields f where
recordFields :: f a -> [(String, Value)]

For single fields


instance
ToJSON a =>
RecordFields (S1 (MetaSel (Just fieldName) _ _ _))
(Rec0 a) where
recordFields meta@(M1 (K1 a)) =
[(selName meta, toJSON a)]

Using Generic Functions


For lists of fields
instance
(RecordFields x, RecordFields xs) =>
RecordFields (x :*: xs) where
recordField (x :*: xs) =
recordField x ++ recordField xs
I
I

Notice the context for this instance.


Haskell will find the appropriate code based purely on the
types required, recursing down the left and right sides of
the tree formed by :*: nodes.

Using Generic Functions


Putting it all together
recordToObject
:: (Generic a, RecordFields (Rep a))
=> a -> JSON.Value
recordToObject =
JSON.object . recordFields . to

Using Generic Functions


Putting it all together
recordToObject
:: (Generic a, RecordFields (Rep a))
=> a -> JSON.Value
recordToObject =
JSON.object . recordFields . to
> recordToObject (Talk {talkTitle = "Less is More"
,talkAuthor = "Ollie"
,talkAbstract = "In this talk..."})
{
"talkTitle": "Less is More",
"talkAuthor": "Ollie",
"talkAbstract": "In this talk..."
}

Using Generic Functions


Instance resolution

:*:

Using Generic Functions

Instance resolution
:*:

Sel1 ('MetaSel ('Just "talkAuthor") ..)

:*:

Using Generic Functions

Instance resolution
:*:

Sel1 ('MetaSel ('Just "talkAuthor") ..)

Sel1 ('MetaSel ('Just "talkTitle") ..)

:*:

Sel1 ('MetaSel ('Just "talkAbstract") ..)

Using Generic Functions

Instance resolution
:*:

[("talkAuthor", "Ollie")]

[("talkTitle", "Less is More")]

:*:

[("talkAbstract", "In this talk...")]

What can we do with generics?


I
I

I
I
I
I

Command line parsers (optparse-generic)


More standard base instances (Monoid, Semigroup, etc)
in generic-deriving.
Generate test data for QuickCheck (both Arbitrary and
Coarbitrary instances).
Serialization (aeson, bson-generic)
Pretty printing (GenericPretty)
Derive the ability to do O(N) sorting (discrimination)
Construct tries with arbitrary complex keys
(generic-trie)
Marshal to/from database rows
(postgresql-simple-sop)

More

I
I

For a great talk about GHC generics, check out Andres


Lhs talk on GHC Generics
one-liner makes a lot of this even easier
Other libraries available:
I
I
I

generic-sop
instant-generics
RepLib

Structural Approaches - Bidirectional


Parsing

Bidirectional Parsing
I

Weve seen that we could generically derive functions to


serialize and deserialize data
A generic approach gives us hope that the two are
compatible, but are there other options?
With bidirectional programming, the general idea is to
build a serializer out of small combinators, where each
combinator also knows how to deserialize
Thus we build (and test) a library of small parser-like
combinators, and then compose these to build larger
serializers/deserializers

Example: web-routes-boomerang

This idea is used in the web-routes-boomerang to build


a type safe and correct version of routing and URL
generation:
Start with a data type of possible routes/pages in a web
application (a sitemap)
Derive bidirectional "boomerang" parsers for each
constructor
Compose these to build a full bidirectional parser for the
entire sitemap.

Example: web-routes-boomerang

We start by defining our sitemap and then deriving


bidirectional functions for each constructor.
data HackageSiteMap
= Home
| PackageDetails String
| AuthorPackages String
makeBoomerangs HackageSiteMap

Example: web-routes-boomerang
This generates basic combinators for each constructor:
> :t rHome
rHome :: Boomerang e tok r (HackageSiteMap :- r)

We can see that the PackageDetails constructor requires a


String
> :t rPackageDetails
rPackageDetails
:: Boomerang e tok (String :- r) (HackageSiteMap :- r)

The two arguments to Boomerang act like a stack of incoming


and outgoing URL parts.

Example: web-routes-boomerang
I

I
I
I

Now we compose a router for our data type out of each


constructor.
Category composition chains parsers together
</> "applies" parsing to a constructor
Literal URL parts can be added by providing strings

hackageSiteRouter :: Router HackageSiteMap


hackageSiteRouter =
rHome <>
"package" . rPackageDetails </> anyString <>
"author" . rAuthorPackages </> anyString

Example: web-routes-boomerang
We can parse URLs from strings:
> parse "/"
Home

Example: web-routes-boomerang
We can parse URLs from strings:
> parse "/"
Home
> parse "/author"
parse error at path segment 2, character 0:
unexpected end of input; expecting any string
while parsing ["author"]

Example: web-routes-boomerang
We can parse URLs from strings:
> parse "/"
Home
> parse "/author"
parse error at path segment 2, character 0:
unexpected end of input; expecting any string
while parsing ["author"]
> parse "/author/EdKmett"
AuthorPackages "EdKmett"

Example: web-routes-boomerang
We can parse URLs from strings:
> parse "/"
Home
> parse "/author"
parse error at path segment 2, character 0:
unexpected end of input; expecting any string
while parsing ["author"]
> parse "/author/EdKmett"
AuthorPackages "EdKmett"

We can also turn concrete routes back into strings


> render (PackageDetails "lens")
"/package/lens"

Resources

I
I
I

Tillmann Rendel and Klaus Ostermann. "Invertible


Syntax Descriptions: Unifying Parsing and Pretty
Printing." In Proc. of Haskell Symposium, 2010.
boomerang on Hackage
JsonGrammar on Hackage
"A Category for Correct-By-Construction Serializers and
Deserializers" (my blog)

Thanks for listening!

S-ar putea să vă placă și