Haskell findIndices函数的解释

Haskell findIndices函数的解释,haskell,Haskell,我说我不理解的源代码 事实上,我没有给予足够的关注,也没有看到这个函数有两种定义: findIndices :: (a -> Bool) -> [a] -> [Int] #if defined(USE_REPORT_PRELUDE) findIndices p xs = [ i | (x,i) <- zip xs [0..], p x] #else -- Efficient definition, adapted from Data.Sequence {-# I

我说我不理解的源代码

事实上,我没有给予足够的关注,也没有看到这个函数有两种定义:

findIndices      :: (a -> Bool) -> [a] -> [Int]
#if defined(USE_REPORT_PRELUDE)
findIndices p xs = [ i | (x,i) <- zip xs [0..], p x]
#else
-- Efficient definition, adapted from Data.Sequence
{-# INLINE findIndices #-}
findIndices p ls = build $ \c n ->
  let go x r k | p x       = I# k `c` r (k +# 1#)
               | otherwise = r (k +# 1#)
  in foldr go (\_ -> n) ls 0#
#endif  /* USE_REPORT_PRELUDE */
findIndices::(a->Bool)->[a]->[Int]
#如果定义(使用报告前奏)
findindicespxs=[i |(x,i)
放手xRk | px=I#k`c`r(k+#1#)
|否则=r(k+#1#)
在foldr go(\ \ \->n)ls 0中#
#endif/*使用_报告_序曲*/
我理解第一个定义,我没有看到。我不理解第二个。我有几个问题:

  • 如果定义了什么(使用报告前奏)

  • 你能解释一下第二个定义吗?什么是
    build
    I
    +
    1

  • 为什么第二个定义是内联的,而不是第一个


    • 对于C编程语言,
      CPP
      扩展启用C预处理器。这里,它用于测试编译过程中是否设置了标志
      USE\u REPORT\u PRELUDE
      。根据该标志,编译器使用代码的
      \if
      \else
      变体

      build
      是一个可以定义为

      build f = f (:) []
      
      因此,使用
      build(\n->…
      基本上可以将
      c
      转换为“cons”
      (:)
      ,将
      n
      转换为“nil”
      []

      这并不是为了方便而使用的:它一点也不方便!但是,编译器优化器在
      build
      foldr
      的组合下工作得非常好,因此这里的代码是以一种奇怪的方式编写的,以利用这一点


      此外,
      I#…
      是整数的低级构造函数

      x :: Int
      x = 4+2
      
      GHC实现
      x
      (非常粗略地)时使用一个指向某个内存的指针,该内存读作
      unevaluated:4+2
      。在第一次强制执行
      x
      后,该内存将被
      evaluated:I#6#
      覆盖。这是实现惰性所必需的。 这里的“装箱”是指通过指针的间接寻址

      相反,类型
      Int#
      是一个普通的机器整数,没有指针,没有间接寻址,没有未赋值的表达式。它是严格的(而不是惰性的),但级别越低,效率就越高

      x' :: Int#
      x' = 6#
      
      x :: Int
      x = I# x'
      
      实际上,
      Int
      被定义为
      newtype Int=I#Int#

      请记住,这不是标准的Haskell,而是GHC特定的低级详细信息。在普通代码中,您不需要使用此类未绑定类型。在库中,作者这样做是为了获得更高的性能,但仅此而已

      有时,即使在我们的代码中我们只使用
      Int
      s,GHC也足够聪明,可以自动将我们的代码转换为使用
      Int
      ,并实现更高的效率,避免装箱。如果我们要求GHC“转储核心”,以便我们可以看到优化的结果,就可以观察到这一点

      例如,编译

      f :: Int -> Int
      f 0 = 0
      f n = n + f (n-1)
      
      GHC生成一个较低级别的版本(这是GHC核心,不是Haskell,但它非常相似,可以理解):


      与C编程语言一样,
      CPP
      扩展支持C预处理器。这里,它用于测试编译期间是否设置了标志
      USE\u REPORT\u PRELUDE
      。根据该标志,编译器使用代码的
      \if
      \else
      变体

      build
      是一个可以定义为

      build f = f (:) []
      
      因此,使用
      build(\n->…
      基本上可以将
      c
      转换为“cons”
      (:)
      ,将
      n
      转换为“nil”
      []

      这并不是为了方便而使用的:它一点也不方便!但是,编译器优化器在
      build
      foldr
      的组合下工作得非常好,因此这里的代码是以一种奇怪的方式编写的,以利用这一点


      此外,
      I#…
      是整数的低级构造函数

      x :: Int
      x = 4+2
      
      GHC实现
      x
      (非常粗略地)时使用一个指向某个内存的指针,该内存读作
      unevaluated:4+2
      。在第一次强制执行
      x
      后,该内存将被
      evaluated:I#6#
      覆盖。这是实现惰性所必需的。 这里的“装箱”是指通过指针的间接寻址

      相反,类型
      Int#
      是一个普通的机器整数,没有指针,没有间接寻址,没有未赋值的表达式。它是严格的(而不是惰性的),但级别越低,效率就越高

      x' :: Int#
      x' = 6#
      
      x :: Int
      x = I# x'
      
      实际上,
      Int
      被定义为
      newtype Int=I#Int#

      请记住,这不是标准的Haskell,而是GHC特定的低级详细信息。在普通代码中,您不需要使用此类未绑定类型。在库中,作者这样做是为了获得更高的性能,但仅此而已

      有时,即使在我们的代码中我们只使用
      Int
      s,GHC也足够聪明,可以自动将我们的代码转换为使用
      Int
      ,并实现更高的效率,避免装箱。如果我们要求GHC“转储核心”,以便我们可以看到优化的结果,就可以观察到这一点

      例如,编译

      f :: Int -> Int
      f 0 = 0
      f n = n + f (n-1)
      
      GHC生成一个较低级别的版本(这是GHC核心,不是Haskell,但它非常相似,可以理解):


      请注意
      go
      go x r k=…
      ==
      go x r=\k->…
      。这是在折叠列表时安排从左到右信息流的标准技巧(
      go
      用作缩减函数,在
      foldr go(\\\\\->n)ls 0
      )。这里是
      [0]的计数
      ,解释为每个步骤的首字母
      k=0
      (k+1)
      k
      是一个不幸的命名选择,
      i
      似乎更好;
      k
      被不相关的“常量”和“延续”重载,而不仅仅是“计数器”w