2016-07-11 28 views
6

Devo leggere una struttura dati da un file di testo (separato dallo spazio), un elemento di dati per riga. Il mio primo tentativo sarebbeLettura della struttura dei dati lunghi 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)) 

Questo codice funziona, ma il codice per readPerson è terribile: devo copiare-incollare il read(row!!n)) per tutti i campi nella mia struttura dati!

Quindi, come secondo tentativo, penso che potrei sfruttare Currying della funzione Person e passargli gli argomenti uno alla volta.

Uhm, ci deve essere qualcosa in Hoogle, ma non riesco a capire il tipo di firma ... Non importa, sembra abbastanza semplice e posso scriverla io:

readPerson row = readFields Person row 

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

Ahh, sembra molto meglio stile di codifica!

Ma, non viene compilato! Occurs check: cannot construct the infinite type: t ~ String -> t

Infatti, la funzione f che sto passando a readFields ha una firma di tipo diverso in ciascuna chiamata; è per questo che non sono riuscito a capire il suo tipo di firma ...

Quindi, la mia domanda è: qual è il modo più semplice ed elegante per leggere una struttura dati con molti campi?

+1

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

+1

"Persone" hanno campi con tipi diversi? Se è così, probabilmente non c'è un modo veramente semplice per risolvere questo problema ... – Bakuriu

risposta

2

EDIT: soluzione più semplice se si sta leggendo dalle stringhe:

{-# 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 

poi, se la lista è della dimensione giusta si ottiene:

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

e se non si ottiene nulla:

\> person Person $ words "John 42" 
Nothing 

Constructing Haskell data types with many fields fornisce una soluzione quando tutti i campi del record sono dello stesso tipo. Se non lo sono, una soluzione un po 'più polimorfa sarebbe:

{-# 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) 

poi,

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

Per costruire Val tipi, è possibile creare un altro tipo di classe e fare le istanze desiderate:

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 

allora, sarebbe la notazione un po 'più semplice:

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

In primo luogo, è sempre buona norma includere i tipi per tutte le dichiarazioni di livello superiore. Rende il codice più strutturato e molto più leggibile.

Un modo semplice per ottenere questo è sfruttare applicative functors. Durante l'analisi, si ha un calcolo "efficace" in cui l'effetto sta consumando parte dell'input e il suo risultato è un pezzo analizzato.Possiamo usare la State Monade per monitorare l'ingresso rimanente, e creare una funzione polimorfica che consuma un elemento del input e read s è:

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) 

E al fine di analizzare molti di questi campi si usa il <$> e <*> combinatori che consentono di sequenziare operazioni come segue:

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

Espressione Person <$> ... è di tipo State [String] Person e abbiamo eseguito evalState sul dato input per eseguire il calcolo stateful ed estrarre l'uscita. Dobbiamo ancora avere lo stesso numero di readField tante volte quanti sono i campi, ma senza dover utilizzare indici o tipi espliciti.

Per un programma reale probabilmente si include una gestione degli errori, poiché read non riesce con un'eccezione, nonché il patterm (x : xs) se l'elenco di input è troppo breve. Utilizzo di un parser a tutti gli effetti, come parsec o attoparsec consente di utilizzare la stessa notazione e di avere una corretta gestione degli errori, personalizzare l'analisi dei singoli settori, ecc


Ancora più universale modo è quello di automatizzare il confezionamento e campi unwrapping nelle liste usando generics. Quindi devi solo derivare Generic. Se sei interessato, posso fare un esempio.

Oppure, si potrebbe utilizzare un pacchetto di serializzazione esistente, o un uno binario come cereali o binario, o di un testo a base di uno come Aeson o YAML, che di solito ti permettono di fare entrambe le cose (può derivare automaticamente (de) la serializzazione da Generic o fornire quella personalizzata).

+1

Consiglio vivamente di prendere in considerazione approcci standard come cereale/binario/aeson/yaml. – leftaroundabout

Problemi correlati