Можно ли принудительно ввести тип в класс в Haskell?

В качестве примера, скажем, я хочу реализовать функцию, которая суммирует список Num s. На полпути через его кодирование я хочу отладить его с помощью Debug.Trace:

module T where
import Debug.Trace

dosum :: (Num a) => [a] -> a
dosum xs = dosum' 0 xs
    where
        dosum' n [] = n
        dosum' n (x:xs) = trace (show n) $ dosum' (n+x) xs

Проблема в том, что это не скомпилируется:

Could not deduce (Show a) arising from a use of dosum'
from the context (Num a)

Я могу добавить (Show a) в dosum, а затем удалить его, когда закончу отладку (в реальной жизни я хочу иметь тип, который не обязательно находится в Show, но я буду отлаживать с целыми числами), Это может быть громоздким, если есть несколько функций, и я продолжаю добавлять удаление операторов Show a.

Я хочу иметь функцию unsafeShow

unsafeShow :: a -> String

который работает, если a равен Show a и может быть поврежден, если это не так. Возможно ли это?

Ответ 1

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

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

Но если вы случайно назовете код с unsafeCoerce чем-то иным, чем Integer, сбой, повреждение памяти, все может произойти - вы просто полностью выбросили свою защитную сетку.

В общем, только "безопасные" использования unsafeCoerce относятся к типам, которые вы уже знаете, равны, но typechecker не (или некоторые другие специализированные прецеденты, см. документы). Даже тогда это будет сильно озадачено тем, кто читает ваш код, если ваши комментарии не объяснят, почему это единственный вариант.

Ответ 2

Нет, это невозможно. Это нарушит параметризм ; полиморфной функции не разрешено вести себя по-разному в зависимости от конкретного типа, с которым он вызван. Это также нарушит предположение открытого мира, поскольку добавление экземпляра Show для типа изменит поведение вашей программы.

Это может быть полезная помощь для отладки, поскольку явно явно небезопасно, но GHC не поддерживает такую ​​функцию, и я не думаю, что ее текущая реализация позволит легко добавить ее.

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

class (Num a) => Number a
instance (Num a) => Number a

который вы можете использовать вместо Num в подписях, меняя объявление на (Num a, Show a) при отладке. (Лучше выбрать более значимое имя, чем Number, однако!)

Ответ 3

Это невозможно. (примечание 1)


1: Исключением является то, что вы можете сбросить кучную структуру кучи GHC с помощью функции a -> String. Вы можете, например, всегда превращайте значение в шестнадцатеричное значение указателя, vacuum. Это вряд ли будет тем, что вы хотите. Эта функция аналогична той, что используется отладчиком GHCi для отображения произвольных значений кучи.

Ответ 4

Невозможно реализовать вашу функцию unsafeShow в чистом Haskell. GHC может предоставить один, но в настоящее время он этого не делает.

Однако вы можете проверить отладчик GHCi. Это позволяет печатать материал, который не имеет экземпляра Show. (Кроме того, он позволяет избежать оценки того, что иначе не было бы оценено, что может быть полезно.)

Ответ 5

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

-- original function, should add NOINLINE to make sure your rule gets a chance to fire.
{-# NOINLINE dosum #-}
dosum :: (Num a) => [a] -> a

-- a version with added debugging
dosumShow :: (Num a, Show a) => [a] -> a

{-# RULES "sub/dosum" forall x. dosum x = dosumShow x #-}

Ответ 6

Это версия GHC 7.4.1? Из него примечания к выпуску:

Класс Num больше не имеет Eq или Show суперклассов. Таким образом, ряд других классов и функций получил явные ограничения Eq или Show, вместо того, чтобы полагаться на ограничение Num для их предоставления.

Вы можете сделать код, который работает как с Haskell98/Haskell2010, так и с GHC:

  • Всякий раз, когда вы делаете экземпляр Num типа, также делаете экземпляры Show и Eq и

  • Всякий раз, когда вы предоставляете функцию, экземпляр или класс ограничение Num t, также давайте ему Show t и Eq t ограничения.

Ваш код хорошо работает в предыдущих версиях GHC (я пробую в 7.0.4).