在C中,我们以这种方式定义枚举:
enum E {
E0,
E1,
E2 = 3,
E3
};
Run Code Online (Sandbox Code Playgroud)
注意E2 = 3表达式,枚举类型结果E0 == 0, E1 == 1, E2 == 3, E3 == 4.
在Haskell中,我们无法在声明中指定枚举.实现不连续枚举的唯一方法是Enum手动实现类.
有没有方便的方法呢?
我使用Template Haskell 编写了一个演示来生成Enum实例.
data E = E0
| E1
| E2_3
| E3
deriving Show
enum ''E
Run Code Online (Sandbox Code Playgroud)
我想知道是否有图书馆试图填补这个空白?
你可以使用Template Haskell的reifyAnnotations功能来简单小巧.
首先,我们需要定义一个注释类型来保存枚举值:
{-# LANGUAGE DeriveDataTypeable #-}
module Def where
import Data.Data
data EnumValue = EnumValue Int deriving (Typeable, Data)
Run Code Online (Sandbox Code Playgroud)
其次,我们需要一些TH代码来使用这些注释并将它们转换为Enum实例定义:
{-# LANGUAGE TemplateHaskell, QuasiQuotes #-}
module TH where
import Def
import Language.Haskell.TH.Syntax
import Language.Haskell.TH
import Control.Monad
import Data.List (mapAccumL)
import Data.Maybe
enumValues :: [(a, Maybe Int)] -> [(a, Int)]
enumValues = snd . mapAccumL (\next (x, mv) -> let v = fromMaybe next mv in (v+1, (x, v))) 0
enumFromAnns :: Name -> Q [Dec]
enumFromAnns name = do
TyConI (DataD _ _ _ cons _) <- reify name
eVals <- fmap enumValues $ forM cons $ \(NormalC conName []) -> do
anns <- reifyAnnotations (AnnLookupName conName)
let ev = case anns of
[EnumValue ev] -> Just ev
[] -> Nothing
return (conName, ev)
[d|
instance Enum $(conT name) where
fromEnum = $(lamCaseE [match (conP c []) (normalB $ lift v) [] | (c, v) <- eVals])
toEnum = $(lamCaseE [match (litP . IntegerL . fromIntegral $ v) (normalB $ conE c) [] | (c, v) <- eVals])|]
Run Code Online (Sandbox Code Playgroud)
最后我们可以使用它(通过一个小的解决方法来确保使用在新的声明组中):
{-# LANGUAGE TemplateHaskell #-}
module AnnotatedEnumExample where
import Def
import TH
data E = E1
| E2
| E42
| E43
deriving Show
{-# ANN E1 (EnumValue 1) #-}
{-# ANN E42 (EnumValue 42) #-}
-- Force new declaration group
return []
enumFromAnns ''E
Run Code Online (Sandbox Code Playgroud)
用法示例:
Run Code Online (Sandbox Code Playgroud)*AnnotatedEnumExample> map fromEnum [E1, E2, E42, E43] [1,2,42,43] *AnnotatedEnumExample> map toEnum [1, 2, 42, 43] :: [E] [E1,E2,E42,E43]