On Thu, Sep 29, 2011 at 11:49:34AM +0200, Agata Murawska wrote:
> On Mon, Sep 26, 2011 at 11:57 AM, Iustin Pop <[email protected]> wrote:
> > This patch replaces the current hard-coded JSON instances (all alike,
> > just manual conversion to/from string) with auto-generated code based
> > on Template Haskell
> > (http://www.haskell.org/haskellwiki/Template_Haskell).
> >
> > The reduction in code line is not big, as the helper module is well
> > documented and thus overall we gain about 70 code lines; however, if
> > we ignore comments we're in good shape, and any future addition of
> > such data types will be much simpler and less error-prone.
> > ---
> > Makefile.am | 3 +-
> > htools/Ganeti/HTools/Types.hs | 107 +++++------------------
> > htools/Ganeti/Jobs.hs | 81 +++++-------------
> > htools/Ganeti/OpCodes.hs | 30 +++----
> > htools/Ganeti/THH.hs | 187
> > +++++++++++++++++++++++++++++++++++++++++
> > 5 files changed, 248 insertions(+), 160 deletions(-)
> > create mode 100644 htools/Ganeti/THH.hs
> >
> > diff --git a/Makefile.am b/Makefile.am
> > index 5d4f608..b3edd40 100644
> > --- a/Makefile.am
> > +++ b/Makefile.am
> > @@ -361,7 +361,8 @@ HS_LIB_SRCS = \
> > htools/Ganeti/HTools/Program/Hspace.hs \
> > htools/Ganeti/Jobs.hs \
> > htools/Ganeti/Luxi.hs \
> > - htools/Ganeti/OpCodes.hs
> > + htools/Ganeti/OpCodes.hs \
> > + htools/Ganeti/THH.hs
> >
> > HS_BUILT_SRCS = htools/Ganeti/HTools/Version.hs htools/Ganeti/Constants.hs
> > HS_BUILT_SRCS_IN = $(patsubst %,%.in,$(HS_BUILT_SRCS))
> > diff --git a/htools/Ganeti/HTools/Types.hs b/htools/Ganeti/HTools/Types.hs
> > index 75dbce5..8e6b6a4 100644
> > --- a/htools/Ganeti/HTools/Types.hs
> > +++ b/htools/Ganeti/HTools/Types.hs
> > @@ -1,3 +1,5 @@
> > +{-# LANGUAGE TemplateHaskell #-}
> > +
> > {-| Some common types.
> >
> > -}
> > @@ -72,6 +74,7 @@ import qualified Data.Map as M
> > import qualified Text.JSON as JSON
> >
> > import qualified Ganeti.Constants as C
> > +import qualified Ganeti.THH as THH
> >
> > -- | The instance index type.
> > type Idx = Int
> > @@ -100,36 +103,12 @@ type GroupID = String
> > -- Ord instance will order them in the order they are defined, so when
> > -- changing this data type be careful about the interaction with the
> > -- desired sorting order.
> > -data AllocPolicy
> > - = AllocPreferred -- ^ This is the normal status, the group
> > - -- should be used normally during allocations
> > - | AllocLastResort -- ^ This group should be used only as
> > - -- last-resort, after the preferred groups
> > - | AllocUnallocable -- ^ This group must not be used for new
> > - -- allocations
> > - deriving (Show, Read, Eq, Ord, Enum, Bounded)
> > -
> > --- | Convert a string to an alloc policy.
> > -allocPolicyFromString :: (Monad m) => String -> m AllocPolicy
> > -allocPolicyFromString s =
> > - case () of
> > - _ | s == C.allocPolicyPreferred -> return AllocPreferred
> > - | s == C.allocPolicyLastResort -> return AllocLastResort
> > - | s == C.allocPolicyUnallocable -> return AllocUnallocable
> > - | otherwise -> fail $ "Invalid alloc policy mode: " ++ s
> > -
> > --- | Convert an alloc policy to the Ganeti string equivalent.
> > -allocPolicyToString :: AllocPolicy -> String
> > -allocPolicyToString AllocPreferred = C.allocPolicyPreferred
> > -allocPolicyToString AllocLastResort = C.allocPolicyLastResort
> > -allocPolicyToString AllocUnallocable = C.allocPolicyUnallocable
> > -
> > -instance JSON.JSON AllocPolicy where
> > - showJSON = JSON.showJSON . allocPolicyToString
> > - readJSON s = case JSON.readJSON s of
> > - JSON.Ok s' -> allocPolicyFromString s'
> > - JSON.Error e -> JSON.Error $
> > - "Can't parse alloc_policy: " ++ e
> > +$(THH.declareSADT "AllocPolicy"
> > + [ ("AllocPreferred", 'C.allocPolicyPreferred)
> > + , ("AllocLastResort", 'C.allocPolicyLastResort)
> > + , ("AllocUnallocable", 'C.allocPolicyUnallocable)
> > + ])
> > +$(THH.makeJSONInstance ''AllocPolicy)
> >
> > -- | The resource spec type.
> > data RSpec = RSpec
> > @@ -182,41 +161,15 @@ data IMove = Failover -- ^ Failover
> > the instance (f)
> > deriving (Show, Read)
> >
> > -- | Instance disk template type.
> > -data DiskTemplate = DTDiskless
> > - | DTFile
> > - | DTSharedFile
> > - | DTPlain
> > - | DTBlock
> > - | DTDrbd8
> > - deriving (Show, Read, Eq, Enum, Bounded)
> > -
> > --- | Converts a DiskTemplate to String.
> > -diskTemplateToString :: DiskTemplate -> String
> > -diskTemplateToString DTDiskless = C.dtDiskless
> > -diskTemplateToString DTFile = C.dtFile
> > -diskTemplateToString DTSharedFile = C.dtSharedFile
> > -diskTemplateToString DTPlain = C.dtPlain
> > -diskTemplateToString DTBlock = C.dtBlock
> > -diskTemplateToString DTDrbd8 = C.dtDrbd8
> > -
> > --- | Converts a DiskTemplate from String.
> > -diskTemplateFromString :: (Monad m) => String -> m DiskTemplate
> > -diskTemplateFromString s =
> > - case () of
> > - _ | s == C.dtDiskless -> return DTDiskless
> > - | s == C.dtFile -> return DTFile
> > - | s == C.dtSharedFile -> return DTSharedFile
> > - | s == C.dtPlain -> return DTPlain
> > - | s == C.dtBlock -> return DTBlock
> > - | s == C.dtDrbd8 -> return DTDrbd8
> > - | otherwise -> fail $ "Invalid disk template: " ++ s
> > -
> > -instance JSON.JSON DiskTemplate where
> > - showJSON = JSON.showJSON . diskTemplateToString
> > - readJSON s = case JSON.readJSON s of
> > - JSON.Ok s' -> diskTemplateFromString s'
> > - JSON.Error e -> JSON.Error $
> > - "Can't parse disk_template as string: "
> > ++ e
> > +$(THH.declareSADT "DiskTemplate"
> > + [ ("DTDiskless", 'C.dtDiskless)
> > + , ("DTFile", 'C.dtFile)
> > + , ("DTSharedFile", 'C.dtSharedFile)
> > + , ("DTPlain", 'C.dtPlain)
> > + , ("DTBlock", 'C.dtBlock)
> > + , ("DTDrbd8", 'C.dtDrbd8)
> > + ])
> > +$(THH.makeJSONInstance ''DiskTemplate)
> >
> > -- | Formatted solution output for one move (involved nodes and
> > -- commands.
> > @@ -347,21 +300,9 @@ class Element a where
> > setIdx :: a -> Int -> a
> >
> > -- | The iallocator node-evacuate evac_mode type.
> > -data EvacMode = ChangePrimary
> > - | ChangeSecondary
> > - | ChangeAll
> > - deriving (Show, Read)
> > -
> > -instance JSON.JSON EvacMode where
> > - showJSON mode = case mode of
> > - ChangeAll -> JSON.showJSON C.iallocatorNevacAll
> > - ChangePrimary -> JSON.showJSON C.iallocatorNevacPri
> > - ChangeSecondary -> JSON.showJSON C.iallocatorNevacSec
> > - readJSON v =
> > - case JSON.readJSON v of
> > - JSON.Ok s | s == C.iallocatorNevacAll -> return ChangeAll
> > - | s == C.iallocatorNevacPri -> return ChangePrimary
> > - | s == C.iallocatorNevacSec -> return ChangeSecondary
> > - | otherwise -> fail $ "Invalid evacuate mode " ++ s
> > - JSON.Error e -> JSON.Error $
> > - "Can't parse evacuate mode as string: " ++ e
> > +$(THH.declareSADT "EvacMode"
> > + [ ("ChangePrimary", 'C.iallocatorNevacPri)
> > + , ("ChangeSecondary", 'C.iallocatorNevacSec)
> > + , ("ChangeAll", 'C.iallocatorNevacAll)
> > + ])
> > +$(THH.makeJSONInstance ''EvacMode)
> > diff --git a/htools/Ganeti/Jobs.hs b/htools/Ganeti/Jobs.hs
> > index ed7bc7d..1e2cebe 100644
> > --- a/htools/Ganeti/Jobs.hs
> > +++ b/htools/Ganeti/Jobs.hs
> > @@ -1,3 +1,5 @@
> > +{-# LANGUAGE TemplateHaskell #-}
> > +
> > {-| Implementation of the job information.
> >
> > -}
> > @@ -32,67 +34,30 @@ import Text.JSON (readJSON, showJSON, JSON)
> > import qualified Text.JSON as J
> >
> > import qualified Ganeti.Constants as C
> > +import qualified Ganeti.THH as THH
> >
> > -- | Our ADT for the OpCode status at runtime (while in a job).
> > -data OpStatus = OP_STATUS_QUEUED
> > - | OP_STATUS_WAITING
> > - | OP_STATUS_CANCELING
> > - | OP_STATUS_RUNNING
> > - | OP_STATUS_CANCELED
> > - | OP_STATUS_SUCCESS
> > - | OP_STATUS_ERROR
> > - deriving (Eq, Enum, Bounded, Show, Read)
> > -
> > -instance JSON OpStatus where
> > - showJSON os = showJSON w
> > - where w = case os of
> > - OP_STATUS_QUEUED -> C.opStatusQueued
> > - OP_STATUS_WAITING -> C.opStatusWaiting
> > - OP_STATUS_CANCELING -> C.opStatusCanceling
> > - OP_STATUS_RUNNING -> C.opStatusRunning
> > - OP_STATUS_CANCELED -> C.opStatusCanceled
> > - OP_STATUS_SUCCESS -> C.opStatusSuccess
> > - OP_STATUS_ERROR -> C.opStatusError
> > - readJSON s = case readJSON s of
> > - J.Ok v | v == C.opStatusQueued -> J.Ok OP_STATUS_QUEUED
> > - | v == C.opStatusWaiting -> J.Ok OP_STATUS_WAITING
> > - | v == C.opStatusCanceling -> J.Ok OP_STATUS_CANCELING
> > - | v == C.opStatusRunning -> J.Ok OP_STATUS_RUNNING
> > - | v == C.opStatusCanceled -> J.Ok OP_STATUS_CANCELED
> > - | v == C.opStatusSuccess -> J.Ok OP_STATUS_SUCCESS
> > - | v == C.opStatusError -> J.Ok OP_STATUS_ERROR
> > - | otherwise -> J.Error ("Unknown opcode status " ++ v)
> > - _ -> J.Error ("Cannot parse opcode status " ++ show s)
> > +$(THH.declareSADT "OpStatus"
> > + [ ("OP_STATUS_QUEUED", 'C.opStatusQueued)
> > + , ("OP_STATUS_WAITING", 'C.opStatusWaiting)
> > + , ("OP_STATUS_CANCELING", 'C.opStatusCanceling)
> > + , ("OP_STATUS_RUNNING", 'C.opStatusRunning)
> > + , ("OP_STATUS_CANCELED", 'C.opStatusCanceled)
> > + , ("OP_STATUS_SUCCESS", 'C.opStatusSuccess)
> > + , ("OP_STATUS_ERROR", 'C.opStatusError)
> > + ])
> > +$(THH.makeJSONInstance ''OpStatus)
> >
> > -- | The JobStatus data type. Note that this is ordered especially
> > -- such that greater\/lesser comparison on values of this type makes
> > -- sense.
> > -data JobStatus = JOB_STATUS_QUEUED
> > - | JOB_STATUS_WAITING
> > - | JOB_STATUS_RUNNING
> > - | JOB_STATUS_SUCCESS
> > - | JOB_STATUS_CANCELING
> > - | JOB_STATUS_CANCELED
> > - | JOB_STATUS_ERROR
> > - deriving (Eq, Enum, Ord, Bounded, Show, Read)
> > -
> > -instance JSON JobStatus where
> > - showJSON js = showJSON w
> > - where w = case js of
> > - JOB_STATUS_QUEUED -> C.jobStatusQueued
> > - JOB_STATUS_WAITING -> C.jobStatusWaiting
> > - JOB_STATUS_CANCELING -> C.jobStatusCanceling
> > - JOB_STATUS_RUNNING -> C.jobStatusRunning
> > - JOB_STATUS_CANCELED -> C.jobStatusCanceled
> > - JOB_STATUS_SUCCESS -> C.jobStatusSuccess
> > - JOB_STATUS_ERROR -> C.jobStatusError
> > - readJSON s = case readJSON s of
> > - J.Ok v | v == C.jobStatusQueued -> J.Ok JOB_STATUS_QUEUED
> > - | v == C.jobStatusWaiting -> J.Ok JOB_STATUS_WAITING
> > - | v == C.jobStatusCanceling -> J.Ok JOB_STATUS_CANCELING
> > - | v == C.jobStatusRunning -> J.Ok JOB_STATUS_RUNNING
> > - | v == C.jobStatusSuccess -> J.Ok JOB_STATUS_SUCCESS
> > - | v == C.jobStatusCanceled -> J.Ok JOB_STATUS_CANCELED
> > - | v == C.jobStatusError -> J.Ok JOB_STATUS_ERROR
> > - | otherwise -> J.Error ("Unknown job status " ++ v)
> > - _ -> J.Error ("Unknown job status " ++ show s)
> > +$(THH.declareSADT "JobStatus"
> > + [ ("JOB_STATUS_QUEUED", 'C.jobStatusQueued)
> > + , ("JOB_STATUS_WAITING", 'C.jobStatusWaiting)
> > + , ("JOB_STATUS_CANCELING", 'C.jobStatusCanceling)
> > + , ("JOB_STATUS_RUNNING", 'C.jobStatusRunning)
> > + , ("JOB_STATUS_CANCELED", 'C.jobStatusCanceled)
> > + , ("JOB_STATUS_SUCCESS", 'C.jobStatusSuccess)
> > + , ("JOB_STATUS_ERROR", 'C.jobStatusError)
> > + ])
> > +$(THH.makeJSONInstance ''JobStatus)
> > diff --git a/htools/Ganeti/OpCodes.hs b/htools/Ganeti/OpCodes.hs
> > index 0930598..b3fc1f3 100644
> > --- a/htools/Ganeti/OpCodes.hs
> > +++ b/htools/Ganeti/OpCodes.hs
> > @@ -1,3 +1,5 @@
> > +{-# LANGUAGE TemplateHaskell #-}
> > +
> > {-| Implementation of the opcodes.
> >
> > -}
> > @@ -34,27 +36,19 @@ import Text.JSON (readJSON, showJSON, makeObj, JSON)
> > import qualified Text.JSON as J
> > import Text.JSON.Types
> >
> > +import qualified Ganeti.Constants as C
> > +import qualified Ganeti.THH as THH
> > +
> > import Ganeti.HTools.Utils
> >
> > -- | Replace disks type.
> > -data ReplaceDisksMode = ReplaceOnPrimary
> > - | ReplaceOnSecondary
> > - | ReplaceNewSecondary
> > - | ReplaceAuto
> > - deriving (Show, Read, Eq)
> > -
> > -instance JSON ReplaceDisksMode where
> > - showJSON m = case m of
> > - ReplaceOnPrimary -> showJSON "replace_on_primary"
> > - ReplaceOnSecondary -> showJSON "replace_on_secondary"
> > - ReplaceNewSecondary -> showJSON "replace_new_secondary"
> > - ReplaceAuto -> showJSON "replace_auto"
> > - readJSON s = case readJSON s of
> > - J.Ok "replace_on_primary" -> J.Ok ReplaceOnPrimary
> > - J.Ok "replace_on_secondary" -> J.Ok ReplaceOnSecondary
> > - J.Ok "replace_new_secondary" -> J.Ok ReplaceNewSecondary
> > - J.Ok "replace_auto" -> J.Ok ReplaceAuto
> > - _ -> J.Error "Can't parse a valid ReplaceDisksMode"
> > +$(THH.declareSADT "ReplaceDisksMode"
> > + [ ("ReplaceOnPrimary", 'C.replaceDiskPri)
> > + , ("ReplaceOnSecondary", 'C.replaceDiskSec)
> > + , ("ReplaceNewSecondary", 'C.replaceDiskChg)
> > + , ("ReplaceAuto", 'C.replaceDiskAuto)
> > + ])
> > +$(THH.makeJSONInstance ''ReplaceDisksMode)
> >
> > -- | OpCode representation.
> > --
> > diff --git a/htools/Ganeti/THH.hs b/htools/Ganeti/THH.hs
> > new file mode 100644
> > index 0000000..fe5e4ae
> > --- /dev/null
> > +++ b/htools/Ganeti/THH.hs
> > @@ -0,0 +1,187 @@
> > +{-# LANGUAGE TemplateHaskell, QuasiQuotes #-}
> > +
> > +{-| TemplateHaskell helper for HTools.
> > +
> > +As TemplateHaskell require that splices be defined in a separate
> > +module, we combine all the TemplateHaskell functionality that HTools
> > +needs in this module (except the one for unittests).
> > +
> > +-}
> > +
> > +{-
> > +
> > +Copyright (C) 2011 Google Inc.
> > +
> > +This program is free software; you can redistribute it and/or modify
> > +it under the terms of the GNU General Public License as published by
> > +the Free Software Foundation; either version 2 of the License, or
> > +(at your option) any later version.
> > +
> > +This program is distributed in the hope that it will be useful, but
> > +WITHOUT ANY WARRANTY; without even the implied warranty of
> > +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> > +General Public License for more details.
> > +
> > +You should have received a copy of the GNU General Public License
> > +along with this program; if not, write to the Free Software
> > +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
> > +02110-1301, USA.
> > +
> > +-}
> > +
> > +module Ganeti.THH ( declareSADT
> > + , makeJSONInstance
> > + ) where
> > +
> > +import Data.Char
> > +import Language.Haskell.TH
> > +
> > +import qualified Text.JSON as JSON
> > +
> > +-- | Ensure first letter is lowercase.
> > +--
> > +-- Used to convert type name to function prefix, e.g. in @data Aa ->
> > +-- aaToString@.
> > +ensureLower :: String -> String
> > +ensureLower [] = []
> > +ensureLower (x:xs) = toLower x:xs
> > +
> > +-- | ToString function name.
> > +toStrName :: String -> Name
> > +toStrName = mkName . (++ "ToString") . ensureLower
> > +
> > +-- | FromString function name.
> > +fromStrName :: String -> Name
> > +fromStrName = mkName . (++ "FromString") . ensureLower
> > +
> > +-- | Generates a data type declaration.
> > +--
> > +-- The type will have a fixed list of instances
> > +strADTDecl :: String -> [String] -> Dec
> > +strADTDecl name constructors =
> > + DataD [] (mkName name) []
> > + (map (flip NormalC [] . mkName) constructors)
> > + [''Show, ''Read, ''Eq, ''Enum, ''Bounded, ''Ord]
> > +
> I take that the first argument is not a Name for consistency with the
> second argument?
Actually, that was not intended; probably a left over from a previous
version of the patch. Since the caller already has it transformed to a
Name, I'll pass the name in, thanks.
> > +-- | Generates a toString function.
> > +--
> > +-- This generates a simple function of the form:
> > +--
> > +-- @
> > +-- nameToString :: Name -> String
> > +-- nameToString Cons1 = var1
> > +-- nameToString Cons2 = \"value2\"
> > +-- @
> > +genToString :: Name -> Name -> [(String, Name)] -> Q [Dec]
> > +genToString fname tname constructors = do
> > + sigt <- [t| $(conT tname) -> String |]
> > + -- the body clauses, matching on the constructor and returning the
> > + -- string value
> > + clauses <- mapM (\(c, v) -> clause [recP (mkName c) []]
> > + (normalB (varE v)) []) constructors
> > + return [SigD fname sigt, FunD fname clauses]
> > +
> > +-- | Generates a fromString function.
> > +--
> > +-- The function generated is monadic and can fail parsing the
> > +-- string. It is of the form:
> > +--
> > +-- @
> > +-- nameFromString :: (Monad m) => String -> m Name
> > +-- nameFromString s | s == var1 = Cons1
> > +-- | s == \"value2\" = Cons2
> > +-- | otherwise = fail /.../
> > +-- @
> > +genFromString :: Name -> Name -> [(String, Name)] -> Q [Dec]
> > +genFromString fname tname constructors = do
> > + -- signature of form (Monad m) => String -> m $name
> > + sigt <- [t| (Monad m) => String -> m $(conT tname) |]
> > + -- clauses for a guarded pattern
> > + let varp = mkName "s"
> > + varpe = varE varp
> > + clauses <- mapM (\(c, v) -> do
> > + -- the clause match condition
> > + g <- normalG [| $varpe == $(varE v) |]
> > + -- the clause result
> > + r <- [| return $(conE (mkName c)) |]
> > + return (g, r)) constructors
> > + -- the otherwise clause (fallback)
> > + oth_clause <- do
> > + g <- normalG [| otherwise |]
> > + r <- [|fail ("Invalid string value for type " ++
> > + $(litE (stringL (nameBase tname))) ++ ": " ++ $varpe) |]
> > + return (g, r)
> > + let fun = FunD fname [Clause [VarP varp]
> > + (GuardedB (clauses++[oth_clause])) []]
> > + return [SigD fname sigt, fun]
> > +
> > +-- | Generates a data type from a given string format.
> > +--
> > +-- The format is expected to multiline. The first line contains the
> > +-- type name, and the rest of the lines must contain two words: the
> > +-- constructor name and then the string representation of the
> > +-- respective constructor.
> > +--
> > +-- The function will generate the data type declaration, and then two
> > +-- functions:
> > +--
> > +-- * /name/ToString, which converts the type to a string
> > +--
> > +-- * /name/FromString, which (monadically) converts from a string to the
> > type
> > +--
> > +-- Note that this is basically just a custom show/read instance,
> > +-- nothing else.
> > +declareSADT :: String -> [(String, Name)] -> Q [Dec]
> > +declareSADT sname cons = do
> > + let name = mkName sname
> > + let ddecl = strADTDecl sname (map fst cons)
> > + tostr <- genToString (toStrName sname) name cons
> > + fromstr <- genFromString (fromStrName sname) name cons
> > + return $ ddecl:tostr ++ fromstr
> The second 'let' seems redundant, I think? (just let, not ddecl ;) )
Indeed. I usually use such redundant lets during development because
then you can move each individual line around.
> > +
> > +
> > +-- | Creates the showJSON member of a JSON instance declaration.
> > +--
> > +-- This will create what is the equivalent of:
> > +--
> > +-- @
> > +-- showJSON = showJSON . /name/ToString
> > +-- @
> > +--
> > +-- in an instance JSON /name/ declaration
> > +genShowJSON :: String -> Q [Dec]
> > +genShowJSON name = [d| showJSON = JSON.showJSON . $(varE (toStrName name))
> > |]
> > +
> > +-- | Creates the readJSON member of a JSON instance declaration.
> > +--
> > +-- This will create what is the equivalent of:
> > +--
> > +-- @
> > +-- readJSON s = case readJSON s of
> > +-- Ok s' -> /name/FromString s'
> > +-- Error e -> Error /description/
> > +-- @
> > +--
> > +-- in an instance JSON /name/ declaration
> > +genReadJSON :: String -> Q Dec
> > +genReadJSON name = do
> > + let s = mkName "s"
> > + body <- [| case JSON.readJSON $(varE s) of
> > + JSON.Ok s' -> $(varE (fromStrName name)) s'
> > + JSON.Error e ->
> > + JSON.Error $ "Can't parse string value for type " ++
> > + $(litE (StringL name)) ++ ": " ++ e
> > + |]
> > + return $ FunD (mkName "readJSON") [Clause [VarP s] (NormalB body) []]
> > +
> > +-- | Generates a JSON instance for a given type.
> > +--
> > +-- This assumes that the /name/ToString and /name/FromString functions
> > +-- have been defined as by the 'declareSADT' function.
> > +makeJSONInstance :: Name -> Q [Dec]
> > +makeJSONInstance name = do
> > + let base = nameBase name
> > + showJ <- genShowJSON base
> > + readJ <- genReadJSON base
> > +
> > + return [InstanceD [] (AppT (ConT ''JSON.JSON) (ConT name)) (readJ:showJ)]
> Hmm no newline before return, I think
Ack.
Interdiff (also fixes one missing dot in docstring):
diff --git a/htools/Ganeti/THH.hs b/htools/Ganeti/THH.hs
index fe5e4ae..6e539a4 100644
--- a/htools/Ganeti/THH.hs
+++ b/htools/Ganeti/THH.hs
@@ -56,10 +56,10 @@ fromStrName = mkName . (++ "FromString") . ensureLower
-- | Generates a data type declaration.
--
--- The type will have a fixed list of instances
-strADTDecl :: String -> [String] -> Dec
+-- The type will have a fixed list of instances.
+strADTDecl :: Name -> [String] -> Dec
strADTDecl name constructors =
- DataD [] (mkName name) []
+ DataD [] name []
(map (flip NormalC [] . mkName) constructors)
[''Show, ''Read, ''Eq, ''Enum, ''Bounded, ''Ord]
@@ -134,7 +134,7 @@ genFromString fname tname constructors = do
declareSADT :: String -> [(String, Name)] -> Q [Dec]
declareSADT sname cons = do
let name = mkName sname
- let ddecl = strADTDecl sname (map fst cons)
+ ddecl = strADTDecl name (map fst cons)
tostr <- genToString (toStrName sname) name cons
fromstr <- genFromString (fromStrName sname) name cons
return $ ddecl:tostr ++ fromstr
@@ -183,5 +183,4 @@ makeJSONInstance name = do
let base = nameBase name
showJ <- genShowJSON base
readJ <- genReadJSON base
-
return [InstanceD [] (AppT (ConT ''JSON.JSON) (ConT name)) (readJ:showJ)]
--
iustin