openlang

Strict를 켜니 지연 레코드 갱신이 뜻밖에 강제돼요

안녕하세요,

제가 잘못 본 건지, 버그인지, 레코드 갱신 디슈가링을 바꾸면서 뜻밖의 결과가 생긴 건지 모르겠지만, 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을 엄격하게 만드는 건 대체로 괜찮지만(수많은 ! 뱅 패턴을 안 써도 되니까요), 명시적으로 갱신하는 필드가 지연 필드로 선언되어 있다면 버그거나 우연한 설계 결과 같아요.

어떻게 보시나요?

영어에서 번역원문 보기

이어지는 대화 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가 건드릴 수 있게 되었다는 거예요.

    저는 버그라고 봅니다. let revised = member { ... }에서 revised는 생성자까지 평가되어야 하고, 그 과정에 갱신된 지연 필드 자체를 평가할 필요는 없어요. 지금까지 아무도 발견하지 못했다니 놀랍네요.

    영어에서 번역원문 보기

같은 질문의 다른 갈래

  1. gcc에서 AT&T 대신 Intel 스타일의 어셈블리를 출력하려면?

    gcc -S으로 나오는 어셈블리는 AT&T 문법인데, 대신 Intel 문법으로 파일을 출력하게 할 수 있나요? 아니면 한 문법을 다른 문법으로 변환할 수 있나요?

    답글 3최근 활동 assembly