File 如何让Haskell代码使用惰性和垃圾收集器

File 如何让Haskell代码使用惰性和垃圾收集器,file,haskell,garbage-collection,lazy-evaluation,File,Haskell,Garbage Collection,Lazy Evaluation,我编写了一个Haskell代码,它必须解决以下问题:我们有n个文件:f1、f2、f3。。。。fn和我剪切这些文件的方式是,每个切片有100行 f1_1, f1_2, f1_3 .... f1_m f2_1, f2_2, .... f2_n ... fn_1, fn_2, .... fn_k 最后,我以以下方式使用切片构造了一个特殊的数据类型(Dags f1_1, f2_1, f3_1, .... fn_1 => Dag1 f1_2, f2_2, f3_2,

我编写了一个Haskell代码,它必须解决以下问题:我们有n个文件:f1、f2、f3。。。。fn和我剪切这些文件的方式是,每个切片有100行

  f1_1, f1_2, f1_3 .... f1_m

  f2_1, f2_2, .... f2_n
  ...

  fn_1, fn_2, .... fn_k
最后,我以以下方式使用切片构造了一个特殊的数据类型(Dags

  f1_1, f2_1, f3_1, .... fn_1 => Dag1

  f1_2, f2_2, f3_2, ..... fn_2 => Dag2

  ....

  f1_k, f2_k, f3_k, ..... fn_k => Dagk
我编写的代码首先剪切所有文件,然后耦合结果列表的第I个元素,并使用最终结果列表构造Dag

看起来像这样

  -- # take a filename and cut the file in slices of 100 lines

  sliceFile :: FilePath -> [[String]]

  -- # take a list of lists and group the i-th elements into list

  coupleIthElement :: [[String]] -> [[String]]

  -- # take a list of lines and create a DAG

  makeDags :: [String] ->  Dag

  -- # final code look like this

  makeDag_ :: [FilePath] -> [Dag]

  makeDags files = map makeDags $ coupleIthElement (concat (map sliceFile files))
问题在于此代码效率低下,因为:

  • 它需要以列表形式存储内存中的所有文件

  • 垃圾收集器无法有效工作,因为所有函数都需要上一个函数的结果列表

我如何重新编写程序来利用垃圾收集器的工作和Haskell的惰性

如果不可能或更简单,我可以做些什么来提高效率,哪怕是一点点

谢谢你的回复


编辑

couplethElement[“abc”、“123”、“xyz”]
必须返回
[“a1x”、“b2y”、“c3z”]

当然,100行是根据行中的某些元素使用特定标准任意选择的,但我放弃了这一点,以使问题更容易理解

另一版本

data Dag = Dag ([(Int, String)], [((Int, Int), Int)]) deriving Show

test_dag = Dag ([(1, "a"),(2, "b"),(3, "c")],[((1,2),1),((1,3),1)])

test_dag2 = Dag ([],[])

第一个列表是由编号和标签定义的每个vertice,第二个列表是边
((1,2),3)
表示vertice 1和2之间的边,成本为3

如果不需要在片中处理文件,请避免此操作。Haskell会自动完成这项工作!在哈斯凯尔,你认为IO是一条小溪。一旦需要数据,就从输入中读取数据;一旦数据未使用,就丢弃数据。例如,这是一个简单的文件复制程序:

main = interact id
interact
具有签名
interact::(String->String)->IO()
,并将输入馈送到一个函数中,该函数处理输入并生成一些输出,这些输出被写入stdout。这个程序比大多数C语言实现更高效,因为运行时会自动缓冲输入和输出

如果你想理解懒惰,你必须忘记你作为一个命令式程序员学到的所有智慧,必须把程序看作是修改数据的描述,而不是一组指令——数据只在需要时才被处理

关键的一点是,为什么您的数据可能被错误地处理是多次遍历列表。函数
makeDags
逐个遍历转置后的切片列表,因此原始列表中的元素可能不会被丢弃。您应该尝试以如下方式编写函数:

sliceFile :: FilePath -> [[String]]
sliceFile fp = do
  f <- readFile fp
  let l = lines fp
      slice [] = []
      slice x  = ll : slice ls where (ll,ls) = splitAt 100 x
  return slice l

sliceFirstRow :: [[String]] -> ([String],[[String]])
sliceFirstRow list = unzip $ map (\(x:xs) -> (x,xs)) list

makeDags :: [[String]] -> [Dag]
makeDags [[]] = []
makeDags list = makeDag firstRow : makeDags restOfList where
 (firstRow,restOfList) = sliceFirstRow list
dagFromHandles :: [Handle] -> IO Dag
dagFromHandles = fmap makeDags . mapM hGetLine

allDags :: [FilePath] -> IO [Dag]
allDags listOfFiles = do
  handles <- mapM (flip openFile ReadMode) listOfFiles
  replicateM 100 (dagFromHandles handles)
sliceFile::FilePath->[[String]]
切片文件fp=do
f([String],[String]])
sliceFirstRow list=解压缩$map(\(x:xs)->(x,xs))列表
makeDags::[[String]]->[Dag]
makeDags[[]]=[]
makeDags list=makeDag firstRow:makeDags restoff列表,其中
(第一行,restOfList)=第一行列表
此函数可能是一个解决方案,因为第一行在完成时不再被引用。但在大多数情况下,这是懒惰的结果,因此您可能会尝试使用
seq
强制构建DAG并允许对IO数据进行垃圾收集。(如果不强制构建DAG,则无法对数据进行垃圾收集)

但无论如何,如果你提供一些关于这些DAG是什么的信息,我可以提供一个更有用的答案。

几点:

1) 你考虑过使用吗?它可能比您自己的
Dag
实现更高效。如果确实需要使用
Dag
,可以使用
fgl
构建图形,然后在图形完成后将其转换为
Dag

