簡體   English   中英

在Haskell中讀取長數據結構

[英]Reading long data structure 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))

這段代碼有效,但是readPerson的代碼很糟糕:我必須為我的數據結構中的所有字段復制粘貼read(row!!n))

所以,作為第二次嘗試,我想我可能會利用這個的柯里Person的功能,並通過它當時的論據之一。

嗯,Hoogle肯定有東西,但我無法弄清楚簽名的類型......沒關系,它看起來很簡單,我可以自己編寫:

readPerson row = readFields Person row

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

啊,編碼風格看起來好多了!

但是,它不編譯! Occurs check: cannot construct the infinite type: t ~ String -> t

實際上,我傳遞給readFields的函數f在每次調用時都有不同的類型簽名; 這就是為什么我無法想出它的類型簽名......

所以,我的問題是:讀取具有多個字段的數據結構的最簡單和優雅的方法是什么?

首先,為所有頂級聲明包含類型始終是一個好習慣。 它使代碼結構更好,更易讀。

如何實現這一點的一個簡單方法是利用應用函子 在解析期間,您有一個“有效”的計算,其中效果消耗部分輸入,其結果是一個解析的片段。 我們可以使用State monad來跟蹤剩余的輸入,並創建一個多態函數,它消耗輸入的一個元素並read它:

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)

為了解析許多這樣的字段,我們使用<$><*>組合器,它們允許按如下方式對操作進行排序:

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

表達式Person <$> ...的類型為State [String] Person ,我們在給定的輸入上運行evalState來運行有狀態計算並提取輸出。 我們仍然需要具有與字段相同數量的readField ,但不必使用索引或顯式類型。

對於一個真正的程序,你可能會包含一些錯誤處理,因為read失敗並帶有異常,如果輸入列表太短,則還有patterm (x : xs) 使用完整的解析器(如parsecattoparsec)允許您使用相同的表示法並進行適當的錯誤處理,自定義各個字段的解析等。


更通用的方法是使用泛型自動包裝和展開字段到列表中。 然后你只是派生Generic 如果你有興趣,我可舉個例子。

或者,您可以使用現有的序列化包,可以是像谷物二進制這樣的二進制包,也可以是基於文本的序列化包,例如aesonyaml ,它們通常允許您同時執行這兩者(從Generic自動派生(de)序列化或提供你的定制)。

編輯:如果您從字符串中讀取更簡單的解決方案:

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

那么,如果列表大小合適,你會得到:

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

如果沒有,你什么也得不到:

\> person Person $ words "John 42"
Nothing

當所有記錄字段具有相同類型時, 構造具有許多字段的Haskell數據類型提供了解決方案。 如果不是,那么稍微多一點的解決方案就是:

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

然后,

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

為了構造Val類型,您可以創建另一個類類並創建所需的實例:

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

那么,它會稍微簡單一點:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM