Получение типов аргументов конструктора с использованием Data и Typeable - PullRequest
3 голосов
/ 16 апреля 2019

Я играю с Haskell Data и Typeable , и я застрял, пытаясь получить аргументы функции без переменной типа, доступной в контексте.

Позвольте мне уточнить, что я имею в виду.Пока у меня есть переменная типа a в количественном выражении, как показано ниже, я могу использовать fromConstr и получить список DataType или TypeRep, как я хочу:

constrArgs :: forall a. Data a => Constr -> [DataType]
constrArgs c = gmapQ f (fromConstr c :: a)
  where f :: forall d. Data d => d -> DataType
        f _ = dataTypeOf @d undefined

(я понимаю,undefined и fromConstr не являются итоговыми, но лень спасает нас здесь.)

Однако, если я попытаюсь избежать количественного определения a, я больше не смогу делать приписывание типа для результата fromConstr.Интересно, есть ли способ написать функцию со следующей сигнатурой типа:

constrArgs' :: Constr -> [DataType]

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

allConstrArgs :: forall a. Data a => [[DataType]]

Проблема в том, что я не могу применить allConstrArgs к самим результатам, потому чтонет способа перейти от DataType к значению уровня типа.

Итак, чтобы исправить это, можем ли мы написать функцию следующего типа?

allConstrsArgs' :: DataType -> [[DataType]]

Я огляделся в базовой библиотеке, но не вижу, как этого достичь.

1 Ответ

5 голосов
/ 16 апреля 2019

Вы не можете получить список типов аргументов из Constr, потому что в нем просто недостаточно данных: это набор строк, ничего более.

Тем не менее, есть способ достичь вашей большей цели: вам просто нужно иметь при себе словарь Data, и какой лучший способ сделать это, чем экзистенциальный тип!

data D = forall a. Data a => D a

allConstrArgs :: D -> [[D]]
allConstrArgs d = constrArgs d <$> allConstrs d

constrArgs :: D -> Constr -> [D]
constrArgs (D a) c = gmapQ D $ mkConstr a c
    where
        mkConstr :: forall a. Data a => a -> Constr -> a
        mkConstr _ = fromConstr

allConstrs :: D -> [Constr]
allConstrs (D a) = case dataTypeRep $ dataTypeOf a of
    AlgRep constrs -> constrs
    _ -> []

mkD :: forall a. Data a => D
mkD = D (undefined :: a)

Здесь тип D служит исключительно для переноса словаря Data - фактическое значение a всегда будет undefined и никогда не будет оцениваться, так что это нормально. Таким образом, значение D служит представлением типа на уровне значений, так что после деструктуризации вы получаете экземпляр Data для этого типа в области видимости.

Функция constrArgs принимает представление типа D и конструктор Constr и возвращает список параметров этого конструктора, каждый из которых также представлен как D - так что теперь вы можете передать его вывод обратно в его вход! Это делается с помощью gmapQ, чей тип первого аргумента идеально соответствует конструктору D.

mkD - это просто служебная функция, предназначенная для того, чтобы скрыть неприятность undefined и использоваться с TypeApplications, например, mkD @Int.

А вот использование:

data X = X0 Int | X1 String deriving (Typeable, Data)
data Y = Y0 String | Y1 Bool | Y2 Char deriving (Typeable, Data)
data Z = ZX X | ZY Y deriving (Typeable, Data)

typName :: D -> String
typName (D a) = dataTypeName $ dataTypeOf a

main = do
    -- Will print [["Prelude.Int"],["Prelude.[]"]]
    print $ map typName <$> allConstrArgs (mkD @X)

    -- Will print [["Prelude.[]"],["Bool"],["Prelude.Char"]]
    print $ map typName <$> allConstrArgs (mkD @Y)

    -- Will print [["X"],["Y"]]
    print $ map typName <$> allConstrArgs (mkD @Z)

Обратите внимание, что для работы вам понадобятся следующие расширения: ScopedTypeVariables, DeriveDataTypeable, GADTs, AllowAmbiguousTypes, TypeApplications

...