Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/list/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
List Haskell:列表与流_List_Haskell_Stream - Fatal编程技术网

List Haskell:列表与流

List Haskell:列表与流,list,haskell,stream,List,Haskell,Stream,我注意到,除了固定的时间附加之外,它的行为似乎很像列表。当然,向列表中添加常量时间附加并不太复杂,而且确实如此 在接下来的讨论中,我们假设列表有固定的时间附加,或者我们对它不感兴趣 我的想法是Haskell列表应该简单地实现为流。为避免出现这种情况,我假设以下情况需要保持: 在某些情况下,列表优于流和 在某些情况下,流比列表更好 我的问题是:上述两种情况的例子是什么 注意:对于这个问题,请忽略我所讨论的特定实现中容易修复的遗漏。我在这里寻找更多的核心结构差异 其他信息: 我想我在这里得到的部分内

我注意到,除了固定的时间附加之外,它的行为似乎很像列表。当然,向列表中添加常量时间附加并不太复杂,而且确实如此

在接下来的讨论中,我们假设列表有固定的时间附加,或者我们对它不感兴趣

我的想法是Haskell列表应该简单地实现为流。为避免出现这种情况,我假设以下情况需要保持:

  • 在某些情况下,列表优于流
  • 在某些情况下,流比列表更好
  • 我的问题是:上述两种情况的例子是什么

    注意:对于这个问题,请忽略我所讨论的特定实现中容易修复的遗漏。我在这里寻找更多的核心结构差异

    其他信息:

    我想我在这里得到的部分内容是,如果我们编写
    [1..1000000]
    ,Haskell编译器(比如GHC)是否会:

  • 列出一个清单
  • 创建一个包含两个整数的对象:1和1000000,这两个整数完全描述了列表
  • 如果是第(1)种情况,为什么要这样做,因为创建中间列表似乎是一种不必要的性能损失


    或者,如果是第(2)种情况,那么我们为什么需要流呢?

    流的优势在于它们更强大。界面:

    data Stream m a = forall s . Stream (s -> m (Step s a)) s Size   
    
    data [] a = a : [a] | []
    
    让您完成许多常规列表无法完成的事情。例如:

    • 跟踪大小(如未知,最大34,精确到12)
    • 执行一元操作以获取下一个元素。列表可以在惰性IO中部分实现这一点,但事实证明,这种技术很容易出错,通常只用于初学者或简单的小脚本
    然而,与列表相比,它们有一个很大的缺点——复杂性!对于初学者程序员来说,要理解流,必须掌握存在类型和一元操作。如果要使用基本列表类型,您必须学习这两个复杂的主题,那么学习haskell将非常困难

    将其与具有以下界面的列表进行比较:

    data Stream m a = forall s . Stream (s -> m (Step s a)) s Size   
    
    data [] a = a : [a] | []
    
    这是非常简单的,可以很容易地教给一个新的程序员

    列表的另一个优点是,您可以简单地对它们进行模式匹配。例如:

    getTwo (a : b : _) = Just (a,b)
    getTwo _ = Nothing
    
    这对有经验的程序员(我仍然在许多方法中使用列表模式匹配)和还没有学会可用于操作列表的标准高阶函数的初学者都很有用

    效率也是列表的另一个潜在优势,因为ghc在列表融合方面花费了大量时间。在许多代码中,从来不会生成中间列表。使用流进行优化可能会困难得多

    所以我认为用流交换列表是一个糟糕的选择。目前的情况更好,如果你需要的话,你可以把它们带进来,但是初学者不会被它们的复杂性所困扰,熟练的用户也不必失去模式匹配

    编辑:关于
    [1..1000000]


    这相当于
    enumfromto1000000
    ,这是一种延迟评估,并且需要进行融合(这使得它非常有效)。例如
    sum[1..1000000]
    在启用优化的情况下不会生成任何列表(并使用恒定内存)。所以,情况(2)是正确的,由于延迟计算,这种情况对流来说不是一个优势。但是如上所述,流比列表有其他优势。

    当您编写
    [1..1000000]
    时,GHC真正做的是创建一个包含
    1
    1000000
    的对象,描述如何构建感兴趣的列表;那个物体叫做“砰”。该列表仅在满足案例审查者的需要时建立;例如,您可以编写:

    printList [] = putStrLn ""
    printList (x:xs) = putStrLn (show x) >> printList xs
    
    main = printList [1..1000000]
    
    评估结果如下:

    main
    = { definition of main }
    printList [1..1000000]
    = { list syntax sugar }
    printList (enumFromTo 1 1000000)
    = { definition of printList }
    case enumFromTo 1 1000000 of
        [] -> putStrLn ""
        x:xs -> putStrLn (show x) >> printList xs
    = { we have a case, so must start evaluating enumFromTo;
        I'm going to skip a few steps here involving unfolding
        the definition of enumFromTo and doing some pattern
        matching }
    case 1 : enumFromTo 2 1000000 of
        [] -> putStrLn ""
        x:xs -> putStrLn (show x) >> printList xs
    = { now we know which pattern to choose }
    putStrLn (show 1) >> printList (enumFromTo 2 1000000)
    
    然后您会发现
    1
    被打印到控制台,我们从顶部附近开始使用
    enumFromTo 2 1000000
    而不是
    enumFromTo 1 1000000
    。最终,你会得到所有的数字打印出来,它将是时间来评估

    printList (enumFromTo 1000000 1000000)
    = { definition of printList }
    case enumFromTo 1000000 1000000 of
        [] -> putStrLn ""
        x:xs -> putStrLn (show x) >> printList xs
    = { skipping steps again to evaluate enumFromTo }
    case [] of
        [] -> putStrLn ""
        x:xs -> putStrLn (show x) >> printList xs
    = { now we know which pattern to pick }
    putStrLn ""
    
    评估就要结束了

    我们需要流的原因有点微妙。原文,大概有最完整的解释。简短的版本是,当管道较长时:

    concatMap foo . map bar . filter pred . break isSpecial
    
    …如何让编译器编译掉所有中间列表并不那么明显。您可能会注意到,我们可以将列表视为具有某种正在迭代的“状态”,并且这些函数中的每一个函数,而不是遍历列表,只是更改在每次迭代中修改状态的方式。
    Stream
    类型尝试将其显式化,结果是流融合。看起来是这样的:我们首先将所有这些函数转换为流版本:

    (toList . S.concatMap foo . fromList) .
    (toList . S.map bar . fromList) .
    (toList . S.filter pred . fromList) .
    (toList . S.break isSpecial . fromList)
    
    然后观察我们总是可以从列表中删除
    。收费表

    toList . S.concatMap foo . S.map bar . S.filter pred . S.break . fromList
    

    …然后奇迹发生了,因为链
    S.concatMap foo。美国地图栏。美国过滤器公司。S.break显式构建迭代器,而不是通过内部构建然后立即消除实际列表来隐式构建迭代器。

    简短回答:列表和流在功能上是无与伦比的。流允许一元操作,但不允许共享,而列表则相反

    一个较长的答案:

    1) 请参阅@nanothief以了解无法使用列表实现的反例 2) 下面是一个反例,它不能很容易地用流实现

    问题是玩具列表示例通常不使用列表的共享功能。代码如下:

    foo = map heavyFunction bar
    baz = take 5 foo
    quux = product foo
    

    对于列表,只需计算一次重函数。使用流计算
    baz
    qux
    而不额外计算
    heavyFunction
    的代码将很难维护。

    Hm,你为什么说流具有恒定的附加/前置时间?从实现来看,似乎追加n个元素将导致