openlang

启用 Strict 后惰性记录更新被意外强制求值

你好,

不知道是我弄错了、这是个 bug,还是修改记录更新的去语法糖过程带来的意外结果,但启用 Strict 后,更新惰性记录字段似乎会强制求值。构造记录时并不会强制求值这个字段。

这里有个测试例子。

{-# LANGUAGE Strict, OverloadedRecordDot #-}
import Prelude hiding (id)
import Debug.Trace

data Member = Member
  {id :: Int, years :: ~Int}

main = do
  let member = Member {id = 17, years = 39}
  let revised = member {years = trace "FORCED" 46}
  print revised.id

GHC v9.12.4 会输出 “FORCED”。v9.2.8 不会。当然,禁用 Strict 也不会输出。

大致的 core 是这样

case trace "FORCED" 46 of years
  __DEFAULT -> case member of
    Member id _ -> Member id years

按我的理解,trace 外面的 case-of 强制了求值。我通常不介意 Strict 让所有 case 和 let 都严格求值,毕竟能省掉无数个 ! bang 模式,但显式更新的字段既然声明为惰性,这似乎就是 bug 或者无意中的设计结果。

你们怎么看?

从英语翻译查看原文

后续讨论 3

顺着彼此的依据读下去
  1. 固定链接

    看起来最早受影响的是 9.6,可能与“已全面支持 GADT 以及其他存在类型数据的记录更新(https://downloads.haskell.org/ghc/9.6.1/docs/users_guide/9.6.1-notes.html )”有关。可以看类型检查器源代码(https://downloads.haskell.org/~ghc/9.6.7/docs/libraries/ghc-9.6.7/src/GHC.Tc.Gen.Expr.html )中的 Note [Record Updates]。其中的解释如下

    To typecheck a record update, we desugar it first.  Suppose we have
        data T p q = T1 { x :: Int, y :: Bool, z :: Char }
                   | T2 { v :: Char }
                   | T3 { x :: Int }
                   | T4 { p :: Float, y :: Bool, x :: Int }
                   | T5
    Then the record update `e { x=e1, y=e2 }` desugars as follows
    
           e { x=e1, y=e2 }
        ===>
           let { x' = e1; y' = e2 } in
           case e of
              T1 _ _ z -> T1 x' y' z
              T4 p _ _ -> T4 p y' x'

    也就是说,类型检查会把这些严格求值的 let 提到外面。我猜在 9.4 及以前,这是在转成 Core 的去语法糖过程中完成的,Strict 影响不到那里,现在放到了类型检查阶段,Strict 就能影响它了。

    我觉得这是个 bug。对于 let revised = member { ... },对 revised 求值应该只需要到构造器,不需要对更新后的惰性字段本身求值。居然之前没人注意到,有点意外。

    从英语翻译查看原文

同一个问题的其他角度

  1. 怎样让 gcc 输出 Intel 风格汇编而不是 AT&T?

    使用 gcc -S 时,生成的汇编采用 AT&T 语法。能否要求输出文件改用 Intel 语法?或者能否把一种语法转换成另一种?

    回复 3最近活动 assembly