[英]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)
。 使用完整的解析器(如parsec或attoparsec)允許您使用相同的表示法並進行適當的錯誤處理,自定義各個字段的解析等。
更通用的方法是使用泛型自動包裝和展開字段到列表中。 然后你只是派生Generic
。 如果你有興趣,我可舉個例子。
或者,您可以使用現有的序列化包,可以是像谷物或二進制這樣的二進制包,也可以是基於文本的序列化包,例如aeson或yaml ,它們通常允許您同時執行這兩者(從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.