Что на самом деле делает seq в Haskell?

Я прочитал в Real World Haskell

Он работает следующим образом: когда выражение seq вычисляется, оно заставляет вычислить его первый аргумент, затем возвращает его второй аргумент. На самом деле он ничего не делает с первым аргументом: seq существует исключительно как способ принудительно вычислить это значение.

где я выделил затем, потому что для меня это означает порядок, в котором происходят два события.

Из Hackage I читать

Значение seq a b является нижним, если a является нижним, и в противном случае равно b. Другими словами, он оценивает первый аргумент a в слабую нормальную форму головы (WHNF). seq обычно вводится для повышения производительности, избегая ненужной лени.

Примечание по порядку оценки: выражение seq a b не гарантирует, что a будет вычислено до b. Единственная гарантия, которую дает seq, заключается в том, что и a, и b будут оценены до того, как seq вернет значение. В частности, это означает, что b может быть оценено до a. […]

Кроме того, если я щелкну оттуда ссылку # Source, страница не существует, поэтому я не могу увидеть код seq.

Это похоже на комментарий под этим ответом:

[…] seq нельзя определить в обычном Haskell

С другой стороны (или на самом деле с той же стороны) другой комментарий гласит:

«Настоящее» seq определено в GHC.Prim как seq :: a -> b -> b; seq = let x = x in x. Это всего лишь фиктивное определение. В основном seq - это специальный синтаксис, обрабатываемый компилятором.

Кто-нибудь может пролить свет на эту тему? Особенно с точки зрения:

  • Какой источник правильный?
  • Is seq's implementation really not writable in Haskell?
    • If so, what does it even mean? That it is a primitive? What does this tell me about what seq actually does?
  • In seq a b a гарантированно оценивается до b, по крайней мере в том случае, если b использует a, например seq a (a + x)?



Ответы (3)


В других ответах уже обсуждалось значение seq и его отношение к pseq. Но, похоже, существует некоторая путаница в отношении того, каковы последствия предупреждений seq.

Технически говоря, это правда, что a `seq` b не гарантирует, что a будет оценен до b. Это может показаться тревожным: как это могло бы служить своей цели, если бы это было так? Давайте рассмотрим пример, который Джон привел в их ответ:

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 действительно здесь. GHC никогда не выберет компиляцию foldl' таким образом, чтобы он оценивал рекурсивный вызов перед вычислением acc', поэтому желаемое поведение сохраняется. Разница между seq и pseq скорее заключается в том, какая гибкость у оптимизатора для принятия другого решения, когда он думает, что у него есть для этого особенно веские причины.

Понимание строгости seq и pseq

Чтобы понять, что это значит, мы должны научиться думать немного как оптимизатор GHC. На практике единственная конкретная разница между seq и pseq заключается в том, как они влияют на анализатор строгости:

  1. seq считается строгим в обоих своих аргументах. То есть в таком определении функции, как

    f a b c = (a `seq` b) + c
    

    f будет считаться строгим по всем трем аргументам.

  2. pseq аналогичен seq, но считается строгим только в его первом аргументе, а не во втором. Это означает, что в определении функции вроде

    g a b c = (a `pseq` b) + c
    

    g будет считаться строгим в a и c, но не b.

Что это значит? Что ж, давайте сначала определим, что значит для функции, прежде всего, «быть строгой в одном из своих аргументов». Идея состоит в том, что если функция является строгой в отношении одного из своих аргументов, то вызов этой функции гарантированно оценит этот аргумент. Это имеет несколько значений:

  • Предположим, у нас есть функция foo :: Int -> Int, которая имеет строгий аргумент, и предположим, что у нас есть вызов foo, который выглядит следующим образом:

    foo (x + y)
    

    Наивный компилятор Haskell создаст преобразователь для выражения x + y и передаст полученный преобразователь в foo. Но мы знаем, что оценка foo обязательно вызовет преобразование, поэтому от этой лени мы ничего не выиграем. Было бы лучше сразу оценить x + y, а затем передать результат foo, чтобы сохранить ненужное выделение преобразователя.

  • Поскольку мы знаем, что нет причин передавать преобразование в foo, мы получаем возможность внести дополнительную оптимизацию. Например, оптимизатор может выбрать внутреннюю перезапись foo, чтобы взять распакованный Int# вместо Int, избегая не только конструкции преобразователя для x + y, но и вообще избегая упаковки результирующего значения. Это позволяет передавать результат x + y напрямую в стек, а не в кучу.

