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

и, насколько я понимаю, case-of вокруг trace форсирует вычисление. В целом я не против того, чтобы Strict делал все case и let строгими, избавляя от бесчисленных bang-паттернов !, но когда явно обновляемое поле объявлено ленивым, это выглядит как баг или случайное следствие дизайна.

Что вы об этом думаете?

Переведено с английскогоПоказать оригинал

Разговор продолжается 3

Следуя доводам
  1. Ссылка на ответ

    Похоже, первая затронутая версия — 9.6, возможно это связано с «Полностью поддержаны обновления записей с GADT и другими экзистенциальными типами данных (https://downloads.haskell.org/ghc/9.6.1/docs/users_guide/9.6.1-notes.html)». Посмотрите Note [Record Updates] в исходнике проверки типов (https://downloads.haskell.org/~ghc/9.6.7/docs/libraries/ghc-9.6.7/src/GHC.Tc.Gen.Expr.html). Там объясняется следующее

    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 выдавать ассемблер Intel вместо синтаксиса AT&T?

    При использовании gcc -S получается ассемблер AT&T. Можно ли вместо этого запросить для выходных файлов синтаксис Intel? Или…

    Ответы 3Последняя активность assembly