[Template-haskell] Examples?
Magnus Therning
magnus at therning.org
Sun Sep 21 05:28:59 EDT 2008
On Sun, Sep 21, 2008 at 10:13 AM, Bulat Ziganshin
<bulat.ziganshin at gmail.com> wrote:
> 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.
That particular example isn't complete enough for a beginner like me
to really understand what is going on. Also I was hoping that the
standard `reify` from `Language.Haskell.TH.Syntax` could be used.
Oh I guess the example below isn't from Data.List.Stream which is what
I ran into when searching for "haskell streams" in Google :-) It
looks a lot more complete though, but I'll need to spend a bit of time
with it to grok it enough to know how it performs its magic.
Thanks!
/M
> 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
>
>
--
Magnus Therning (OpenPGP: 0xAB4DFBA4)
magnus@therning.org Jabber: magnus@therning.org
http://therning.org/magnus identi.ca|twitter: magthe
More information about the template-haskell
mailing list