1

我正在编写我的第一个 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
4

2 回答 2

2

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

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

虽然我只推荐它而不是@bheklilr 的解决方案,用于检查更多值的情况。

于 2014-03-13T17:52:59.580 回答
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 或辅助函数,<||>让您可以“或一起”几个谓词,就好像它们是值一样。您可以使用 进行类似的操作(<&&>) = liftA2 (&&),但在这里它可能对您没有那么有用。

于 2014-03-13T17:25:03.150 回答