Шаблон расширения Haskell, соответствующий типу

Кто-нибудь знает о расширении, которое позволило бы установить ограничения типа для сопоставления с образцом? Например:

{Language Extension}
IsOrderable::(Eq a)=>a->IO Bool
IsOrderable x = case x of
    o::(Ord a=>a) -> do
                         putStrLn "This equatable thing is also orderable."
                         return True
    _             -> do
                         putStrLn "This equatable thing is not orderable."
                         return False

Примечание: я спрашиваю об этом, чтобы я мог создавать монады, которые реагируют по-разному в зависимости от типа ввода. В частности, я пытался сделать монаду вероятности, но я хотел бы проверить, является ли тип ввода уравновешенным, чтобы я мог комбинировать дубликаты.

2 ответа

Решение

Есть способ, но это не красиво. Сначала мы сделаем функцию, которую мы хотим видеть dispatch для экземпляра класса type.

class IsOrd a where
    isOrd :: a -> IO Bool

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

instance Ord a => IsOrd a where
    isOrd = putStrLn "Orderable!" >> return True
instance IsOrd a where
    isOrd = putStrLn "Not orderable" >> return False

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

Теперь начинается сложная часть: она описана здесь и здесь. (Вторая ссылка, сборник Олега Киселева по классам типов, может содержать другие важные материалы, которые я не включаю в этот список, потому что пока не знаю об этом. В целом, это также отличный ресурс!). Суть его заключается в том, чтобы:

data HTrue
data HFalse
instance (Ord a) => IsOrd' HTrue a where
  isOrd' _ x = putStrLn "Orderable." >> return True
instance IsOrd' HFalse a where
  isOrd' _ x = putStrLn "Not orderable" >> return False

Где мы добавили дополнительный логический уровень на уровне типа к голове экземпляра, чтобы в вашем примере были caseS становятся отличными экземплярами головы. Довольно изящная идея!

Есть и другие детали, о которых вам нужно беспокоиться, некоторые из которых я не до конца понимаю, но все равно достаточно легко заставить их работать: вот код, который делает то, что вы хотите (вам придется все время обрабатывать экземпляры Eq это из IsOrd и далее, если вы хотите Eq ограничение тоже). И вот конечный результат:

*Scratch> :l scratch.hs
[1 of 1] Compiling Scratch          ( scratch.hs, interpreted )
Ok, modules loaded: Scratch.
*Scratch> isOrd (5::Int)
Orderable.
True
*Scratch> isOrd ('c')
Not orderable
False

Я не знаю такого расширения, и, кроме того, я подозреваю, что было бы очень трудно создать такое расширение с помощью текущего метода реализации классов GHC. В частности, GHC выполняет классы, передавая "доказательства" того, что данный тип реализует данный класс; поэтому, когда вы видите foo :: Eq a => a -> IO Bool это действительно значит foo принимает два аргумента: один является a а другой является доказательством того, что существует Eq a пример. Это доказательство не поддавалось.

Теперь подумайте о том, что происходит в предложенном вами коде: у вас в области есть значение типа a и доказательство того, что есть Eq a экземпляр, а затем вы задаете вопрос: есть ли в этой программе доказательства того, что есть Ord a пример? Более того, я не собираюсь говорить вам заранее, что a является. Пожалуйста, дайте мне правильный ответ на мой вопрос в любом случае, спасибо!

Вот самый канонический хитрый пример, который я могу придумать.

{-# LANGUAGE ExistentialQuantification #-}
data Existential = forall a. Eq a => Existential a

tricky :: Existential -> IO Bool
tricky (Existential x) = isOrderable x

Existential type оборачивает значение другого типа, помня только две вещи: значение, которое мы обернули, и некоторые доказательства того, что есть Eq введите для этого экземпляра. В частности, сам тип даже не запоминается! Так что теперь, когда приходит время бежать tricky, у нас есть значение некоторого типа (но мы забыли, какой именно) и доказательство того, что рассматриваемый тип является экземпляром Eq, Теперь, как мы собираемся угадать, является ли это также Ord? Мы не можем знать - мы даже не знаем, какой экземпляр искать! (Действительно, мы даже не можем реально проверить значение разумным способом, чтобы попытаться восстановить, какой это тип; единственная проверка, которую мы можем сделать, - это операции, предлагаемые Eq тип класс. Это не говорит нам много.) Мы ищем Ord Int пример? Или может быть Ord (Complex Double) пример? Без понятия.

С другой стороны, вы уже можете сделать что-то вроде этого:

{-# LANGUAGE DefaultSignatures #-}
class Eq a => QueryOrd a where
    isOrd :: a -> IO Bool
    default isOrd :: Ord a => a -> IO Bool
    isOrd _ = putStrLn "yup" >> return True

isOrdAltDef :: Eq a => a -> IO Bool
isOrdAltDef _ = putStrLn "nope" >> return False

instance Eq a => QueryOrd (Complex a) where isOrd = isOrdAltDef
instance         QueryOrd MyFancyType where isOrd = isOrdAltDef

... но вам понадобится один экземпляр QueryOrd для каждогоOrd экземпляр Eq,

Другие вопросы по тегам