Получить список экземпляров в классе типов в Haskell

Есть ли способ программно получить список экземпляров класса типа?

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

Ответы

Ответ 1

Вы можете сгенерировать экземпляры в области для данного типа класса с использованием шаблона Haskell.

import Language.Haskell.TH

-- get a list of instances
getInstances :: Name -> Q [ClassInstance]
getInstances typ = do
  ClassI _ instances <- reify typ
  return instances

-- convert the list of instances into an Exp so they can be displayed in GHCi
showInstances :: Name -> Q Exp
showInstances typ = do
  ins <- getInstances typ
  return . LitE . stringL $ show ins

Запуск этого в GHCi:

*Main> $(showInstances ''Num)
"[ClassInstance {ci_dfun = GHC.Num.$fNumInteger, ci_tvs = [], ci_cxt = [], ci_cls = GHC.Num.Num, ci_tys = [ConT GHC.Integer.Type.Integer]},ClassInstance {ci_dfun = GHC.Num.$fNumInt, ci_tvs = [], ci_cxt = [], ci_cls = GHC.Num.Num, ci_tys = [ConT GHC.Types.Int]},ClassInstance {ci_dfun = GHC.Float.$fNumFloat, ci_tvs = [], ci_cxt = [], ci_cls = GHC.Num.Num, ci_tys = [ConT GHC.Types.Float]},ClassInstance {ci_dfun = GHC.Float.$fNumDouble, ci_tvs = [], ci_cxt = [], ci_cls = GHC.Num.Num, ci_tys = [ConT GHC.Types.Double]}]"

Другой полезный метод показывает все экземпляры в области видимости для данного типа класса с использованием GHCi.

Prelude> :info Num
class (Eq a, Show a) => Num a where
  (+) :: a -> a -> a
  (*) :: a -> a -> a
  (-) :: a -> a -> a
  negate :: a -> a
  abs :: a -> a
  signum :: a -> a
  fromInteger :: Integer -> a
    -- Defined in GHC.Num
instance Num Integer -- Defined in GHC.Num
instance Num Int -- Defined in GHC.Num
instance Num Float -- Defined in GHC.Float
instance Num Double -- Defined in GHC.Float

Изменить: важно знать, что компилятор знает только классы типов в области видимости в любом данном модуле (или в приглашении ghci и т.д.). Поэтому, если вы вызываете функцию showInstances TH без импорта, вы получите только экземпляры из Prelude. Если у вас есть другие модули в области видимости, например. Data.Word, тогда вы также увидите все эти экземпляры.

Ответ 3

Это приведет к множеству проблем, как только вы получите объявления экземпляров типа

instance Eq a => Eq [a] where
    [] == [] = True
    (x:xs) == (y:ys) = x == y && xs == ys
    _ == _ = False

и

instance (Eq a,Eq b) => Eq (a,b) where
    (a1,b1) == (a2,b2) = a1 == a2 && b1 == b2

вместе с одним конкретным экземпляром (например, instance Eq Bool).

Вы получите бесконечный список экземпляров для Eq - Bool, [Bool], [[Bool]], [[[Bool]]] и т.д., (Bool,Bool), ((Bool,Bool),Bool), (((Bool,Bool),Bool),Bool) и т.д., вдоль с различными комбинациями таких, как ([((Bool,[Bool]),Bool)],Bool) и т.д. Не ясно, как представить их в String; даже для списка TypeRep потребуется довольно умное перечисление.

Компилятор может (попытаться) определить, является ли тип экземпляром Eq для любого заданного типа, но он не читается во всех объявлениях экземпляра в области видимости, а затем просто начинает выводить все возможные экземпляры, поскольку никогда не закончится!

Важный вопрос, конечно, для чего вам это нужно?

Ответ 4

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

Класс типов будет переведен в тип данных. В качестве примера возьмем Eq:

class Eq a where
  (==),(/=) :: a -> a -> Bool

Класс typeclass будет переведен в словарь, содержащий все его функции:

data Eq a = Eq {
    (==) :: a -> a -> Bool,
    (/=) :: a -> a -> Bool
  }

Каждое ограничение типа typeclass затем преобразуется в дополнительный аргумент, содержащий словарь:

elem :: Eq a => a -> [a] -> Bool
elem _ [] = False
elem a (x:xs) | x == a    = True
              | otherwise = elem a xs

становится:

elem :: Eq a -> a -> [a] -> Bool
elem _  _ [] = False
elem eq a (x:xs) | (==) eq x a = True
                 | otherwise   = elem eq a xs

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

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

Ответ 5

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

Возможно, вы могли бы добавить в свою версию Template Haskell или другую магию, чтобы включить все исходные файлы в текст, доступный во время выполнения (c.f. program quine). Тогда ваша программа будет "grep self"...