I seem to be coming across mapping keywords straight to a datatype fairly often and I solve it as below. It can quickly get out of hand as you have to repeat the string values.
Is there a more compact way to express this?
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
EDIT:
As a followup question, since this seemed to be too easy. How would the compact solution work with 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
I’m not sure this is a terribly elegant solution, but if you derive a few more typeclasses:
data Keyword = Apple | Banana | Cantaloupe deriving (Eq, Read, Show, Enum, Bounded)
You can suddenly get all of the values:
ghci> [minBound..maxBound] :: [Keyword]
[Apple,Banana,Cantaloupe]
For any particular value, we can parse it and then return the value:
parseEnumValue :: (Show a) => a -> Parser a
parseEnumValue val = string (map toLower $ show val) >> return val
Then we can combine these to parse any value of it:
parseEnum :: (Show a, Enum a, Bounded a) => Parser a
parseEnum = choice $ map parseEnumValue [minBound..maxBound]
Try it out:
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"