안녕하세요,
제가 잘못 본 건지, 버그인지, 레코드 갱신 디슈가링을 바꾸면서 뜻밖의 결과가 생긴 건지 모르겠지만, 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을 엄격하게 만드는 건 대체로 괜찮지만(수많은 ! 뱅 패턴을 안 써도 되니까요), 명시적으로 갱신하는 필드가 지연 필드로 선언되어 있다면 버그거나 우연한 설계 결과 같아요.
어떻게 보시나요?
영어에서 번역·원문 보기Hey,
I can't tell whether I'm mistaken, this is a bug, or changing record-update desugaring produced a surprising consequence, but updating a lazy record field with Strict enabled seems to force it. Constructing the record doesn't force that field.
Here's a sample test:
{-# 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
With GHC v9.12.4, “FORCED” appears. It doesn't with v9.2.8. Disabling Strict also prevents it, of course.
The approximate core looks like
case trace "FORCED" 46 of years
__DEFAULT -> case member of
Member id _ -> Member id years
and, as I understand it, the case-of around trace forces evaluation. I'm generally happy for Strict to make every case and let strict (sparing us countless ! bang patterns), but when the field being explicitly updated was declared lazy, this feels like a bug or an accidental design consequence.
How does that look to you?