Как видите, анализ строгости очень важен для создания эффективного компилятора Haskell, поскольку он позволяет ему принимать гораздо более разумные решения, среди прочего, о том, как компилировать вызовы функций. По этой причине мы обычно хотим, чтобы анализ строгости находил как можно больше возможностей для активной оценки вещей, позволяя нам сэкономить на бесполезном распределении кучи.

Имея это в виду, давайте вернемся к нашим примерам f и g выше. Давайте подумаем, какой строгости мы интуитивно ожидаем от этих функций:

  1. Напомним, что тело f - это (a `seq` b) + c. Даже если мы полностью проигнорируем специальные свойства seq, мы знаем, что в конечном итоге он оценивает свой второй аргумент. Это означает, что f должен быть по крайней мере таким же строгим, как если бы его тело было просто b + ca полностью неиспользованным).

    Мы знаем, что оценка b + c должна фундаментально оценивать как b, так и c, поэтому f должен, по крайней мере, быть строгим как в b, так и в c. Строго ли это в a - вопрос более интересный. Если бы seq был на самом деле просто flip const, этого не было бы, поскольку a не использовался бы, но, конечно, весь смысл seq состоит в том, чтобы ввести искусственную строгость, поэтому на самом деле f также считается строгим в a .

    К счастью, строгость f, о которой я говорил выше, полностью соответствует нашей интуиции о том, какой строгостью она должна быть. f строг во всех своих аргументах, как и следовало ожидать.

  2. Интуитивно понятно, что все вышеперечисленные аргументы в пользу f также должны применяться к g. Единственное отличие состоит в замене seq на pseq, и мы знаем, что pseq обеспечивает более сильную гарантию порядка оценки, чем seq, поэтому мы ожидаем, что g будет не менее строгим, чем f… который так сказать, также строг во всех своих аргументах.

    Однако, что примечательно, это не строгость, которую GHC устанавливает для g. GHC считает g строгим в a и c, но не b, хотя по нашему определению строгости, приведенному выше, g явно является строгим в b: b должен быть оценен для g для получения результата! Как мы увидим, именно это несоответствие делает pseq таким глубоко волшебным и почему это вообще плохая идея.

Последствия строгости

Теперь мы увидели, что seq ведет к строгости, которую мы ожидаем, а pseq - нет, но не сразу понятно, что это означает. Чтобы проиллюстрировать это, рассмотрим возможный сайт звонков, где используется f:

f a (b + 1) c

Мы знаем, что f строго во всех своих аргументах, поэтому по тем же рассуждениям, которые мы использовали выше, GHC должен быстро оценить b + 1 и передать свой результат f, избегая преобразований.

На первый взгляд может показаться, что все хорошо, но подождите: что, если a - это преобразователь? Несмотря на то, что f также является строгим в a, это всего лишь простая переменная - возможно, она была передана в качестве аргумента откуда-то еще - и у GHC нет никаких причин для принудительного применения a здесь, если f собирается принудительно это сделать. Единственная причина, по которой мы форсируем b + 1, - это избавить новый преобразователь от создания, но мы ничего не сохраняем, кроме форсирования уже созданного a на сайте вызова. Это означает, что a на самом деле может быть передан как неоцененный преобразователь.

Это своего рода проблема, потому что в теле f мы написали a `seq` b, запрашивая оценку a до b. Но по нашим рассуждениям выше, GHC просто пошли дальше и сначала оценили b! Если нам действительно, действительно нужно убедиться, что b не будет оцениваться до тех пор, пока не пройдет a, этот тип нетерпеливой оценки не может быть разрешен.

Конечно, именно поэтому pseq считается ленивым во втором аргументе, хотя на самом деле это не так. Если мы заменим f на g, то GHC послушно выделит новый преобразователь для b + 1 и передаст его в кучу, гарантируя, что он не будет оценен слишком рано. Это, конечно, означает большее выделение кучи, отсутствие распаковки и (что хуже всего) отсутствие распространения информации о строгости дальше по цепочке вызовов, создавая потенциально каскадные пессимизации. Но вот чего мы просили: любой ценой не оценивать b слишком рано!

Надеюсь, это иллюстрирует, почему pseq соблазнительно, но в конечном итоге контрпродуктивно, если вы действительно не знаете, что делаете. Конечно, вы гарантируете оценку, которую ищете… но какой ценой?

