在Haskell中读取长数据结构

时间:2016-07-11 09:38:37

标签: 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函数的Currying,并在当时传递一个参数。

嗯,在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

实际上,我传递给f的函数readFields在每次调用时都有不同的类型签名;这就是为什么我无法想出它的类型签名......

所以,我的问题是:读取具有多个字段的数据结构的最简单,最优雅的方法是什么?

2 个答案:

答案 0 :(得分:3)

首先,为所有顶级声明添加类型始终是一个好习惯。它使代码结构更好,更易读。

如何实现这一目标的一个简单方法是利用applicative functors。在解析过程中,你有一个&#34;有效的&#34;计算,其中效果消耗部分输入,其结果是一个解析的片段。我们可以使用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)允许您使用相同的表示法并进行适当的错误处理,自定义各个字段的解析等。

更通用的方法是使用generics自动将字段换行和展开到列表中。然后你只是派生Generic。如果您有兴趣,我可举个例子。

或者,您可以使用现有的序列化软件包,例如 cereal binary 等二进制文件包,或者基于文本的 aeson yaml ,通常允许您同时执行这两项操作(从Generic自动派生(de)序列化或提供自定义序列化)。

答案 1 :(得分:2)

编辑:如果您正在阅读字符串,则更简单的解决方案:

{-# 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
当所有记录字段属于同一类型时,

Constructing Haskell data types with many fields提供解决方案。如果不是,那么稍微多一点的解决方案就是:

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