52700.fb2
Подсказка: За счёт семантики case-выражений нам не нужно специальных конструкций для того чтобы
реализовать seq в STG:
seq = FUN( a b ->
case a of
x -> b
)
При этом вызов функции seq будет встроен. Необходимо будет заменить в коде все вызовы seq на пра-
вую часть определения (без FUN). Также обратите внимание на то, что плюс не является примитивной
функцией:
plusInt = FUN( ma mb ->
case ma of
I# a -> case mb of
I# b -> case (primitivePlus a b) of
res -> I# res
)
В этой функции всплыла на поверхность одна тонкость. Если бы мы писали это выражение в Haskell,
то мы бы сразу вернули результат (I#(primitivePlus a b)), но мы пишем в STG и конструктор может
принять только атомарное выражение. Тогда мы могли бы подумать и сохранить его по старинке в
let-выражении:
-> let v = primitivePlus a b
in
I# v
Но это не правильное выражение в STG! Конструкция в правой части let-выражения должна быть объ-
ектом кучи, а у нас там простое выражение. Но было бы плохо добавить к нему THUNK, поскольку это
выражение содержит вызов примитивной функции на незапакованных значениях. Эта операция выпол-
няется очень быстро. Было бы плохо создавать для неё специальный объект на куче. Поэтому мы сразу
вычисляем это выражение в третьем case. Эта функция также будет встроенной, необходимо заменить
все вызовы на определение.
• Набейте руку в профилировании, пусть это станет привычкой. Вы долго писали большую программу и
теперь вы можете узнать много подробностей из её жизни, что происходит с ней во время вычисления
кода. Вернитесь к прошлой главе и попрофилируйте разные примеры. В конце главы мы рассматрива-
ли пример с поиском корней, там мы создавали большой список промежуточных результатов и в нём
искали решение. Я говорил, что такие алгоритмы очень эффективны при ленивой стратегии вычис-
лений, но так ли это? Будьте критичны, не верьте на слово, ведь теперь у вас есть инструменты для
проверки моих туманных гипотез.
• Откройте документацию к GHC. Пролистайте её. Проникнитесь уважением к разработчикам GHC. Най-
дите исходники GHC и почитайте их. Посмотрите на Haskell-код, написанный профессионалами. Вы-
берите функцию наугад и попытайтесь понять как она строит свой результат.
Упражнения | 179
• Откройте документацию вновь. Нас интересует глава Profiling. Найдите в разделе профилирование
кучи как выполняется retainer profiling. Это специальный тип профилирования направленный на по-
иск данных, которые удерживают в памяти другие данные (типичный сценарий для утечек памяти).
Разберитесь с этим типом профилирования (флаг hr).
• Постройте систему правил, которая выполняет слияние для списков List, определённых в примере для
прагмы RULES. Сравните показатели производительности с правилами и без (для этого скомпилируйте
дважды с флагом O и без) на тестовом выражении:
main = print $ sumL $
mapL (\x -> x - 1000) $ mapL (+100) $ mapL (*2) $ genL 0 1e6
Функция sumL находит сумму элементов в списке, функция genL генерирует список чисел с единичным