Выводы

Надеюсь, приведенное выше объяснение ясно показало, как seq и pseq имеют преимущества и недостатки:

  • seq отлично работает с анализатором строгости, выявляя гораздо больше потенциальных оптимизаций, но эти оптимизации могут нарушить ожидаемый нами порядок оценки.

  • pseq любой ценой сохраняет желаемый порядок оценки, но делает это только путем откровенной лжи анализатору строгости, чтобы он не подходил к делу, резко ослабляя его способность помогать оптимизатору делать хорошие вещи.

Как мы узнаем, какие компромиссы выбрать? Хотя теперь мы можем понять, почему seq может иногда не оценивать свой первый аргумент перед вторым, у нас больше нет оснований полагать, что это нормально, чтобы это произошло.

Чтобы развеять ваши страхи, давайте сделаем шаг назад и задумаемся, что же здесь происходит на самом деле. Обратите внимание, что GHC никогда фактически не компилирует a `seq` b выражение таким образом, что a не может быть вычислен до b. Учитывая такое выражение, как a `seq` (b + c), GHC никогда не нанесет вам тайного удара в спину и не оценит b + c перед тем, как оценить a. Скорее, то, что он делает, гораздо более тонкое: оно может косвенно привести к индивидуальной оценке b и c перед вычислением общего b + c выражения, поскольку анализатор строгости заметит, что общее выражение все еще строго в обоих b и c.

Как все это сочетается друг с другом, невероятно сложно, и это может вскружить вам голову, так что, возможно, вы не найдете предыдущий абзац таким успокаивающим. Но чтобы сделать эту мысль более конкретной, вернемся к примеру foldl' в начале этого ответа. Напомним, что он содержит такое выражение:

acc' `seq` foldl' f acc' xs

Чтобы избежать взрыва преобразователя, нам нужно оценить acc' перед рекурсивным вызовом foldl'. Но, учитывая приведенные выше рассуждения, так будет всегда! Разница, которую делает seq здесь относительно pseq, опять же, актуальна только для анализа строгости: она позволяет GHC сделать вывод, что это выражение также является строгим в f и xs, а не только в acc', который в этой ситуации практически не меняется в все:

  • Общая функция foldl' по-прежнему не считается строгой в f, поскольку в первом случае функции (где xs равно []) f не используется, поэтому для некоторых шаблонов вызовов foldl' является ленивым в f.

  • foldl' может считаться строгим в xs, но здесь это совершенно неинтересно, поскольку xs является лишь частью одного из аргументов foldl', и эта информация о строгости никак не влияет на строгость foldl'.

Итак, если здесь нет никакой разницы, почему бы не использовать pseq? Что ж, предположим, что foldl' встроен некоторое конечное число раз на сайте вызова, поскольку, возможно, форма его второго аргумента частично известна. Информация о строгости, предоставляемая seq, может затем вызвать несколько дополнительных оптимизаций на сайте вызова, что приведет к цепочке выгодных оптимизаций. Если бы использовалось pseq, эти оптимизации были бы скрыты, и GHC дал бы худший код.

Таким образом, реальный вывод здесь состоит в том, что, хотя seq может иногда не оценивать свой первый аргумент раньше второго, это верно только технически, способ, которым это происходит, неуловимый, и маловероятно, что это нарушит вашу программу. Это не должно вызывать особого удивления: seq - это инструмент, который авторы GHC ожидают от программистов, которые будут использовать в этой ситуации, поэтому было бы довольно грубо с их стороны заставить его делать неправильные вещи! seq - идиоматический инструмент для этой работы, а не pseq, поэтому используйте seq.

Когда же тогда вы используете pseq? Только тогда, когда вы действительно действительно заботитесь об очень конкретном порядке оценки, что обычно происходит только по одной из двух причин: вы используете параллелизм на основе par или вы используете unsafePerformIO и заботитесь о порядке побочных эффектов. Если вы не делаете ничего из этого, не используйте pseq. Если все, что вас волнует, - это варианты использования, такие как foldl', когда вы просто хотите избежать ненужного накопления переходов, используйте seq. Это то, для чего он нужен.

