1

私は最初の Haskell プログラムを書いています。このプログラムは通常の CSV ファイルを解析しますが、多くの問題に直面しています。これは、私の構文の経験不足に起因するものと思われます。

現在、コードは 1 つのレコードを正常に解析しますが、最後のレコードでは、パーサーが改行を取り上げるため、後続の行のレコードを処理しません。

私の提案する解決策は、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

これには、おそらくラムダを使用する必要があります。

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

ラムダ関数は、匿名の 1 回限りのインライン関数です。名前にバインドされていないことを除けば、通常の関数と同じように使用できます。

関数を定義することもできます

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') <||> (== ','))

そうすれば、ラムダ関数またはヘルパー関数を完全に回避<||>でき、複数の述語を値であるかのように「または一緒に」することができます。と同様に行うことができますが(<&&>) = liftA2 (&&)、ここではおそらくそれほど役に立ちません。

于 2014-03-13T17:25:03.150 に答える