我必须从文本文件(空格分隔)中读取数据结构,每行一个数据项.我的第一个尝试是
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))
Run Code Online (Sandbox Code Playgroud)
这段代码有效,但代码readPerson很糟糕:我必须复制粘贴read(row!!n))数据结构中的所有字段!
所以,作为第二次尝试,我认为我可能会利用Person函数的Currying ,并在当时传递一个参数.
嗯,Hoogle肯定有东西,但我无法弄清楚签名的类型......没关系,它看起来很简单,我可以自己编写:
readPerson row = readFields Person row
readFields f [x] = (f x)
readFields f (x:xs) = readFields (f (read x)) xs
Run Code Online (Sandbox Code Playgroud)
啊,编码风格看起来好多了!
但是,它不编译! Occurs check: cannot construct the infinite type: t ~ String -> t
实际上,f我传递给的函数readFields在每次调用时都有不同的类型签名; 这就是为什么我无法想出它的类型签名......
所以,我的问题是:读取具有多个字段的数据结构的最简单和优雅的方法是什么?
编辑:如果您正在读取字符串,则解决方案更简单:
{-# 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
Run Code Online (Sandbox Code Playgroud)
然后,如果列表大小合适,您将得到:
\> person Person $ words "John 42 6.05"
Just (Person {name = "John", age = 42, height = 6.05})
Run Code Online (Sandbox Code Playgroud)
如果没有,你什么也得不到:
\> person Person $ words "John 42"
Nothing
Run Code Online (Sandbox Code Playgroud)
当所有记录字段都属于同一类型时,构造具有多个字段的 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)
Run Code Online (Sandbox Code Playgroud)
然后,
\> person Person [SVal "John", IVal 42, DVal 6.05]
Just (Person {name = "John", age = 42, height = 6.05})
Run Code Online (Sandbox Code Playgroud)
为了构造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
Run Code Online (Sandbox Code Playgroud)
那么,它的表示法会稍微简单一些:
\> person Person [cast "John", cast (42 :: Int), cast 6.05]
Just (Person {name = "John", age = 42, height = 6.05})
Run Code Online (Sandbox Code Playgroud)