Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/haskell/9.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
Haskell Isn';这不是一次双重穿越吗?_Haskell_Functional Programming_Traversal - Fatal编程技术网

Haskell Isn';这不是一次双重穿越吗?

Haskell Isn';这不是一次双重穿越吗?,haskell,functional-programming,traversal,Haskell,Functional Programming,Traversal,在的“编程技巧”部分,我发现了以下示例: count :: (a -> Bool) -> [a] -> Int count p = length . filter p 据说这是一个更好的替代方案 count :: (a -> Bool) -> [a] -> Int count _ [] = 0 count p (x:xs) | p x = 1 + count p xs | otherwise = count p xs 就可读

在的“编程技巧”部分,我发现了以下示例:

count :: (a -> Bool) -> [a] -> Int
count p = length . filter p
据说这是一个更好的替代方案

count :: (a -> Bool) -> [a] -> Int
count _ [] = 0
count p (x:xs)
   | p x       = 1 + count p xs
   | otherwise =     count p xs
就可读性而言,我完全同意


然而,这不是一个双重遍历,因此实际上比显式递归函数更糟糕吗?GHC中的懒惰是否意味着这相当于优化后的一次遍历?哪种实现更快,为什么更快?

无论哪种方式,每个项目都必须执行一个或两个操作。必要的是检查谓词。添加1的第二个取决于谓词的结果

这样,如果不考虑缓存等的影响,两种情况都会产生相等的操作数。 第一种情况下有两个单独的遍历,一个收集元素,一个计算元素。如果列表大于缓存可以处理的范围,这将降低处理速度。这实际上是在一种严格的语言中发生的

然而,哈斯克尔的懒惰在这里出现了。由于计数函数
length
需要,由
filter
生成的列表将逐元素计算(存在)。然后,由于
length
仅将它们用于计数,而不保留对新创建的列表的引用,因此这些元素可以立即进行垃圾收集。因此,在计算过程中,任何时候都只有
O(1)
内存被占用

在第一个版本中构造结果“过滤”列表会有一些开销。但与列表较大时出现的缓存效果相比,这通常可以忽略不计。(对于小列表,它可能很重要;这需要测试。)此外,它可能会根据编译器和选择的优化级别进行优化


更新。第二个版本实际上会消耗内存,正如其中一条评论所指出的那样。为了进行公平比较,您需要在正确的位置使用累加参数和严格性注释器编写,因为(我希望)
length
已经以这种方式编写。

您可以测试哪个版本更快,例如:

然后运行
ghc-O2-prof-fprof auto-rtsopts Main.hs
/Main+RTS-p
。它将生成文件Main.prof。然后将主函数改为使用
countme
,并比较结果。我的建议是:

  • 4.12s用于隐式版本
  • 6.34s用于显式版本
如果关闭优化,那么隐式优化仍然会稍微快一点(但不会快很多)


除了其他人已经解释过的融合和懒惰之外,我想另一个原因可能是
length
filter
是前奏函数,编译器可以更好地优化它们。

因此,要了解优化器的实际功能,让我们:


由于我们使用的是unbox类型
GHC.Prim.Int#
,因此我们只有一个数据循环。

在GHC中启用优化时,两个遍历可能会融合为一个。但是,是的,原则上你是对的,尽管第二次遍历只在较短的过滤列表上进行,所以它通常不会有太大的影响。第二个版本不是尾部递归的,可能会比第一个版本使用更多的内存。您可以使用(严格的)累加器将其保存在恒定内存中。或者您可以使用
foldl'(\c a->if p a then succc else c)0
。您可能需要:DThanks来了解计时!只是想知道为什么数字是93823249?那是一个随机数。为什么?分析有助于找出单个程序的时间花费在哪里。这不是一个很好的基准测试工具。最好使用
criteria
之类的库进行基准测试。您是否有参考资料说明为什么评测不适合进行基本基准测试?我不是哈斯克尔大师,我的理解正确吗
filter
根据
length
的请求逐个构造过滤后的列表,而后者又对其进行解构。这种中间构造/解构可能被称为“第二次遍历”,但编译器可以对其进行优化。@stholzm Right。我可以这样说:由于懒惰,第一次和第二次遍历是交错的。优化编译器可以删除第二个,因为它所做的只是构造和销毁(并将一个添加到其他数字中)。
module Main where


main :: IO ()
main = print countme'

count' :: (a -> Bool) -> [a] -> Int
count' p = length . filter p

count :: (a -> Bool) -> [a] -> Int
count _ [] = 0
count p (x:xs)
   | p x       = 1 + count p xs
   | otherwise =     count p xs


list = [0..93823249]

countme' = count' (\x -> x `mod` 15 == 0) list
countme = count (\x -> x `mod` 15 == 0) list
% ghc -O2 -ddump-simpl Temp.hs
[1 of 1] Compiling Temp             ( Temp.hs, Temp.o )

==================== Tidy Core ====================
Result size of Tidy Core = {terms: 29, types: 26, coercions: 0}

Temp.count
  :: forall a_arN.
     (a_arN -> GHC.Types.Bool) -> [a_arN] -> GHC.Types.Int
[GblId,
 Arity=2,
 Caf=NoCafRefs,
 Str=DmdType <L,C(U)><S,1*U>,
 Unf=Unf{Src=<vanilla>, TopLvl=True, Arity=2, Value=True,
         ConLike=True, WorkFree=True, Expandable=True,
         Guidance=IF_ARGS [60 0] 191 0}]
Temp.count =
  \ (@ a_aMA)
    (p_arV :: a_aMA -> GHC.Types.Bool)
    (eta_B1 :: [a_aMA]) ->
    letrec {
      go_aNr [Occ=LoopBreaker]
        :: [a_aMA] -> GHC.Prim.Int# -> GHC.Types.Int
      [LclId, Arity=1, Str=DmdType <S,1*U>]
      go_aNr =
        \ (ds_aNs :: [a_aMA]) ->
          case ds_aNs of _ [Occ=Dead] {
            [] -> GHC.Types.I#;
            : y_aNx ys_aNy ->
              case p_arV y_aNx of _ [Occ=Dead] {
                GHC.Types.False -> go_aNr ys_aNy;
                GHC.Types.True ->
                  let {
                    g_a10B [Dmd=<L,C(U)>] :: GHC.Prim.Int# -> GHC.Types.Int
                    [LclId, Str=DmdType]
                    g_a10B = go_aNr ys_aNy } in
                  \ (x_a10C :: GHC.Prim.Int#) -> g_a10B (GHC.Prim.+# x_a10C 1)
              }
          }; } in
    go_aNr eta_B1 0
Temp.count :: forall aType.  (aType -> Bool) -> [aType] -> Int
Temp.count = \(@ aType) (p :: aType -> Bool) (as :: [aType]) ->
  letrec {
    go :: [aType] -> GHC.Prim.Int# -> Int
    go = \(xs :: [aType]) ->
      case xs of _ {
        [] -> I#; -- constructor to make a GHC.Prim.Int# into an Int
        : y ys ->
          case p y of _ {
            False -> go ys;
            True ->
              let {
                go' :: GHC.Prim.Int# -> Int
                go' = go ys 
              } in \(x :: GHC.Prim.Int#) -> go' (GHC.Prim.+# x 1)
          }
      }; 
  } in go as 0