2016-07-11 7 views
6

Ich muss eine Datenstruktur aus einer Textdatei (Leerzeichen getrennt), ein Datenelement pro Zeile lesen. Meine erste vorläufige würdeLesen lange Datenstruktur in Haskell

data Person = Person {name :: String, surname :: String, age :: Int, ... dozens of other fields} deriving (Show,...) 

main = do 
    string <- readFile "filename.txt" 
    let people = readPeople string 
    do_something people 

readPeople s = map (readPerson.words) (lines s) 

readPerson row = Person (read(row!!0)) (read(row!!1)) (read(row!!2)) (read(row!!3)) ... (read(row!!dozens)) 

sein Dieser Code funktioniert, aber der Code für readPerson ist schrecklich: Ich habe das read(row!!n)) für alle Felder in meiner Datenstruktur kopieren und einfügen!

So, als ein zweiter Versuch, ich denke, dass ich Currying der Person Funktion ausnutzen könnte, und übergeben Sie die Argumente zu der Zeit.

Uhm, muss es etwas in Hoogle sein, aber ich kann nicht die Art Signatur herauszufinden ... Es macht nichts, es sieht einfach genug, und ich kann es selbst schreiben:

readPerson row = readFields Person row 

readFields f [x] = (f x) 
readFields f (x:xs) = readFields (f (read x)) xs 

Ahh, sieht viel besser aus Kodierungsstil!

Aber es kompiliert nicht! Occurs check: cannot construct the infinite type: t ~ String -> t

Tat die Funktion f ich bin readFields vorbei hat eine andere Art Signatur in jedem Aufruf; deshalb konnte ich seine Signatur nicht erkennen ...

Meine Frage ist also: Was ist die einfachste und eleganteste Art, eine Datenstruktur mit vielen Feldern zu lesen?

+1

siehe http://stackoverflow.com/questions/38271220/constructing-haskell-data-types-with-many-fields –

+1

Hat 'Person' Felder mit verschiedenen Typen? Wenn ja, gibt es wahrscheinlich keine wirklich einfache Möglichkeit, dieses Problem zu beheben ... – Bakuriu

Antwort

2

EDIT: Einfachere Lösung, wenn Sie von Strings lesen:

{-# LANGUAGE FlexibleInstances #-} 

data Person = Person { name :: String, age :: Int, height :: Double } 
    deriving Show 

class Person' a where 
    person :: a -> [String] -> Maybe Person 

instance Person' Person where 
    person c [] = Just c 
    person _ _ = Nothing 

instance (Read a, Person' b) => Person' (a -> b) where 
    person f (x:xs) = person (f $ read x) xs 
    person _ _  = Nothing 

instance {-# OVERLAPPING #-} Person' a => Person' (String -> a) where 
    person f (x:xs) = person (f x) xs 
    person _ _  = Nothing 

dann, wenn die Liste der richtigen Größe erhalten Sie:

\> person Person $ words "John 42 6.05" 
Just (Person {name = "John", age = 42, height = 6.05}) 

und wenn nicht Sie nichts bekommen:

\> person Person $ words "John 42" 
Nothing 

Constructing Haskell data types with many fields bietet eine Lösung, wenn alle Datensatzfelder vom selben Typ sind. Wenn sie nicht, eine etwas polymorphe Lösung sind sein:

{-# LANGUAGE FlexibleInstances, CPP #-} 

data Person = Person { name :: String, age :: Int, height :: Double } 
    deriving Show 

data Val = IVal Int | DVal Double | SVal String 

class Person' a where 
    person :: a -> [Val] -> Maybe Person 

instance Person' Person where 
    person c [] = Just c 
    person _ _ = Nothing 

#define PERSON(t, n)        \ 
instance (Person' a) => Person' (t -> a) where { \ 
    person f ((n i):xs) = person (f i) xs;   \ 
    person _ _ = Nothing; }       \ 

PERSON(Int, IVal) 
PERSON(Double, DVal) 
PERSON(String, SVal) 

dann,

\> person Person [SVal "John", IVal 42, DVal 6.05] 
Just (Person {name = "John", age = 42, height = 6.05}) 

Um Val Typen wählen, Sie einen anderen Typ-Klasse erstellen können und die gewünschten Instanzen machen:

class Cast a where 
    cast :: a -> Val 

instance Cast Int where cast = IVal 
instance Cast Double where cast = DVal 
instance Cast String where cast = SVal 

dann wäre es etwas einfache Notation sein:

\> person Person [cast "John", cast (42 :: Int), cast 6.05] 
Just (Person {name = "John", age = 42, height = 6.05}) 
3

Zunächst ist es immer eine gute Methode, Typen für alle Deklarationen der obersten Ebene einzubeziehen. Es macht den Code besser strukturiert und viel lesbarer.

Eine einfache Möglichkeit, dies zu erreichen, ist die Nutzung von applicative functors. Während des Parsens haben Sie eine "effektvolle" Berechnung, bei der der Effekt einen Teil der Eingabe verbraucht und sein Ergebnis ein geparstes Stück ist.Wir können die State Monade verwenden, um den verbleibenden Eingang zu verfolgen und eine polymorphe Funktion erstellen, die ein Element des Eingangs und read s verbraucht:

import Control.Applicative 
import Control.Monad.State 

data Person = Person { name :: String, surname :: String, age :: Int } 
    deriving (Eq, Ord, Show, Read) 

readField :: (Read a) => State [String] a 
readField = state $ \(x : xs) -> (read x, xs) 

Und um viele solche Felder analysieren wir verwenden die <$> und <*> Kombinatoren, die Operationen sequenzieren ermöglichen, wie folgt:

readPerson :: [String] -> Person 
readPerson = evalState $ Person <$> readField <*> readField <*> readField 

Expression Person <$> ... vom Typ State [String] Person und wir führen evalState auf gegebenen Eingang die Stateful Berechnung ausgeführt und die Ausgabe zu extrahieren. Wir müssen immer noch die gleiche Anzahl von readField haben, so oft es Felder gibt, aber ohne Indizes oder explizite Typen verwenden zu müssen.

Für ein echtes Programm würden Sie wahrscheinlich einige Fehlerbehandlung enthalten, wie read scheitert mit einer Ausnahme, sowie das Muster (x : xs), wenn die Eingabeliste zu kurz ist. ein vollwertiges Parser wie parsec oder attoparsec erlaubt Verwenden Sie die gleiche Schreibweise zu verwenden und die richtige Fehlerbehandlung müssen, Parsen von einzelnen Feldern anpassen usw.


noch universeller Weise ist Einwickeln und Auswickeln Felder zu automatisieren in Listen mit generics. Dann leiten Sie einfach Generic ab. Wenn Sie interessiert sind, kann ich ein Beispiel geben.

Oder könnten Sie ein vorhandenes Serialisierung Paket verwenden, entweder eine binäre Eins wie Getreide oder binären oder eine textbasierte wie Aeson oder yaml, die in der Regel erlauben Ihnen, beides zu tun (Entweder automatisch (De) Serialisierung von Generic oder bieten Sie Ihre benutzerdefinierte).

+1

Ich würde dringend empfehlen, Standard-Ansätze wie Getreide/Binär/Aeson/Yaml. – leftaroundabout