seq在Haskell中实际做什么?

seq在Haskell中实际做什么?,haskell,functional-programming,lazy-evaluation,order-of-execution,weak-head-normal-form,Haskell,Functional Programming,Lazy Evaluation,Order Of Execution,Weak Head Normal Form,从我读到的 它的操作如下:当计算seq表达式时,它强制计算其第一个参数,然后返回其第二个参数。它实际上对第一个参数没有任何作用:seq仅作为强制计算该值的一种方式存在 我之所以强调这一点,是因为对我来说,这意味着这两件事发生的顺序 从我读到的 如果a位于底部,则序列a b的值位于底部,否则等于b。换句话说,它将第一个参数a计算为弱头范式(WHNF)。通常引入seq是为了通过避免不必要的惰性来提高性能 关于求值顺序的说明:表达式seq A b不保证将在b之前求值A。seq提供的唯一保证是在seq返

从我读到的

它的操作如下:当计算
seq
表达式时,它强制计算其第一个参数,然后返回其第二个参数。它实际上对第一个参数没有任何作用:
seq
仅作为强制计算该值的一种方式存在

我之所以强调这一点,是因为对我来说,这意味着这两件事发生的顺序

从我读到的

如果
a
位于底部,则
序列a b
的值位于底部,否则等于
b
。换句话说,它将第一个参数
a
计算为弱头范式(WHNF)。通常引入seq是为了通过避免不必要的惰性来提高性能

关于求值顺序的说明:表达式
seq A b
不保证将在
b
之前求值
A
seq
提供的唯一保证是在
seq
返回值之前,将对
a
b
进行评估。特别是,这意味着可以在
a
之前对
b
进行评估。[……]

此外,如果我从那里点击
#Source
链接,页面不存在,因此我看不到
seq
的代码

这似乎与下面的评论一致:

无法在普通Haskell中定义[…]
seq

另一方面(或实际上,在同一方面),另一条评论是:

“real”在GHC.Prim中定义为
seq::a->b->b;seq=设x=x在x中
这只是一个虚拟定义。基本上,
seq
是由编译器专门处理的语法

有人能解释一下这个话题吗?特别是在以下方面:

  • 什么来源是正确的
  • seq
    的实现真的不能在Haskell中写入吗?
    • 如果是的话,这意味着什么?它是原始的吗?这告诉了我
      seq
      实际做了什么
  • seq a中,至少在
    b
    使用
    a
    的情况下,b
    保证在
    b
    之前进行评估,例如
    seq a(a+x)

    • 现实世界中的哈斯克尔错了,你引用的所有其他东西都是正确的。如果您非常关心评估顺序,请改用
      pseq

      seq
      在两个thunk之间引入了人工数据依赖关系。通常,thunk只有在模式匹配需要时才被迫求值。如果thunk
      a
      包含{…}的表达式
      案例b,则强制
      a
      也强制
      b
      。因此,这两者之间存在依赖关系:为了确定
      a
      的值,我们必须评估
      b

        │
      ┌─▼───────┐
      │ seq a b │
      └─┬─────┬─┘
        │     │  
      ┌─▽─┐ ┌─▼─┐
      │ a │ │ b │
      └───┘ └───┘
      
      seq
      指定任意两个thunk之间的这种关系。当强制执行
      seq c d
      时,除了强制执行
      d
      之外,还强制执行
      c
      。请注意,我之前没有说过:根据标准,实现可以在
      d
      之前强制
      c
      ,或者在
      c
      之前强制
      d
      ,甚至可以强制执行它们的混合。只要求如果
      c
      没有停止,那么
      seq cd
      也不会停止。如果要保证评估顺序,可以使用
      pseq

      下图说明了不同之处。黑箭头(▼) 表示实际的数据依赖关系,可以使用
      大小写
      表示这种依赖关系;白色箭头(▽) 指示人为依赖项

      • 强制执行
        seq a b
        必须同时强制执行
        a
        b

          │
        ┌─▼───────┐
        │ seq a b │
        └─┬─────┬─┘
          │     │  
        ┌─▽─┐ ┌─▼─┐
        │ a │ │ b │
        └───┘ └───┘
        
      • 强制
        pseq a b
        必须强制
        b
        ,后者必须首先强制
        a

          │
        ┌─▼────────┐
        │ pseq a b │
        └─┬────────┘
          │
        ┌─▼─┐
        │ b │
        └─┬─┘
          │
        ┌─▽─┐
        │ a │
        └───┘
        
      目前,它必须作为一个内在的实现,因为它的类型,
      for all a b.a->b->b
      ,声称它适用于任何类型
      a
      b
      ,没有任何约束。它过去属于一个类型类,但由于类型类版本被认为具有较差的ergo,它被删除并成为一个原语nomics:adding
      seq
      试图修复一个深度嵌套的函数调用链中的性能问题,需要在链中的每个函数上添加一个样板
      seq
      约束(我更喜欢明确性,但现在很难更改)

      因此,
      seq
      ,以及它的语法糖,比如
      data
      类型中的严格字段或模式中的
      BangPatterns
      ,就是通过将某个内容附加到将要评估的其他内容的评估中来确保对其进行评估。经典示例是
      foldl'
      。这里,
      seq
      确保递归调用是强制的,累加器也是强制的:

      foldl' :: (a -> b -> a) -> a -> [b] -> a
      foldl' f acc [] = acc
      foldl' f acc (x : xs)
        = acc' `seq` foldl' f acc' xs
        where
          acc' = f acc x
      

      编译器要求if
      f
      是严格的,例如
      (+)
      在像
      Int
      这样的严格数据类型上,累加器在每一步都被简化为
      Int
      ,而不是构建一个只在最后进行评估的thunk链。

      其他答案已经讨论了
      seq
      的含义及其与
      pseq
      的关系。但似乎有一些c关于
      seq
      警告的确切含义

      实际上,从技术上讲,代码< > SEQ B <代码>不能保证<代码> < <代码> >将在<代码> b/COD>之前进行评估。这可能看起来很麻烦:如果是这样的话,它怎么可能达到它的目的呢?让我们考虑乔恩在回答中给出的例子:

      foldl' :: (a -> b -> a) -> a -> [b] -> a
      foldl' f acc [] = acc
      foldl' f acc (x : xs)
        = acc' `seq` foldl' f acc' xs
        where
          acc' = f acc x
      
      当然,我们关心的是在这里递归调用之前对
      acc'
      进行评估。如果不是这样,那么
      foldl'
      的全部目的就失去了!那么为什么不在这里使用
      pseq
      呢?而且
      seq
      真的就是这样吗
      g a b c = (a `pseq` b) + c
      
      foo (x + y)
      
      f a (b + 1) c
      
      acc' `seq` foldl' f acc' xs