person Alexis King    schedule 06.04.2021
comment
Может, я тупой, но я еще не понял всей истории. Например, вы пишете GHC никогда не выберет [...]; так что все дело в том, что я доверяю GHC? На мой взгляд, это означает, что в отношении того, что делает seq, я не могу доверять компилятору только потому, что это компилятор с нулевым уровнем ошибок, 100% совместимый со стандартами (если он существует): чтобы я мог ему доверять, он должен гарантировать больше, чем что, например что он достаточно умен. Это вообще измеримая вещь? Другими словами, если завтра GHC выберет [...], будет ли он менее совместимым со стандартами, чем сегодня? - person Enlico; 07.04.2021
comment
Еще один момент, который мне не помогает, - это гибкость оптимизатора для изменения правил. Нарушение правил? Разве мы не в стране детерминизма? - person Enlico; 07.04.2021
comment
@Enlico Вы абсолютно правы в том, что один только отчет Haskell не гарантирует полезной реализации seq, поэтому, если вы ищете что-то, прописанное в стандарте, который требует поведения, на которое мы все полагаемся seq, вы никогда этого не найдете. Прости. Но GHC является стандартом де-факто в наши дни и действует уже довольно давно, и хотя он может нигде не упоминать эту гарантию явно, я могу вас заверить, что он ее предоставляет. Я на 100% уверен, что если seq перестанет работать для этого, это будет считаться серьезной ошибкой и будет исправлено в кратчайшие сроки. - person Alexis King; 07.04.2021
comment
@Enlico Что касается "страны детерминизма", я не понимаю, что вы имеете в виду. Отчет Haskell очень старается не указывать почти ничего о порядке оценки, поэтому компиляторы могут свободно вносить оптимизацию по своему усмотрению. Это не идет на компромисс с семантическим детерминизмом или чистотой, потому что порядок оценки невозможно наблюдать изнутри программы Haskell без использования unsafePerformIO (что, собственно, и является большей частью того, почему это небезопасно). seq очень необычен в том смысле, что он должен влиять на порядок оценки, но он совершенно уникален, поэтому он должен быть магическим примитивом. - person Alexis King; 07.04.2021
comment
К вашему первому комментарию: хорошо, это (я надеюсь!) Дает мне лучший взгляд на всю тему. Правильно ли я скажу, что с = foldl' f acc' xs GHC может / решил бы никогда не оценивать acc' (ну, не до достижения пустого xs и возврата), тогда как в = acc' seq` foldl 'f acc' xs, it will have to, and so it will have a chance to say _oh, since I have to evaluate acc '' как LHS seq и он используется в правой части seq, я сначала оценю LHS, чтобы я мог повторно использовать ее в правой части _? - person Enlico; 07.04.2021
comment
К вашему второму комментарию: я имел в виду, что мое понимание правил таково, что они являются фактом, их нельзя отклонить или уклониться, так что вообще означает отклонение правил? Я понимаю, что стандарт оставляет место (я бы сказал, степени свободы) для разных реализаций одного и того же, но не означает ли это, что две разные реализации по-разному соблюдают правила? - person Enlico; 07.04.2021
comment
@Enlico (мета: вы можете заключить код в комментарии в двойные обратные кавычки для правильного выделения, например this`op`that`, который затем отображается this`op`that). - person Will Ness; 07.04.2021
comment
@Enlico re foldl', да. where { acc' = f acc x } определяет acc'. но ничего в том, как acc' используется в гипотетическом foldl_ f acc (x : xs) = foldl_ f acc' xs where { acc' = f acc x }, заставляет его. так что это преобразователь и остается преобразователем. т.е. foldl_ f acc [a,b,c] === let {x = f acc a; y = f x b; z = f y c} in z - даже после достижения этого конца списка. Теперь представьте, что вы печатаете это z - его определение относится к определению y, которое относится к определению x и т. Д. Три преобразователя для принудительного выполнения вместо одного значения чтобы сразу получить. :) (или я неправильно понял ваш qn?) - person Will Ness; 07.04.2021
comment
@Enlico Я согласен с вами, что мое использование фразы «нарушать правила» бесполезно сбивает с толку - на самом деле здесь нет никакого «правила», поэтому я отредактировал свой ответ, чтобы удалить его. Предполагаемое значение заключалось в том, что a `seq` b дает компилятору возможность иногда оценивать (части) b перед a, так что ему разрешается делать другие оптимизации, которые особенно полезны, но на самом деле не влияют на общую направленность использования seq. - person Alexis King; 08.04.2021
comment
@Enlico В целом, я думаю, что это стоит повторить: в отчете Haskell мало говорится о seq, потому что его поведение замечательно сложно определить, не уточняя его таким образом, чтобы многие чрезвычайно полезные оптимизации запрещены. Но пусть это не сбивает вас с толку: seq предназначен для использования с этой целью во всех компиляторах Haskell, а не только в GHC. Таким образом, хотя его поведение не является формально гарантированным, оно неофициально гарантируется, и вам не следует слишком беспокоиться о неплотности его спецификации по этой причине. - person Alexis King; 08.04.2021
comment
если не отчет, то по крайней мере в документации GHC следует добавить, что цель seq a b - служить сигналом компилятору для предотвращения накопления переходов в a при оценке b. может что-то в этом роде. - person Will Ness; 08.04.2021

