我似乎经常将关键字直接映射到数据类型,我解决它如下所示。当你必须重复字符串值时,它很快就会失控。
有更简洁的表达方式吗?
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
编辑:
作为后续问题,因为这似乎太容易了。紧凑型解决方案如何与try
一起使用?
E.g。
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
答案 0 :(得分:9)
如果您只是想避免重复,可以使用(<$)
运算符:
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"
对于只使用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
这是很多样板文件,但现在你只需要为任何兼容类型创建一个解析器:
{-# LANGUAGE DeriveGeneric #-}
data Keyword = Apricot | Apple | Banana | Cantaloupe deriving (Show, Generic)
parseKeyword :: Parser Keyword
parseKeyword = genericParser
GHCI测试:
> parseTest parseKeyword "apple"
Apple
> parseTest parseKeyword "apricot"
Apricot
> parseTest parseKeyword "banana"
Banana
处理像RedApple
这样的多字构造函数只是为"RedApple"
- &gt;编写字符串翻译函数的问题。 "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))
答案 1 :(得分:5)
我不确定这是一个非常优雅的解决方案,但是如果你派出更多的类型类:
data Keyword = Apple | Banana | Cantaloupe deriving (Eq, Read, Show, Enum, Bounded)
您可以突然获得所有值:
ghci> [minBound..maxBound] :: [Keyword]
[Apple,Banana,Cantaloupe]
对于任何特定值,我们可以解析它然后返回值:
parseEnumValue :: (Show a) => a -> Parser a
parseEnumValue val = string (map toLower $ show val) >> return val
然后我们可以将它们组合起来解析它的任何值:
parseEnum :: (Show a, Enum a, Bounded a) => Parser a
parseEnum = choice $ map parseEnumValue [minBound..maxBound]
尝试一下:
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"
答案 2 :(得分:5)
这个怎么样?
parseKeyword
= (string "apple" >> return Apple)
<|> (string "banana" >> return Banana)
<|> (string "cantaloupe" >> return Cantaloupe)
对于你的后续行动,这似乎与我执行的六个随机测试的实现一样好
parseKeyword :: Parser Keyword
parseKeyword
= try (string "apple" >> return Apple)
<|> (string "apricot" >> return Apricot)
<|> (string "banana" >> return Banana)
<|> (string "cantaloupe" >> return Cantaloupe)
该技术只是使每个子表达式返回最终类型,而不是将其委托给case语句的块结尾。返回不会改变解析器的行为。