在Haskell Attoparsec中的制表符或换行符之前,如何使用takeTill? (布尔表达式的问题)

时间:2014-03-13 16:48:53

标签: haskell boolean-expression attoparsec

我正在编写我的第一个Haskell程序。该程序解析普通的CSV文件,但我遇到了许多问题,这些问题无疑源于我对语法的经验不足。

目前,代码成功解析了一条记录,但在最终记录中,解析器占用换行符,因此不会在后续行上处理记录。

我建议的解决方案是在我的fieldData规范中添加一个检查以检查“takeTill”标签或换行符,但我不知道如何执行此操作。

当前代码:

fieldData = takeTill (== '\t')

尝试:

fieldData = takeTill (== '\t' || '\n') -- wrong, something about infix precedence
fieldData = takeTill (== ('\t' || '\n')) -- wrong, type error
fieldData = takeTill ((== '\t') || (== '\n')) -- wrong, type error
fieldData x = takeTill ((x == '\t') || (x == '\n')) -- wrong, type error
fieldData x = takeTill x ((x == '\t') || (x == '\n')) -- wrong, not enough arguments

我觉得我对如何在Haskell中构造布尔条件有一些基本的误解,并希望得到帮助。例如,在ghci中,我可以let fun x = (x == 'a' || x == 'b')并且它可以很好地匹配不同的字符,所以在使用函数时,我显然遗漏了一些东西。

或者,这甚至是正确的方法吗?如果这不是解决问题的正确方法,我会很感激指向"纠正"方式。

完整的代码如下:

{- Parsing a tab-separated file using Attoparsec.
A record contains:
number\tname\tgenre\tabilities\tweapon\n

-}
import System.FilePath.Posix
import Data.Attoparsec.Char8
import Control.Applicative
import qualified Data.ByteString as B
import qualified Data.ByteString.Char8 as C

data AbilitiesList = AbilitiesList String deriving Show

data PlayerCharacter = PlayerCharacter {
    id :: Integer,
    name :: String,
    genre :: String,
    abilities :: AbilitiesList,
    weapon :: String
} deriving Show

type Players = [PlayerCharacter]

fieldData = takeTill (== '\t')
tab = char '\t'

parseCharacter :: Parser PlayerCharacter
parseCharacter = do
    id <- decimal
    tab
    name <- fieldData
    tab
    genre <- fieldData
    tab
    abilities <- fieldData
    tab
    weapon <- fieldData
    return $ PlayerCharacter id (C.unpack name) (C.unpack genre) (AbilitiesList (C.unpack abilities)) (C.unpack weapon)

abilitiesFile :: FilePath
abilitiesFile = joinPath ["data", "ff_abilities.txt"]

playerParser :: Parser Players
playerParser = many $ parseCharacter <* endOfLine

main :: IO ()
main = B.readFile abilitiesFile >>= print . parseOnly playerParser

2 个答案:

答案 0 :(得分:2)

为此您可能想要使用lambda:

takeTill (\x -> x == '\t' || x == '\n')

lambda函数是一种匿名的一次性内联函数。您可以像普通函数一样使用它们,除非它们没有绑定到名称。

您还可以定义一个功能

tabOrNL :: Char -> Bool
tabOrNL '\t' = True
tabOrNL '\n' = True
tabOrNL _    = False

-- Or equivalently

tabOrNL :: Char -> Bool
tabOrNL x = x == '\t' || x == '\n'

然后你可以做

takeTill tabOrNL

如果你想变得非常喜欢,函数的Applicative实例可以在这里派上用场:

(<||>) :: Applicative f => f Bool -> f Bool -> f Bool
(<||>) = liftA2 (||)
infixr 2 <||>

然后你可以做

takeTill ((== '\t') <||> (== '\n'))

甚至

takeTill ((== '\t') <||> (== '\n') <||> (== ','))

这样你可以完全避免使用lambda或helper函数,<||>可以让你只是&#34;或者在一起&#34;几个谓词好像它们是价值观。您可以使用(<&&>) = liftA2 (&&)执行类似操作,但在此处可能对您没有用。

答案 1 :(得分:2)

另一种解决方案是使用elem检查字符是否在列表中:

takeTill (`elem` "\t\n")

虽然我只推荐@ bheklilr的解决方案,以便查看更多值的案例。