Являются ли неявные параметры трудными для встраивания в GHC?
Мне любопытно, какие возражения против неявных параметров обсуждались в статье Киселева и Шаня " Функциональная жемчужина: неявные конфигурации ".
Встроенный код (β-редуцирование) не имеет смысла при наличии неявных параметров.
В самом деле? Я ожидаю, что GHC должен встроиться в ту же область, что и передаваемый неявный параметр, нет?
Я полагаю, что понимаю их возражение, что:
Поведение термина может измениться, если его подпись добавлена, удалена или изменена.
Пользовательская документация GHC объясняет, что программисты должны заботиться о полиморфной рекурсии и ограничении мономорфизма. Это то, что они подразумевают под проблемой для встраивания?
Я предполагаю, что этот пример полиморфной рекурсии охватывает то, что они подразумевают под "обобщением неявных параметров"? Что-нибудь еще?
Это ReifiesStorable
Тип класса из Data.Reflection действительно разумное решение этих трудностей? Кажется, он десериализует всю неявную структуру данных каждый раз, когда к ней обращаются, что может иметь катастрофические последствия для производительности. Например, мы могли бы хотеть, чтобы нашей неявной информацией была таблица Кейли или таблица символов, которая занимает оперативную память и должна быть доступна во время миллионов алгебраических операций.
Может быть, есть какое-то лучшее решение, которое использует неявные параметры, или другой метод, который компилятор может легко оптимизировать за кулисами, в то же время гарантируя большее через систему типов с использованием потоков состояния или что-то еще?
1 ответ
Да, пример из руководства GHC показывает, как добавление сигнатуры типа может изменить семантику кода с неявными параметрами, и я считаю, что именно это они имеют в виду, ломая встраивание; подача заявления len_acc1
против применения len_acc2
создает один и тот же код, несмотря на разную семантику двух.
Что касается обобщения неявных параметров, это означает, что вы не можете написать функцию, которая может работать с несколькими неявными параметрами; нет механизма абстрагирования над ними, так как неявный параметр, который использует функция, фиксируется ее типом. С помощью отражения вы можете легко написать такую функцию, как doSomethingWith :: (Reifies s a, Num a) => Proxy s -> a
, который может работать с любым типом, который увеличивает числовое значение.
Что касается ReifiesStorable
вы смотрите на старую версию пакета отражений; последняя версия имеет очень эффективную реализацию, в которой reify
стоит столько же, сколько вызов функции.1 Обратите внимание, что даже при старой реализации вы обычно не используете ReifiesStorable
класс напрямую, но вместо Reifies
, который использует ReifiesStorable
чтобы овладеть StablePtr
Таким образом, копируется всего несколько байтов, а не весь объект. (Это то же самое, что и оригинальная реализация в документе.) Обе реализации определенно достаточно быстры для практического использования: старая, "медленная" реализация требует около 100 мс для повторного отображения и отображения 100000 значений, а новая реализация - до 10. Миз.
(Полное раскрытие: я работал над новой реализацией.)
1 Быстрая реализация зависит от деталей реализации Haskell. Более старая, более медленная реализация автоматически используется для реализаций Haskell, с которыми быстрая реализация еще не была протестирована; до сих пор было показано, что GHC и Hugs работают с быстрой реализацией. Вы можете запросить медленную реализацию с -fslow
, но вряд ли перестанет работать, если GHC существенно не пересмотрит реализацию классов типов. (Даже если это произойдет, вам нужно будет только перекомпилировать пакеты, которые используют отражение, чтобы заставить его работать снова.)