2) 在构建图形时,似乎实际上并不使用切片,而是它们控制有多少图形。如果是的话,像这样的事情怎么样:

sliceFile :: FilePath -> [[String]]
sliceFile fp = do
  f <- readFile fp
  let l = lines fp
      slice [] = []
      slice x  = ll : slice ls where (ll,ls) = splitAt 100 x
  return slice l

sliceFirstRow :: [[String]] -> ([String],[[String]])
sliceFirstRow list = unzip $ map (\(x:xs) -> (x,xs)) list

makeDags :: [[String]] -> [Dag]
makeDags [[]] = []
makeDags list = makeDag firstRow : makeDags restOfList where
 (firstRow,restOfList) = sliceFirstRow list
dagFromHandles :: [Handle] -> IO Dag
dagFromHandles = fmap makeDags . mapM hGetLine

allDags :: [FilePath] -> IO [Dag]
allDags listOfFiles = do
  handles <- mapM (flip openFile ReadMode) listOfFiles
  replicateM 100 (dagFromHandles handles)
dagFromHandles::[Handle]->IO Dag
dagFromHandles=fmap makeDags。mapmhgetline
AllDAG::[FilePath]->IO[Dag]
allDags listOfFiles=do
处理IO()
runDags::[FilePath]->IO()
runDags listOfFiles=do
句柄>=useDag)
这将更有效地利用垃圾收集


当然,这假设我正确地理解了这个问题,但我不确定我是否理解。请注意,
concat(map sliceFile)
应该是no-op(
sliceFile
需要在IO中定义类型,但现在忽略它),所以我不明白您为什么要为它烦恼。

100行分组有什么重要的地方吗,或者这是任意的?您的“sliceFile”类型没有意义:它肯定必须返回“IO[[String]]”ByteStrings代替String工作吗?这样会更有效率。如果我们更好地理解这个问题,可能会有所帮助。您的“耦合器元素”函数看起来像“转置”。还是我误解了它的作用?答案很好,但是关于ByteStrings的信息是不正确的
Data.ByteString.Lazy
用于二进制数据,操作作用于8位字的Haskell类型
Word8
Data.ByteString.Lazy.Char8
用于ASCII文本,操作作用于
Char
s的子集。两者都使用相同的内存进行存储@Fuzzxl,在你真正使用之前,你能不能停止回答问题?这是我见过的第十个不正确的答案。所有严格的bytestring都是8位字向量。相同的基础数据,相同的Haskell类型。然而,Char8中的函数只是根据需要在Char之间进行转换。(例如,
pack
packs Chars而不是Word8s,以及
map
将Char应用于映射函数而不是Word8。)您可以在相同的数据上使用.Char8和普通函数,这些函数只是相同基础数据的视图。感谢FUZxxl的代码,它真的很有用