[Template-haskell] Examples?

Bulat Ziganshin bulat.ziganshin at gmail.com
Sun Sep 21 05:13:27 EDT 2008


Hello Magnus,

Sunday, September 21, 2008, 1:02:16 PM, you wrote:

>> examples are
>> 1) serialization at the end of TH wiki page
>> 2) serialization (again) in the Streams library
> I assume the TH wiki page is
> http://www.haskell.org/haskellwiki/Template_Haskell but I don't see

i mean 10.5 although it just counts amount of fields.

Data.AltBinary.TH module from Streams:

{-# OPTIONS_GHC -fglasgow-exts -fth #-}
{- |
   Module     : Data.AltBinary.TH
   Copyright  : Copyright (C) 2006 Bulat Ziganshin
   License    : BSD3

   Maintainer : Bulat Ziganshin <Bulat.Ziganshin at gmail.com>
   Stability  : experimental
   Portability: GHC

Binary I/O and serialization library: automatic deriving of Binary instances
using Template Haskell

Based on the: Binary I/O library (c) The University of Glasgow 2002

Based on the nhc98 Binary library, which is copyright
(c) Malcolm Wallace and Colin Runciman, University of York, 1998.
Under the terms of the license for that software, we must tell you
where you can obtain the original version of the Binary library, namely
    http://www.cs.york.ac.uk/fp/nhc98/

Also based on the TH support in SerTH library by Einar Karttunen

-}

module Data.AltBinary.TH (
    deriveBinary  -- Derive a Binary instance for the type supplied.
  ) where

import Language.Haskell.TH
import Language.Haskell.TH.Syntax
import Control.Monad

import Data.AltBinary.Stream
import Data.AltBinary.Class
import Data.AltBinary.Custom.Base (bitsNeeded)

-- | Derive a Binary instance for the type supplied.
deriveBinary :: Name -> Q [Dec]
deriveBinary t = do
    -- Get lists of parameters and constructors for type t
    (origParams, constructors) <- getParamsConstructors t

    -- Create N fresh type variables to use in instance declaration
    --   where N is a number of parameters in this type constructor,
    --   e.g. 0 for Int, 1 for Maybe, 2 for (,) ...
    params <- mapM (const$ newName "a") origParams

    -- How many bits needed to encode constructor number?
    let bits = bitsNeeded 1 (length constructors)

    -- Generate bodies of 'put_' and 'get' methods
    put_body <- makePutBody bits constructors
    get_body <- makeGetBody bits constructors

    -- Generate Binary instance for type t. Something like this:
    --     instance (Binary a) => Binary ($t a) where
    --       put_ = $put_body
    --       get  = $get_body
    return [InstanceD (map (appBinary.VarT) params)   -- instance (Binary a) =>
                      (appBinary$ appType t params)   --           Binary ($t a)
                      [FunD 'put_ put_body,           -- where put_ = $put_body
                       FunD 'get [get_body]]]         --       get  = $get_body


-- | Generate from list of type names result of types application:
--     appType T [a,b] -> "T a b"
appType :: Name -> [Name] -> Type
--appType t []      = ConT t                                     -- T
--appType t [t1]    = AppT (ConT t) (VarT t1)                    -- T a
--appType t [t1,t2] = AppT (AppT (ConT t) (VarT t1)) (VarT t2)   -- T a b == (T a) b
appType t ts  =  foldl (\a e -> AppT a (VarT e)) (ConT t) ts     -- general definition

-- | Apply a Binary type constructor to given type: "t" -> "Binary t"
appBinary :: Type -> Type
appBinary t  =  AppT (ConT ''Binary) t


-- | Get lists of parameters and constructors for type t
getParamsConstructors :: Name -> Q ([Name], [Con])
getParamsConstructors t = do
    declaration <- reify t
    case declaration of
        TyConI (DataD    _ _ params constructors _) -> return (params, constructors)
        TyConI (NewtypeD _ _ params constructor  _) -> return (params, [constructor])
        _ -> do let typename = nameBase t
                report True ("deriveBinary ''"++typename
                             ++": impossible, because it's neither"
                             ++" data nor newtype declaration")
                return ([],[])


-- | Make body for function `put_`:
--     put_ h (A x1 x2) = putBits 2 h (0::Int) >> put_ h x1 >> put_ h x2
--     put_ h (B x1)    = putBits 2 h (1::Int) >> put_ h x1
--     put_ h  C        = putBits 2 h (2::Int)
makePutBody :: Int -> [Con] -> Q [Clause]
makePutBody bits [] = do -- no constructors, i.e. it is the phantom type
                         a <- clause [] (normalB [| \_ _ -> return () |]) []
                         return [a]

makePutBody bits constructors = mapM (putClause bits) (zip constructors [0..])

-- | Generate `put_` clause for one constructor:
--     put_ h (A x1 x2) = putBits 2 h (0::Int) >> put_ h x1 >> put_ h x2
putClause :: Int -> (Con,Int) -> Q Clause
putClause bits (constructor, constructorNum) = do
    let (name, fieldsCount) = cnorm constructor
    -- Create variables for left and right side of function definition
    (hpat:pats, hvar:vars) <- genNames (fieldsCount+1)
    -- Add "putBits ..." clause unless there is only one constructor
    let putbitsClause = if bits==0  then [| return () |]
                                    else [| putBits bits $hvar (constructorNum::Int) |]
    -- Recursively build (put_ h x1 >> ...) expression from [x1...] variables list
    let f []       = [| return () |]
        f (v:vars) = [| put_ $hvar $v >> $(f vars) |]
    -- Generate function clause for one constructor
    clause [hpat, conP name pats]                       -- \h (A x1 x2) ->
           (normalB [| $putbitsClause >> $(f vars) |])  -- putBits 2 h 0 >> put_ h x1 >> put_ h x2
           []


-- | Make body for function `get`:
--     get h = do (n::Int) <- getBits 2 h
--                case n of
--                    0 -> do x1 <- get h; x2 <- get h; return (A x1 x2)
--                    1 -> do x1 <- get h; return (B x1)
--                    2 -> do return C
makeGetBody :: Int -> [Con] -> Q Clause
makeGetBody bits [] = do -- no constructors, i.e. it is the phantom type
                         let c = [| \_ -> return (error "value of phantom type") |]
                         clause [] (normalB c) []

makeGetBody bits constructors = do
    -- Generate two pattern/expression pairs used here
    ([hpat],[hvar]) <- genNames 1
    ([npat],[nvar]) <- genNames 1
    -- List of matches for "case"
    let matches = map (getCase hvar) (zip constructors [0..])
    -- The whole "case" expression lambdified: (\n -> case n of ...)
    let caseExp = lamE [npat] (caseE nvar matches)
    -- Add "getBits ..." clause before "case ..." unless there is only one constructor
    let getbitsClause =  if bits==0  then [| ($caseExp) 0 |]
                                     else [| do (n::Int) <- getBits bits $hvar; ($caseExp) n |]
    -- And now the final result
    clause [hpat]                   -- \h ->
           (normalB getbitsClause)  -- do (n::Int) <- getBits 2 h; case n of ...
           []

-- | Generate `get` case for one constructor:
--     0 -> get h >>= (\x1-> get h >>= (\x2-> return (A x1 x2)))
getCase :: ExpQ -> (Con,Integer) -> Q Match
getCase hvar (constructor, constructorNum) = do
    let (name, fieldsCount) = cnorm constructor
    let constructorName = conE name
    -- Recursively build the right part of match and accumulate in
    -- the `returnExpr` the returned expression (technique borrowed from the printf example)
    let f 0 returnExpr = [| return $returnExpr |]
        f n returnExpr = [| get $hvar >>= (\x -> $(f (n-1) [|$returnExpr x|])) |]
    match (litP$ IntegerL constructorNum)
          (normalB (f fieldsCount constructorName))
          []


-- | Generate `n` unique variables and return them in form of patterns and expressions
genNames :: Int -> Q ([PatQ],[ExpQ])
genNames n = do
  ids <- replicateM n (newName "x")
  return (map varP ids, map varE ids)

-- | Normalize a constructor (return its name and number of fields)
cnorm :: Con -> (Name,Int)
cnorm (NormalC n l) = (n,length l)
cnorm (RecC    n l) = (n,length l)


-- 
Best regards,
 Bulat                            mailto:Bulat.Ziganshin at gmail.com



More information about the template-haskell mailing list