$(Lecture) ^. _10

Table of content

Lens

Costate Comonad Coalgebra is an equivalent of Java's member variable update technology for Haskell

Nested data structures

//we have a "person":
var person = {
    name: "James",
    age: 28,
    address: {
        house: 42,
        street: "Some Road",
        city: "London"
    }
};
data Person = Person 
    { name    :: String
    , age     :: Int
    , address :: Address
    }
data Address = Address 
    { house  :: Int
    , street :: String
    , city   :: String
    }
james = Person {
    name    = "James",
    age     = 28,
    address = Address {
        house  = 42,
        street = "Some Road",
        city   = "London"
    }
}

JavaScript: not typesafe

Haskell: typesafe

Problem

var houseNumber =
  person.address.house;

person.address.house = 29;
houseNumber =
  house (address james)

updatedJames =
  james {
    address =
      (address james) { house = 29 }
    }

JavaScript: convenient

Haskell: ugly

setHouse person value =
  person { address = (address person) { house = value }  }
setStreet person value =
  person { address = (address person) { street = value }  }

newPerson1 = setHouse person 45
newPerson2 = setStreet person "New Street"

Setters are pain :(

Because they are not composable!

Naive lenses

data Lens obj field = Lens
     { view :: obj -> field
     , set  :: field -> obj -> obj
     }

lens(obj, field) — pair of

getter from obj to field

setter of field inside object

over :: Lens obj field -> (field -> field) -> (obj -> obj)
over lens updater obj = set lens (updater $ view lens obj) obj
view :: Lens obj field -> obj -> field
set  :: Lens obj field -> field -> obj -> obj

Still Naive lenses

data Lens obj field = Lens
     { view :: obj -> field
     , over :: (field -> field) -> (obj -> obj)
     }

Let's use another but equivalent definition for further convenience

set  :: Lens obj field -> field -> obj -> obj
set = _
set  :: Lens obj field -> field -> obj -> obj
set lens newField obj = over lens (const newField) obj
view :: Lens obj field -> obj -> field
over :: Lens obj field -> (field -> field) -> (obj -> obj)

Naive lens example

personAddressLens :: Lens Person Address
personAddressLens = Lens address (\fn obj -> obj { address = fn (address obj) })

addressCityLens :: Lens Address City
addressCityLens = Lens city (\fn obj -> obj { city = fn (city obj) })

Basic lens examples

data Lens obj field = Lens
     { view :: obj -> field
     , over :: (field -> field) -> (obj -> obj)
     }
data Person = Person 
     { name    :: String
     , age     :: Int
     , address :: Address
     }

data Address = Address 
     { house  :: Int
     , street :: Street
     , city   :: City
     }

newtype City = City String
newtype Street = Street String

Composing lenses

personAddressLens :: Lens Person  Address
addressCityLens   :: Lens Address City

Naive ugly implementation

personCityLens :: Lens Person City
personCityLens = 
    Lens (city . address)
         (\cityFn person -> person 
             { address = address person 
                 { city = cityFn $ city $ address person } 
             }
         )

Composing lenses

personCityLens :: Lens Person City
personCityLens = 
    Lens (view addressCityLens    . view personAddressLens) 
         (over personAddressLens  . over addressCityLens)

Using existing lenses

(.) :: (b -> c) -> (a -> b) -> (a -> c)
(.) g f x = g (f x)

view addressCityLens :: Address -> City
view personAddressLens :: Person -> Address

a -> b = Person -> Address
a = Person, b = Address

b -> c = Address -> City
b = Address, c = City

a -> c = Person -> City
(.) :: (b -> c) -> (a -> b) -> (a -> c)

over personAddressLens :: (Address -> Address) -> (Person -> Person)
over addressCityLens  :: (City -> City)   -> (Address -> Address)

a -> b = (City -> City) -> (Address -> Address)
a = City -> City, b = Address -> Address

b -> c = (Address -> Address) -> (Person -> Person)
b = Address -> Address, c = Person -> Person

a -> c = (City -> City) -> (Person -> Person)

Using our lens

ghci> view addressLens james
Address {house = 42, street = "Some Road", city = "London"}

ghci> view personToCityLens james
"London"

ghci> set personToCityLens "Paris" james
Person { name    = "James"
       , age     = 28, 
       , address = Address { house  = 42
                           , street = "Some Road"
                           , city   = "Paris"
                           }
       }

ghci> over personToCityLens (++"!!!!") james
Person { name    = "James"
       , age     = 28, 
       , address = Address { house  = 42
                           , street = "Some Road"
                           , city   = "London!!!!"
                           }
       }

General lens composition

(.>) :: Lens obj   field 
     -> Lens field subfield
     -> Lens obj   subfield
o2f .> f2s = _
(.>) :: Lens obj   field 
     -> Lens field subfield
     -> Lens obj   subfield
o2f .> f2s = Lens (view f2s . view o2f) 
                  (over o2f . over f2s)
personAddressLens :: Lens Person  Address
addressCityLens   :: Lens Address City
personCityLens :: Lens Person City
personCityLens = personAddressLens .> addressCityLens

Lenses are composable!

We just created naive implementation of lens library. Unfortunately, our lenses are not so great, real lens are different...

Using our composable lenses

ghci> set (addressLens .> cityLens) "Paris" james
Person { name    = "James"
       , age     = 28, 
       , address = Address { house  = 42
                           , street = "Some Road"
                           , city   = "Paris"
                           }
       }

ghci> over (addressLens .> cityLens) (++"!!!!") james
Person { name    = "James"
       , age     = 28, 
       , address = Address { house  = 42
                           , street = "Some Road"
                           , city   = "London!!!!"
                           }
       }

It's possible to make API nicer!

ghci> james & addressLens.>cityLens .= "Paris" 
Person { name    = "James"
       , age     = 28, 
       , address = Address { house  = 42
                           , street = "Some Road"
                           , city   = "Paris"
                           }
       }

Real lenzzz

type Lens  s t a b = forall f. Functor f => (a -> f b) -> (s -> f t)
type Lens' s   a   = Lens s s a a
type Lens' s   a   = forall f. Functor f => (a -> f a) -> (s -> f s)
-- lens :: (s -> a) -> (s -> a -> s) -> (a -> f a) -> s -> f s
lens :: (s -> a) -> (s -> a -> s) -> Lens' s a
lens get set f s = set s <$> f (get s)
newtype Const a x = Const { getConst :: a }
instance Functor (Const a) where
    fmap _ (Const v) = Const v
-- view :: ((a -> Const a a) -> (s -> Const a s)) -> s -> a
view :: Lens' s a -> s -> a
view lns s = _
newtype Identity a = Identity { runIdentity :: a }
instance Functor Identity where
    fmap f (Identity x) = Identity (f x)
-- over :: ((a -> Identity a) -> s -> Identity s) -> (a -> a) -> s -> s
over :: Lens' s a -> (a -> a) -> s -> s
over lns fn s = _
-- set :: ((a -> Identity a) -> s -> Identity s) -> a -> s -> s
set :: Lens' s a -> a -> s -> s
set l a s = runIdentity $ l (Identity . const a) s
newtype Const a x = Const { getConst :: a }
instance Functor (Const a) where
    fmap _ (Const v) = Const v
-- view :: ((a -> Const a a) -> (s -> Const a s)) -> s -> a
view :: Lens' s a -> s -> a
view l s = getConst $ l Const s
newtype Identity a = Identity { runIdentity :: a }
instance Functor Identity where
    fmap f (Identity x) = Identity (f x)
-- over :: ((a -> Identity a) -> s -> Identity s) -> (a -> a) -> s -> s
over :: Lens' s a -> (a -> a) -> s -> s
over l fn s = runIdentity $ l (Identity . fn) s

Lens laws

view l (set l field obj)   ≡ field

I. You get back what you put in:

set l (view l obj) obj   ≡ obj

II. Putting back what you got doesn't change anything:

set l field (set l field obj) ≡ set l field obj

III. Setting twice is the same as setting once:

Control.Lens

(.~) :: Lens' s a -> a        -> (s -> s)
(.=) :: Lens' s a -> a        -> State s ()

(%~) :: Lens' s a -> (a -> a) -> (s -> s)
(%=) :: Lens' s a -> (a -> a) -> State s ()
  1. Operators that begin with ^ are kinds of views. The only example we've seen so far is (^.) which is view exactly.

  2. Operators that end with ~ are like over or set. In fact,
    (.~) == set and (%~) is over.

  3. Operators that have . in them are usually somehow «basic»

  4. Operators that have % in them usually take functions.

  5. Operators that have = in them are just like their cousins where = is replaced by ~, but instead of taking the whole object as an argument, they apply their modifications in a State monad.

Initial state

initialState :: Game
initialState = Game
    { _score = 0
    , _units =
        [ Unit { _health = 10
               , _position = Point { _x = 3.5
                                   , _y = 7.0 }
               }
        , Unit { _health = 15
               , _position = Point { _x = 1.0
                                   , _y = 1.0 }
               }
        , Unit { _health = 8
               , _position = Point { _x = 0.0
                                   , _y = 2.1 }
               }
        ]
    , _boss = Unit
        { _health = 100
        , _position = Point { _x = 0.0
                            , _y = 0.0 }
        }
    }

Let's have some initial state for testing

score :: Lens' Game Int
score = lens _score (\game v -> game { _score = v })

units :: Lens' Game [Unit]
units = lens _units (\game v -> game { _units = v })

boss :: Lens' Game Unit
boss = lens _boss (\game v -> game { _boss = v })

health :: Lens' Unit Int
health = lens _health (\unit v -> unit { _health = v })

position :: Lens' Unit Point
position = lens _position (\unit v -> unit { _position = v })

x :: Lens' Point Double
x = lens _x (\point v -> point { _x = v })

y :: Lens' Point Double
y = lens _y (\point v -> point { _y = v })

Lens for Game

lens :: (s -> a) -> (s -> a -> s) -> Lens' s a

Lens are functions

(.) :: ((Unit -> f Unit) -> (Game -> f Game))
    -> ((Int  -> f Int ) -> (Unit -> f Unit))
    -> ((Int  -> f Int ) -> (Game -> f Game))
type Lens' obj field 
    = forall f . Functor f => (field -> f field) -> (obj -> f obj)
boss   :: Functor f => (Unit -> f Unit) -> (Game -> f Game) -- Lens' Game Unit
health :: Functor f => (Int  -> f Int)  -> (Unit -> f Unit) -- Lens' Unit Int
(.) :: (b -> c) -> (a -> b) -> (a -> c)
(g . f) x = g (f x)
boss . health :: ???
a ~ (Int  -> f Int)
b ~ (Unit -> f Unit)
c ~ (Game -> f Game)
(.) :: Lens' Game Unit 
    -> Lens' Unit Int
    -> Lens' Game Int
boss . health :: Lens' Game Int

Getters and setters

ghci> initialState^.score
0
ghci> initialState^.boss.position.x
0.0
ghci> let thisBoss = initialState^.boss
ghci> thisBoss
Unit {_health = 100, _position = Point {_x = 0.0, _y = 0.0}}
ghci> thisBoss & position.x .~ 1.0
Unit {_health = 100, _position = Point {_x = 1.0, _y = 0.0}}

Getters

Setters

Tuples & modifiers

ghci> ("Hello", 4.5) ^. _1
"Hello"
ghci> ([1,2,3], "Hello", 4.5) ^. _1
[1,2,3]
ghci> ([1,2,3], "Hello", 4.5) ^. _3
4.5
ghci> ("Hello", 4.5) & _1 %~ (++ ", world!")
("Hello, world!",4.5)

Real lenzzz

import Control.Monad.Trans.Class
import Control.Monad.Trans.State

strike :: StateT Game IO ()
strike = do
    liftIO $ putStrLn "*shink*"
    boss.health -= 10
ghci> newState <- execStateT strike initialState 
*shink*
ghci> newState^.boss.health
90

Multiple values

fireBreath :: StateT Game IO ()
fireBreath = do
    lift $ putStrLn "*rawr*"
    units.traversed.health -= 3

Lens allows focusing on single element when this element exists for sure.

type Lens' obj field 
    = forall f . Functor f => (field -> f field) -> obj -> f obj
type Traversal s t a b 
    = forall f . Applicative f => (a -> f b) -> (s -> f t)
type Traversal' obj field 
    = forall f . Applicative f => (field -> f field) -> (obj -> f obj)

Traversal allows  to focus on multiple elements

class (Functor t, Foldable t) => Traversable t where
  traverse :: Applicative f => (a -> f b) -> t a -> f (t b)

traverse :: (Applicative f, Traversable t) => (a -> f b) -> t a -> f (t b)
traverse :: (Applicative f, Traversable t) => (a -> f b) -> (t a -> f (t b))
traverse :: Traversal (t a) (t b) a b 

traversed :: Traversable t => Traversal' (t a) a
traversed = traverse

Traversable composition

(.) :: Lens'      a b -> Traversal' b c -> Traversal' a c
(.) :: Traversal' a b -> Lens'      b c -> Traversal' a c
units                  :: Lens'      Game [Unit]
units.traversed        :: Traversal' Game  Unit
units.traversed.health :: Traversal' Game  Int

Traversable is composable with Lens!

ghci> :t units.traversed.health
units.traversed.health
  :: Applicative f => (Int -> f Int) -> Game -> f Game

Deal with Traversable

ghci> toListOf partyHP newState 
[7,12,5]

ghci> initialState^..partyHP
[10,15,8]
ghci> newState^..partyHP
[7,12,5]

You can't use (^.) to extract values of Traversal. Because (^.) is for single value. And Traversal can contain multiple values. So you need to collect values into some Monoid.

type Getting r s a = (a -> Const r a) -> s -> Const r s

toListOf :: Getting (Endo [a]) s a -> s -> [a]
(^..)    :: s -> Getting (Endo [a]) s a -> [a]

partyHP = units.traversed.health

0}{`/Ξ]-[|o||`| 

around :: Point -> Double -> Traversal' Unit Unit
around center radius = filtered (\unit ->
    (unit^.position.x - center^.x)^2
  + (unit^.position.y - center^.y)^2
  < radius^2 )
ghci> initialState^..units.traversed.position
[Point {_x = 3.5, _y = 7.0},Point {_x = 1.0, _y = 1.0},Point {_x
 = 0.0, _y = 2.1}]

ghci> newState <- execStateT (fireBreath (Point 0.5 1.5)) initialState 
*rawr*
ghci> (initialState^..partyHP, newState^..partyHP)
([10,15,8],[10,12,5])

filtered Traversal

fireBreath :: Point -> StateT Game IO ()
fireBreath target = do
    lift $ putStrLn "*rawr*"
    units.traversed.(around target 1.0).health -= 3

Zoom

partyLoc :: Traversal' Game Point
partyLoc = units.traversed.position

retreat :: StateT Game IO ()
retreat = do
    lift $ putStrLn "Retreat!"
    zoom partyLoc $ do
        x += 10
        y += 10

Combining stuff

battle :: StateT Game IO ()
battle = do
    -- Charge!
    for_ ["Take that!", "and that!", "and that!"] $ \taunt -> do
        liftIO $ putStrLn taunt
        strike

    -- The dragon awakes!
    fireBreath (Point 0.5 1.5)

    replicateM_ 3 $ do
        -- The better part of valor
        retreat
        -- Boss chases them
        zoom (boss.position) $ do
            x += 10
            y += 10

Optics hierarchy

Prisms

preview :: Prism' s a -> s -> Maybe a
review :: Prism' s a -> a -> s
_Left :: Prism' (Either a b) a
_Just :: Prism' (Maybe a) a

ghci> preview _Left (Left "hi")
Just "hi"
ghci> preview _Left (Right "hi")
Nothing

ghci> review _Left "hi"
Left "hi"
ghci> preview _Just (Just "hi")
Just "hi"

ghci> review _Just "hi"
Just "hi"

ghci> Left "hi" ^? _Left
Just "hi"
_Cons :: Prism' [a] (a, [a])
_Nil :: Prism' [a] ()

ghci> preview _Cons []
Nothing

ghci> preview _Cons [1,2,3]
Just (1, [2,3])

ghci> preview _Nil []
Just ()

ghci> preview _Nil [1,2,3]
Nothing
type Prism s t a b 
    = forall p f. (Choice p, Applicative f) => p a (f b) -> p s (f t)

Prism use case

Updating values inside type sums.

data X = MkX { _fieldX :: String }
data C = MkC1 X | MkC2 Y
data B = MkB { _fieldB :: C   }
data A = MkA { _fieldA :: [B] }

foo :: A

How to update fieldX inside A only if it's X?

foo & fieldA.traversed.fieldB._MkC1.fieldX .~ "New string"

microlens family

lens library is really heavy. It has a lot of dependencies. If you're writing your own library you might want to use lighter alternatives which are, happily, completely compatible with lens library!

Read with Lens

Macros

Boilerplate annihilator

instance MonadBaseControl IO IO where
    type StM IO a = a
    liftBaseWith f = f id
    restoreM = return
    {-# INLINABLE liftBaseWith #-}
    {-# INLINABLE restoreM #-}
instance MonadBaseControl Maybe Maybe where
    type StM Maybe a = a
    liftBaseWith f = f id
    restoreM = return
    {-# INLINABLE liftBaseWith #-}
    {-# INLINABLE restoreM #-}
instance MonadBaseControl [] [] where
    type StM [] a = a
    liftBaseWith f = f id
    restoreM = return
    {-# INLINABLE liftBaseWith #-}
    {-# INLINABLE restoreM #-}
instance MonadBaseControl STM STM where
    type StM STM a = a
    liftBaseWith f = f id
    restoreM = return
    {-# INLINABLE liftBaseWith #-}
    {-# INLINABLE restoreM #-}

Looks like boilerplate!

instance MonadBaseControl (Either e) (Either e) where
    type StM (Either e) a = a
    liftBaseWith f = f id
    restoreM = return
    {-# INLINABLE liftBaseWith #-}
    {-# INLINABLE restoreM #-}

-XCPP




instance MonadBaseControl IO IO where
    type StM IO a = a
    liftBaseWith f = f id
    restoreM = return
    {-# INLINABLE liftBaseWith #-}
    {-# INLINABLE restoreM #-}

Haskell has support for small subset of CPP preprocessor pragmas. These pragmas can be useful for different reasons, like, generate boilerplate instances.

{-# LANGUAGE CPP #-}

#define BASE(M)                           \
instance MonadBaseControl (M) (M) where { \
    type StM (M) a = a;                   \
    liftBaseWith f = f id;                \
    restoreM = return;                    \
    {-# INLINABLE liftBaseWith #-};       \
    {-# INLINABLE restoreM #-}}

Raw Haskell

CPP pragmas

Instance generation

And now we can easily generate instances:

BASE(IO)
BASE(Maybe)
BASE(Either e)
BASE([])
BASE((->) r)
BASE(Identity)
BASE(STM)

#if MIN_VERSION_base(4,4,0)
BASE(Strict.ST s)
BASE(       ST s)
#endif

#undef BASE

CPP pragmas are easy to use. They can be ugly though sometimes unavoidable. But they are not powerful enough.

Template Haskell

TH for lens generation (1 / 3)

data Game = Game
     { _score :: Int
     , _units :: [Unit]
     , _boss  :: Unit
     } deriving (Show)

data Unit = Unit
     { _health   :: Int
     , _position :: Point
     } deriving (Show)

data Point = Point
     { _x :: Double
     , _y :: Double
     } deriving (Show)

Step 1. Create your data types with fields prefixed by underscore.

As usual, in Haskell, you can use something complex and smart without understanding how it works. So here's quick guide how to use lens library in real life.

TH for lens generation (2 / 3)

{-# LANGUAGE TemplateHaskell #-}

module MyModule where

import Control.Lens

data Game = Game
     { _score :: Int
     , _units :: [Unit]
     , _boss  :: Unit
     } deriving (Show)

{- And other data types -}

Step 2. Add this to your module(s):

• {-# LANGUAGE TemplateHaskell #-}

• import Control.Lens

TH for lens generation (3 / 3)

Step 3. Use makeLenses macro to generate lenses.

{-# LANGUAGE TemplateHaskell #-}

import Control.Lens

data Game = Game
     { _score :: Int
     , _units :: [Unit]
     , _boss  :: Unit
     } deriving (Show)

data Unit = Unit
     { _health   :: Int
     , _position :: Point
     } deriving (Show)

data Point = Point
     { _x :: Double
     , _y :: Double
     } deriving (Show)

makeLenses ''Game
makeLenses ''Unit
makeLenses ''Point
score :: Lens' Game Int
score = lens _score (\game v -> game { _score = v })

units :: Lens' Game [Unit]
units = lens _units (\game v -> game { _units = v })

boss :: Lens' Game Unit
boss = lens _boss (\game v -> game { _boss = v })

health :: Lens' Unit Int
health = lens _health (\unit v -> unit { _health = v })

position :: Lens' Unit Point
position = lens _position (\unit v -> unit { _position = v })

x :: Lens' Point Double
x = lens _x (\point v -> point { _x = v })

y :: Lens' Point Double
y = lens _y (\point v -> point { _y = v })

Macro expanding

makeLenses expands roughly to something like this

Motivating example

fst  (x,_)     = x  -- tuples of different size in Haskell have different types!
fst3 (x,_,_)   = x
fst4 (x,_,_,_) = x

print $ fst3 ("hello world", 1, 2)
print $ fst4 ("hello world", 1, 2, 3)

Template haskell to the rescue!

{-# LANGUAGE TemplateHaskell #-}
print $ $(fstN 3) ("hello world", 1, 2)
print $ $(fstN 4) ("hello world", 1, 2, 3)
{-# LANGUAGE TemplateHaskell #-}
module FstN where
import Language.Haskell.TH

fstN :: Int -> Q Exp
fstN n = do
   x <- newName "x"
   pure $ LamE [TupP $ VarP x : replicate (n - 1) WildP] (VarE x)

How to write it?

Everything is not okay with such code

Ok, how about explaining it?

LamE [TupP [VarP x_1,WildP,WildP]] (VarE x_1)  -- AST of lambda \(x,_,_) -> x
ghci> :t (VarP, VarE)
(VarP, VarE) :: (Name -> Pat, Name -> Exp)
ghci> :t newName
newName :: String -> Q Name

So, VarP and VarE takes a Name. Let’s see how we can satisfy them:

ghci> :t (varE, varP)
(varE, varP) :: (Name -> Q Exp, Name -> Q Pat)

For every constructor there's monadic function

Writing fst3 in TH

import Language.Haskell.TH

fst3 = do
    x <- newName "x"
    pure $ LamE [TupP [VarP x, WildP, WildP]] (VarE x)

Corresponds to this expression:

\(x,_,_) -> x

fst3 is a macro. Use $(fst3) to expand macro. $() is called splice.

ghci> :t fst3
fst3 :: Q Exp  -- Q is some monad
ghci> :t $(fst3)
$(fst3) :: (t2, t1, t) -> t2
ghci> $(fst3) ("hello", 10, True)
"hello"
import Language.Haskell.TH

fst3 = do
    x <- newName "x"
    lamE [tupP [varP x, wildP, wildP]] (varE x)

Writing fstN in TH

fst4 :: Q Exp
fst4 = do
    x <- newName "x"
    lamE [tupP [varP x, wildP, wildP, wildP]] (varE x)

fst4 — just add one more wildP

fstN :: Int   -- ^ take tuple «length» as argument
     -> Q Exp
fstN n = do
    x <- newName "x"
    lamE [tupP $ varP x : replicate (n - 1) wildP] (varE x)

We can easily generalize it into fstN

fst5 :: Q Exp
fst5 = do
    x <- newName "x"
    lamE [tupP [varP x, wildP, wildP, wildP, wildP]] (varE x)

fst5 — just add two more wildP

You got the idea :)

Stage restriction

For technical reasons,

 

(M :: Macro) : module[M] ≠ module[$(M)]

 

macros M and its splice $(M) must be defined in different modules.

-XQuasiQuotes

  • The [| … |] notation is the quasi quotes for Haskell expression.

  • The contents within quasi quotes will be parsed at compile time.

  • There exist several GHC parsers, but you can define your own!

When you do [| \x -> x |], the string inside the brackets is parsed by the Haskell compiler as expression and gives you back the AST (Abstract Syntax Tree) of this expression.

ghci> :set -XQuasiQuotes 
ghci> import Language.Haskell.TH (runQ)
ghci> runQ [| \x -> x |]
LamE [ VarP x_0 ] ( VarE x_0 )
ghci> runQ [| data A = B Int |]
<interactive>:11:9: error: parse error on input ‘data’
ghci> runQ [d| data A = B Int |]  -- «d» is for declaration
[ DataD [] A_1 [] Nothing 
    [ NormalC B_2 
        [ 
            ( Bang NoSourceUnpackedness NoSourceStrictness
            , ConT GHC.Types.Int
            ) 
        ]
    ] []
]

Standard TH quotes

  • Expression quotes

    • [| \x -> x + 1 |] :: Q Exp

  • Type quotes

    • [t| Int -> Int |] :: Q Type

  • Pattern quotes

    • [p| xs@(x:r) |] :: Q Pat

  • Declaration quotes

    • [d| data Pair a = Pair a a |] :: Q [Dec]

Custom quasi quotes example

{-# LANGUAGE QuasiQuotes #-}

import Data.Text (Text, pack)
import NeatInterpolation (text)

example :: Text -> Int -> Text
example prefix n = 
    let nText = pack $ show n in
    [text|
      Number $prefix : $nText is:
          - even: ${even n}
    |]
ghci> :set -XOverloadedStrings 
ghci> import qualified Data.Text.IO as TextIO
ghci> TextIO.putStrLn $ example "number" 5
Number number : 5 is:
    - even: ${even n}

Not so neat as you want... But still.

Other quasi quotes use cases

bar :: String
bar = [qc| Well {"hello" ++ " there"} {6 * 7} |]
absdir :: QuasiQuoter

homeDir :: Path Abs Dir
homeDir = [absdir|/home/chris/|]
$ ghci -ddump-splices -XTemplateHaskell
Prelude> $([| 1 + 2 ^ 3 * 4 |])
<interactive>:1:3-21: Splicing expression
    [| 1 + 2 ^ 3 * 4 |] ======> (1 + ((2 ^ 3) * 4))
33

TH for instance generation

{-# LANGUAGE TemplateHaskell #-}

module CustomShow where

import Language.Haskell.TH

emptyShow :: Name -> Q [Dec]
emptyShow name = [d|instance Show $(conT name) where show _ = ""|]
{-# LANGUAGE TemplateHaskell #-}

import CustomShow (emptyShow)

data MyData = MyData
     { foo :: String
     , bar :: Int
     }

emptyShow ''MyData  -- ''MyData == mkName "MyData"
-- ^ Expands to: instance Show MyData where show _ = ""

main = print $ MyData { foo = "bar", bar = 5 }

Other module:

import           Data.FileEmbed (embedFile, makeRelativeToProject)

compileConfig :: CompileConfig
compileConfig =
    $(do let file = $(embedFile =<< makeRelativeToProject "constants.yaml")
         case decodeEither file of
             Left a  -> fail (toString a)
             Right x -> lift (x :: CompileConfig))
k :: Integral a => a
k = fromIntegral . ccK $ compileConfig

genesisN :: Integral i => i
genesisN = fromIntegral . ccGenesisN $ compileConfig
import qualified Data.Aeson.TH              as A
import           Language.Haskell.TH.Syntax (Lift)
import           Serokell.Aeson.Options     (defaultOptions)

data CompileConfig = CompileConfig
    { ccK        :: !Int
    , ccGenesisN :: !Int
    } deriving (Show, Lift)

A.deriveFromJSON defaultOptions ''CompileConfig
k: 2
genesisN: 12

Automatic deriving to parse from file

constants.yaml

Lecture 11: Template Haskell + Lens

By ITMO CTD Haskell

Lecture 11: Template Haskell + Lens

This lecture introduces Template Haskell and gives idea of lens, why we need them and how they solve problems.

  • 5,001