Каковы точки строгости Хаскелла?



мы все знаем (или должны знать), что Haskell ленив по умолчанию. Ничто не оценивается, пока оно не должно быть оценено. Так когда же нужно что-то оценивать? Есть моменты, где Хаскелл должен быть строгим. Я называю это "точками строгости", хотя этот конкретный термин не так широко распространен, как я думал. По моим словам:




сокращение (или оценка) в Haskell только происходит в точках строгости.




Итак, вопрос: что, точно, являются ли точки строгости Хаскелла? моя интуиция говорит, что main,seq / bang patterns, pattern matching и any IO действие через main являются основными точками строгости, но я действительно не знаю, почему я это знаю.



(кроме того, если они не называются "точками строгости", что are они называются?)



Я думаю, что хороший ответ будет включать в себя некоторые дискуссии о WHNF и так далее. Я тоже представьте, что это может коснуться лямбда-исчисления.





Edit: дополнительные мысли по этому вопросу.



поскольку я размышлял над этим вопросом, я думаю, что было бы более ясно добавить что-то к определению точки строгости. Точки строгости могут иметь различные условиях и различной глубина (или строгость). Возвращаясь к моему определению, что "сокращение Haskell происходит только в точках строгости", давайте добавим это определение этого пункта: "точка строгости срабатывает только тогда, когда ее окружающий контекст оценивается или уменьшается."