seq вводит искусственную зависимость данных между двумя преобразователями. Обычно преобразователь вынужден выполнять оценку только тогда, когда этого требует сопоставление с образцом. Если преобразователь a содержит выражение case b of { … }, то форсирование a также вызывает b. Таким образом, между ними существует зависимость: чтобы определить значение a, мы должны оценить b.

seq определяет эту взаимосвязь между любыми двумя произвольными преобразователями. Когда seq c d принудительно, c принудительно в дополнение к d. Обратите внимание, что я не говорю раньше: согласно стандарту реализация может принудительно принудительно устанавливать c перед d или d перед c или даже их сочетание. Требуется только, чтобы если c не остановился, то и seq c d также не остановился. Если вы хотите гарантировать порядок оценки, вы можете использовать pseq.

Диаграммы ниже иллюстрируют разницу. Черная стрелка (▼) указывает на реальную зависимость данных, которую можно выразить с помощью case; белая стрелка (▽) указывает на искусственную зависимость.

  • При принудительном использовании seq a b должны быть принудительно задействованы как a, так и b.

      │
    ┌─▼───────┐
    │ seq a b │
    └─┬─────┬─┘
      │     │  
    ┌─▽─┐ ┌─▼─┐
    │ a │ │ b │
    └───┘ └───┘
    
  • Форсирование pseq a b должно форсировать b, которое сначала должно форсировать a.

      │
    ┌─▼────────┐
    │ pseq a b │
    └─┬────────┘
      │
    ┌─▼─┐
    │ b │
    └─┬─┘
      │
    ┌─▽─┐
    │ a │
    └───┘
    

В его нынешнем виде он должен быть реализован как встроенный, потому что его тип forall a b. a -> b -> b утверждает, что он работает для любых типов a и b без каких-либо ограничений. Раньше он принадлежал к классу типов, но его удалили и превратили в примитив, потому что версия класса типов считалась плохо эргономичной: добавление seq, чтобы попытаться исправить проблему производительности в глубоко вложенной цепочке вызовов функций, потребовало бы добавления шаблона Seq a ограничение на каждую функцию в цепочке. (Я бы предпочел ясность, но сейчас ее будет сложно изменить.)

Итак, 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

Этот запрашивает у компилятора, что если f является строгим, например (+) для строгого типа данных, такого как Int, то аккумулятор уменьшается до Int на каждом шаге, вместо того, чтобы строить цепочку преобразователей, которая должна быть оценивается только в конце.

