我似乎经常将关键字直接映射到数据类型,我解决它如下所示.由于必须重复字符串值,因此很快就会失控.
是否有更紧凑的表达方式?
import Text.ParserCombinators.Parsec
data Keyword = Apple | Banana | Cantaloupe
parseKeyword :: Parser Keyword
parseKeyword = ( string "apple"
<|> string "banana"
<|> string "cantaloupe"
) >>= return . strToKeyword
where strToKeyword str = case str of
"apple" -> Apple
"banana" -> Banana
"cantaloupe" -> Cantaloupe
Run Code Online (Sandbox Code Playgroud)
编辑:
作为后续问题,因为这似乎太容易了.紧凑型解决方案如何工作try?
例如
import Text.ParserCombinators.Parsec
data Keyword = Apple | Apricot | Banana | Cantaloupe
parseKeyword :: Parser Keyword
parseKeyword = ( try (string "apple")
<|> string "apricot"
<|> string "banana"
<|> string "cantaloupe"
) >>= return . strToKeyword
where strToKeyword str = case str of
"apple" -> Apple
"apricot" -> Apricot
"banana" -> Banana
"cantaloupe" -> Cantaloupe
Run Code Online (Sandbox Code Playgroud)
如果您只是想避免重复,可以使用(<$)运算符:
import Text.ParserCombinators.Parsec
import Control.Applicative ((<$))
data Keyword = Apple | Banana | Cantaloupe
parseKeyword :: Parser Keyword
parseKeyword
= Apple <$ string "apple"
<|> Banana <$ string "banana"
<|> Cantaloupe <$ string "cantaloupe"
Run Code Online (Sandbox Code Playgroud)
对于只有单元构造函数的任何类型,我们也可以使用以下方法制作完全通用的解决方案GHC.Generics:
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators #-}
import Text.ParserCombinators.Parsec
import Control.Applicative ((<*))
import Data.Char (toLower)
import GHC.Generics
class GParse f where
gParse :: Parser (f a)
instance (GParse f, Constructor c) => GParse (C1 c f) where
gParse = fmap M1 gParse <* string (map toLower $ conName (undefined :: t c f a))
instance GParse f => GParse (D1 c f) where
gParse = fmap M1 gParse
instance (GParse a, GParse b) => GParse (a :+: b) where
gParse = try (fmap L1 gParse) <|> fmap R1 gParse
instance GParse U1 where
gParse = return U1
genericParser :: (Generic g, GParse (Rep g)) => Parser g
genericParser = fmap to gParse
Run Code Online (Sandbox Code Playgroud)
这是相当多的样板,但现在你可以为任何兼容类型创建一个解析器:
{-# LANGUAGE DeriveGeneric #-}
data Keyword = Apricot | Apple | Banana | Cantaloupe deriving (Show, Generic)
parseKeyword :: Parser Keyword
parseKeyword = genericParser
Run Code Online (Sandbox Code Playgroud)
GHCI测试:
> parseTest parseKeyword "apple"
Apple
> parseTest parseKeyword "apricot"
Apricot
> parseTest parseKeyword "banana"
Banana
Run Code Online (Sandbox Code Playgroud)
处理多字构造函数RedApple只是为"RedApple"- > 编写字符串转换函数"red_apple"并在C1实例中使用它.即
import Data.List (intercalate)
import Data.Char (toLower, isLower)
mapName :: String -> String
mapName = intercalate "_" . splitCapWords where
splitCapWords "" = []
splitCapWords (x:xs) =
let (word, rest) = span isLower xs
in (toLower x : word) : splitCapWords rest
instance (GParse f, Constructor c) => GParse (C1 c f) where
gParse = fmap M1 gParse <* string (mapName $ conName (undefined :: t c f a))
Run Code Online (Sandbox Code Playgroud)
我不确定这是一个非常优雅的解决方案,但是如果你得到更多的类型类:
data Keyword = Apple | Banana | Cantaloupe deriving (Eq, Read, Show, Enum, Bounded)
Run Code Online (Sandbox Code Playgroud)
您可以突然获得所有值:
ghci> [minBound..maxBound] :: [Keyword]
[Apple,Banana,Cantaloupe]
Run Code Online (Sandbox Code Playgroud)
对于任何特定值,我们可以解析它然后返回值:
parseEnumValue :: (Show a) => a -> Parser a
parseEnumValue val = string (map toLower $ show val) >> return val
Run Code Online (Sandbox Code Playgroud)
然后我们可以将它们组合起来解析它的任何值:
parseEnum :: (Show a, Enum a, Bounded a) => Parser a
parseEnum = choice $ map parseEnumValue [minBound..maxBound]
Run Code Online (Sandbox Code Playgroud)
试试看:
ghci> parseTest (parseEnum :: Parser Keyword) "cantaloupe"
Cantaloupe
ghci> parseTest (parseEnum :: Parser Keyword) "orange"
parse error at (line 1, column 1):
unexpected "o"
expecting "apple", "banana" or "cantaloupe"
Run Code Online (Sandbox Code Playgroud)
这个怎么样?
parseKeyword
= (string "apple" >> return Apple)
<|> (string "banana" >> return Banana)
<|> (string "cantaloupe" >> return Cantaloupe)
Run Code Online (Sandbox Code Playgroud)
对于你的后续工作,这似乎与我执行的六个随机测试的实现一样好
parseKeyword :: Parser Keyword
parseKeyword
= try (string "apple" >> return Apple)
<|> (string "apricot" >> return Apricot)
<|> (string "banana" >> return Banana)
<|> (string "cantaloupe" >> return Cantaloupe)
Run Code Online (Sandbox Code Playgroud)
该技术只是使每个子表达式返回最终类型,而不是将其委托给case语句的块结尾.返回不会更改解析器的行为.