Итак, позвольте мне попытаться начать с того ответа, который я хочу. main Это точка строгости. Он специально обозначен как основная точка строгости его контекста: программа. Когда программа (main's контекст) оценивается, активируется точка строгости main. Глубина Майна максимальна: она должна быть полностью оценена. Основная обычно состоит действий IO, которые также являются точками строгости, контекст которых main.



теперь попробуй: обсудить seq и сопоставление шаблонов в этих терминах. Объясните нюансы применения функции: насколько она строгая? Как это нет? А как же deepseq? let и case заявления? unsafePerformIO? Debug.Trace? Определения верхнего уровня? Строгие типы данных? Модели взрыва? Так далее. Сколько из этих элементов можно описать с точки зрения только seq или сопоставления с образцом?

696   7  

7 ответов:

хорошим местом для начала является понимание этой статьи:естественная семантика для ленивой оценки (Launchbury). Это скажет вам, когда выражения оцениваются для небольшого языка, подобного ядру GHC. Тогда остающийся вопрос заключается в том, как сопоставить полный Haskell с Core, и большая часть этого перевода дается самим отчетом Haskell. В GHC мы называем этот процесс "результате обессахаривания", потому что она удаляет синтаксический сахар.

Ну, это не вся история, потому что GHC включает в себя целый плот оптимизаций между десугарингом и генерацией кода, и многие из этих преобразований будут перестраивать ядро так, чтобы вещи оценивались в разное время (анализ строгости, в частности, приведет к тому, что вещи будут оцениваться ранее). Так что по-настоящему понять, как код программа будет оценена, вам нужно посмотреть на ядро, произведенное GHC.

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

Я бы, наверное, пересмотреть этот вопрос, при каких обстоятельствах Haskell будет оценивать выражение? (возможно, приколоть " к слабой голове нормальной формы.")

В первом приближении можно определить следующим образом:

  • выполнение действий ввода-вывода будет оценивать любые выражения, которые им "нужны."(Поэтому вам нужно знать, выполняется ли действие ввода-вывода, например, его имя main, или оно вызывается из main, и вам нужно знать, какое действие по необходимости.)
  • выражение, которое оценивается (Эй, это рекурсивное определение!) будет оценивать любые выражения, которые ему нужны.

из вашего интуитивного списка действия main и IO попадают в первую категорию, А seq и сопоставление шаблонов попадают во вторую категорию. Но я думаю, что первая категория больше соответствует вашей идее "точки строгости", потому что именно так мы заставляем оценку в Haskell становиться наблюдаемых эффекты для пользователей.

предоставление всех деталей конкретно является большой задачей, так как Haskell-это большой язык. Это также довольно тонко, потому что параллельный Haskell может оценивать вещи спекулятивно, даже если мы в конечном итоге не используем результат в конце: это третья порода вещей, которые вызывают оценку. Вторая категория довольно хорошо изучена: вы хотите посмотреть на требовательность функции участвует. Первая категория тоже может считаться своего рода "строгость", хотя это немного изворотливо, потому что evaluate x и seq x $ return () на самом деле разные вещи! Вы можете относиться к нему правильно, если вы даете какую-то семантику монаде IO (явно передавая RealWorld# токен работает для простых случаев), но я не знаю, есть ли имя для такого рода стратифицированного анализа строгости в целом.

C имеет понятие точки последовательности, которые являются гарантией для конкретных операций, что один операнд будет вычислен перед другим. Я думаю, что это самая близкая существующая концепция, но по существу эквивалентный термин строгость точки (или точка законную силу) больше соответствует мышлению Хаскелла.

на практике Haskell не является чисто ленивым языком: например, сопоставление шаблонов обычно строго (Таким образом, попытка соответствия шаблону заставляет оценку происходить, по крайней мере, достаточно далеко, чтобы принять или отклонить совпадение.

...

программисты также могут использовать seq примитив, чтобы заставить выражение вычислять независимо от того, будет ли когда-либо использоваться результат.

$! определяется в терминах seq.

-ленивый против нестрогого.

так что ваши мысли о !/$! и seq по существу верно, но сопоставление шаблонов подчиняется более тонким правилам. Вы всегда можете использовать ~ чтобы заставить ленивый по шаблону, конечно. Интересный момент из той же статьи:

анализатор строгости также ищет случаи, когда суб-выражения всегда требуются внешним выражением, и преобразует их в нетерпеливую оценку. Он может это сделать, потому что семантика (с точки зрения "дна") не делает изменение.

давайте продолжим вниз по кроличьей норе и посмотрим на документы для оптимизации, выполняемой GHC:

анализ строгости-это процесс, с помощью которого GHC пытается определить во время компиляции, какие данные определенно будут "всегда необходимы". Затем GHC может построить код, чтобы просто вычислить такие данные, а не обычный (более высокий накладные расходы) процесс для хранения вычисления и выполнения его позже.

- GHC Оптимизация: Анализ Строгости.

другими словами, строгий код может быть сгенерирован в любом месте в качестве оптимизации, потому что создание thunks неоправданно дорого, когда данные всегда будут нужны (и/или могут использоваться только один раз).

...больше не может быть выполнена оценка значения; он, как говорят, находится в нормальная форма. Если мы находимся на любом из промежуточных шагов, чтобы мы выполнили хоть какая-то оценка по значению, она находится в слабая голова нормальной формы (WHNF). (Существует также "нормальная форма головы", но она не используется в Haskell.) Полная оценка чего-то в WHNF сводит его к чему-то в нормальной форме...

-Wikibooks Haskell: Лень

(термин в голова нормальной формы если нет бета-redex в должности начальника1. Повторное выражение-это повторное выражение головы, если оно есть предшествовать только лямда осуществляющими забор воды субъектами не redexes 2.) Поэтому, когда вы начинаете заставлять thunk, вы работаете в WHNF; когда больше не осталось thunks, чтобы заставить, вы находитесь в нормальной форме. Еще один интересный момент:

...если в какой-то момент нам нужно было, скажем, распечатать z для пользователя, нам нужно было бы полностью оценить его...

что, естественно, подразумевает, что, действительно, любой IO действие, выполняемое от main тут оценка силы, которая должна быть очевидной, учитывая, что программы Haskell действительно делают что-то. Все, что нужно пройти через последовательность, определенную в main должен быть в нормальной форме и, следовательно, подлежит строгой оценке.

C. A. McCann получил это право в комментариях, хотя: единственное, что особенное о main это main определяется как специальный; сопоставление шаблонов в конструкторе достаточно для обеспечения последовательности, наложенной элемент IO монады. Только в этом отношении seq и сопоставление шаблонов являются фундаментальными.

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

хороший источник для модели Хаскелла "лени" можно найти здесь:http://en.wikibooks.org/wiki/Haskell/Laziness

в принципе, важно понять разницу между thunk и слабым заголовком нормальной формы WHNF.

Я так понимаю, что Хаскелл тянет вычисления в обратном направлении по сравнению с императивными языками. Это означает, что в отсутствие паттернов "seq" и bang в конечном итоге это будет какой-то побочный эффект, который заставляет оценку thunk, что может вызвать предварительные оценки в свою очередь (истинная лень).

поскольку это приведет к ужасной утечке пространства, компилятор затем выясняет, как и когда оценивать thunks заранее, чтобы сэкономить место. Программист может поддержать этот процесс путем предоставления строгость аннотации (en.wikibooks.org/wiki/Haskell/Strictness , www.haskell.org/haskellwiki/Performance/Strictness) для дальнейшего сокращения использования пространства в виде вложенных Громов.

Я не эксперт в операционной семантике haskell, поэтому я просто оставлю ссылку в качестве ресурса.

еще немного ресурсы:

http://www.haskell.org/haskellwiki/Performance/Laziness

http://www.haskell.org/haskellwiki/Haskell/Lazy_Evaluation

ленивый не означает ничего не делать. Всякий раз, когда ваш шаблон программы соответствует case выражение, оно оценивает что-то -- просто достаточно в любом случае. В противном случае он не может понять, какие RHS использовать. Не видите никаких выражений case в вашем коде? Не волнуйтесь, компилятор переводит ваш код в урезанную форму Haskell, где их трудно избежать.

для новичка основным эмпирическим правилом является let ленив, case менее ленивый.

Это не полный ответ, направленный на карму, а просто часть головоломки - в той мере, в какой это касается семантики, имейте в виду, что существует несколько стратегий оценки, которые обеспечивают то же самое семантика. Одним из хороших примеров здесь - и проект также говорит о том, как мы обычно думаем о семантике Haskell-был проект Eighter Haskell, который радикально изменил стратегии оценки в то время как сохранение та же семантика: http://csg.csail.mit.edu/pubs/haskell.html

компилятор Glasgow Haskell переводит ваш код на лямбда-исчисление-подобный язык под названием базовый. В этом языке, что-то будет оцениваться, всякий раз, когда вы шаблон соответствует ему по case-заявление. Таким образом, если функция вызывается, то будет вычислен внешний конструктор и только он (если нет принудительных полей). Все остальное консервируется в стук. (Преобразователи представлен let привязка).

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

Если вы попытаетесь оценить функцию вручную, вы можете в основном думать:

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

Comments

    Ничего не найдено.