person Jon Purdy    schedule 04.04.2021
comment
Последний пример - это как раз то, что меня смущает в отношении всего seq против pseq. Поскольку seq не гарантирует порядок оценки, кажется, что нет гарантии, что acc' будет принудительно перед рекурсивным вызовом, только то, что это произойдет в какой-то момент. Следовательно, действительно может случиться так, что цепочка преобразователей все равно будет построена до того, как будет выполняться каждый acc'. GHC оценивает acc' рано (AFAIK), но похоже, что здесь следует использовать pseq (?). Однако я бы хотел, чтобы в этом было доказано, что я ошибаюсь. Однако, если я прав, я не понимаю, как seq можно использовать для обеспечения производительности. - person chi; 04.04.2021
comment
@chi: Строго (ха) Я думаю, вы правы: pseq - правильный выбор для производительности. Единственная причина, по которой я действительно могу думать об использовании seq вместо pseq, заключается в выражении типа (a `seq` b) `pseq` c, где я действительно забочусь о том, чтобы a и b оценивались до c, а я не < / i> неважно, идет ли первым a или b. В Отчете уделяется особое внимание тому, чтобы не обязывать стратегию реализации, но я думаю, что он должен был обязывать разрешить программисту требовать секвенирования и совместного использования. В настоящее время существует очень много кода, тонко зависящего от семантики GHC seq и let. - person Jon Purdy; 04.04.2021
comment
Я действительно говорю о строгих гарантиях, поскольку именно они отличает seq и pseq. Теоретически можно было бы определить seq x y = pseq y x и уважать требования Отчета, я думаю, но это ужасно нарушило бы производительность большого количества кода, который в таком случае больше нельзя было бы использовать. Возможно, нам нужна модель затрат для Haskell (по крайней мере, асимптотическая), но я думаю, что трудно ее определить, не слишком ограничивая реализацию. - person chi; 04.04.2021
comment
@chi, я думаю, ты прав, строго говоря. Но GHC пытается оптимизировать код, а не деоптимизировать его. Так что в большинстве случаев seq действительно дает хорошие результаты. В foldl', когда произойдут некоторые преобразования, у нас будет что-то похожее на go [] acc = acc; go (x : xs) acc = acc `seq` go xs (f acc x). У GHC есть два практических варианта: acc `pseq` go xs (f acc x) и let r = go xs (f acc x) in r `pseq` acc `pseq` r. Прежний код хорошо рекурсивен, и, следовательно, это очевидная вещь. Последнее - беспорядок; кто бы это сделал? - person dfeuer; 04.04.2021
comment
Скорее всего, вы столкнетесь с переупорядочиванием во что-то вроде f x y = y `seq` x + y. Есть очень хороший шанс, что y `seq` будет полностью проигнорирован, и GHC решит для себя, следует ли сначала форсировать x или y. Но в типичных случаях это просто не повлияет на производительность. - person dfeuer; 04.04.2021
comment
@dfeuer то, что вы сказали, мы надеемся, но это не гарантия. вся эта ситуация с seq не имеет особого смысла. зачем вообще seq, если только pseq действительно это делает? и если нет гарантии с seq, то и с паттернами взрыва тоже нет. (!!?) - person Will Ness; 05.04.2021
comment
@WillNess, я не знаю полных деталей, но я полагаю, что такой экстремальный контроль порядка будет мешать анализу спроса и преобразованию рабочего-обертки, которые действительно важны для оптимизации. В коде, который будет скомпилирован с оптимизацией, было бы лучше думать о seq как о разрешении компилятору оценить что-то раньше (что он, вероятно, очень хочет сделать), а не принуждение это сделать так. - person dfeuer; 05.04.2021
comment
@WillNess, вот небольшой пример: скажем, я пишу cond :: Bool -> Int -> Int; cond True x y = x `seq` y; cond False x y = y `seq` x. GHC рассмотрит это и скажет, что cond является строгим по всем трем своим аргументам, и поэтому ему разрешено оценить все три перед вызовом cond. Среди прочего, это позволяет использовать worker-wrapper (чего он, вероятно, здесь не будет, но сделает вид, что функция более сложная). wcond :: Bool -> Int# -> Int# -> Int#; wcond True x _ = x; wcond False _ y = y; cond b (I# x) (I# y) = I# (wcond b x y). Множество возможностей распаковки! - person dfeuer; 05.04.2021
comment
@dfeuer Понятно, спасибо. ---- все, что мне нужно, это ясность. если seq всегда работает, когда я использую его для предотвращения взрыва преобразователей, они должны просто сказать об этом в документации, этими самыми словами. явно. если иногда и лучше, то это несомненный плюс. но нам нужна ясность, а не расплывчатость. - person Will Ness; 05.04.2021
comment
@JonPurdy, я немного прояснил свой вопрос. Не могли бы вы взглянуть на него, если увидите возможность интегрировать свой ответ с другими частями? - person Enlico; 05.04.2021
comment
@WillNess, в оптимизации кода не всегда есть поправки. - person dfeuer; 05.04.2021
comment
@dfeuer, но это не оптимизация. это важная часть практического Haskell. точнее, это должно быть. В конце концов, схемы взрыва есть в руководстве, это не какие-то секреты. - person Will Ness; 05.04.2021
comment
@WillNess, давайте обсудим это в другом месте. Не тот форум / формат. - person dfeuer; 05.04.2021
comment
@WillNess Вы можете найти обсуждение в заголовке stackoverflow.com/questions/48410245/ выяснения. - person Alexis King; 06.04.2021

Real World Haskell ошибается, и все остальное, что вы цитируете, верны. Если вам важен порядок оценки, используйте вместо него pseq.

person Daniel Wagner    schedule 04.04.2021