Невозможно правильно определить преобразование из универсального типа, определенного с помощью GADT.

Я определил универсальный тип данных, который может содержать что угодно (ну, с текущей реализацией не совсем ничего)!

Вот он (полный код):

{-#LANGUAGE NoMonomorphismRestriction#-}
{-#LANGUAGE GADTs#-}
{-#LANGUAGE StandaloneDeriving#-}

data AnyT where
    Any :: (Show a, Read a) => a -> AnyT

readAnyT :: (Read a, Show a) => (String -> a) -> String -> AnyT
readAnyT readFun str = Any $ readFun str

showAnyT :: AnyT -> String
showAnyT (Any thing) = show thing

deriving instance Show AnyT --Just for convinience!

a = [Any "Hahaha", Any 123]

И я могу играть с ним в консоли:

*Main> a
[Any "Hahaha",Any 123]
it :: [AnyT]
*Main> readAnyT (read::String->Float) "134"
Any 134.0
it :: AnyT
*Main> showAnyT $ Any 125
"125"
it :: String

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

toAnyT :: (Show a, Read a) => a -> AnyT -- Rather useless
toAnyT a = Any a

fromAny :: AnyT -> a
fromAny (Any thing) = thing

И есть проблема! определение fromAny из предыдущего кода неверно! И я не знаю, как сделать это правильно. Я получаю ошибку в GHCI:

2.hs:18:23:
    Could not deduce (a ~ a1)
    from the context (Show a1, Read a1)
      bound by a pattern with constructor
                 Any :: forall a. (Show a, Read a) => a -> AnyT,
               in an equation for `fromAny'
      at 2.hs:18:10-18
      `a' is a rigid type variable bound by
          the type signature for fromAny :: AnyT -> a at 2.hs:17:12
      `a1' is a rigid type variable bound by
           a pattern with constructor
             Any :: forall a. (Show a, Read a) => a -> AnyT,
           in an equation for `fromAny'
           at 2.hs:18:10
    In the expression: thing
    In an equation for `fromAny': fromAny (Any thing) = thing
Failed, modules loaded: none.

Я пробовал и другие способы, которые тоже выдавали ошибки.


У меня есть довольно плохое решение для этого: определение необходимых функций через showAnyT и read (замените предыдущие определения функций):

toAnyT :: (Show a, Read a) => a -> AnyT -- Rather useless
toAnyT a = Any a

fromAny :: Read a => AnyT -> a
fromAny thing = read (showAnyT thing)

Да, это работа. Я могу играть с ним:

*Main> fromAny $ Any 1352 ::Float
1352.0
it :: Float
*Main> fromAny $ Any 1352 ::Int
1352
it :: Int
*Main> fromAny $ Any "Haha" ::String
"Haha"
it :: String

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

Не могли бы вы помочь мне найти аккуратное и хорошее решение?


person Anon Imous    schedule 20.11.2013    source источник


Ответы (3)


Сначала отказ от ответственности: я не знаю всего контекста проблемы, которую вы пытаетесь решить, но первое впечатление, которое у меня возникает, заключается в том, что такое использование экзистенциалов является неправильным инструментом для работы, и вы, возможно, пытаетесь реализовать некоторые шаблон кода, который распространен в объектно-ориентированном языке, но плохо подходит для Haskell.

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

import Data.Typeable

data AnyT where
    Any :: (Show a, Read a, Typeable a) => a -> AnyT

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

readAnyT :: (Read a, Show a, Typeable a) => (String -> a) -> String -> AnyT
readAnyT readFun str = Any $ readFun str

showAnyT :: AnyT -> String
showAnyT (Any thing) = show thing

toAnyT :: (Show a, Read a, Typeable a) => a -> AnyT -- Rather useless
toAnyT a = Any a

fromAny может быть реализован как возврат Maybe a (поскольку вы не можете быть уверены, что получаемое вами значение имеет тот тип, который вы ожидаете).

fromAny :: Typeable a => AnyT -> Maybe a
fromAny (Any thing) = cast thing
person shang    schedule 20.11.2013
comment
Большое спасибо за ответ! Это не для решения конкретной проблемы, а для обучения и наличия большего количества инструментов в моей голове. - person Anon Imous; 20.11.2013

Вы используете GADT для создания экзистенциального типа данных. Тип a в конструкторе существовал, но восстановить его невозможно. Единственная доступная вам информация состоит в том, что у него есть Show и Read экземпляры. Точный тип забывается, потому что это то, что тип вашего конструктора инструктирует делать система типов. «Убедитесь, что у этого типа есть правильные экземпляры, а затем забудьте, что это такое».

Кстати, есть одна функция, которую вы пропустили:

readLike :: String -> AnyT -> AnyT
readLike s (Any a) = Any $ read s `asTypeOf` a

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

person Carl    schedule 20.11.2013
comment
Спасибо, но я не понимаю, для чего мне нужна пропущенная функция, которую вы здесь упомянули. Не могли бы вы написать мне это? - person Anon Imous; 20.11.2013
comment
Вам это не нужно. Это единственное, чего не хватало с точки зрения функциональности. Вы упаковывали экземпляр Read в конструктор, но ни для чего его не использовали. Это то, что вы можете сделать с экземпляром Read, поэтому его включение не будет пустой тратой времени. - person Carl; 20.11.2013

То, что у вас есть, называется экзистенциальным типом. Если вы перейдете по этой ссылке, то обнаружите, что в этом шаблоне единственный способ работать с «данными» внутри типа контейнера — использовать классы типов.

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

Подумайте об этом так: вы можете положить что угодно в коробку. Теперь, когда вы извлекаете что-то из этой коробки, у вас нет возможности указать, что вы получите из нее, поскольку вы можете положить в нее что угодно. Теперь, если вы говорите, что можете положить в эту коробку любую съедобную вещь, то вы уверены, что когда вы выберете что-то из этой коробки, она будет съедобной.

person Ankur    schedule 20.11.2013
comment
Не совсем верно, что единственный способ работать со значением экзистенциально-количественного типа — через класс типов. Вы также можете использовать несколько значений, квантифицированных по одному и тому же экзистенциальному типу вместе. Произнесите data Foo b = forall a. Foo a (a -> b). Этот пример скучен, потому что он просто изоморфен data Foo b = Foo b, но таким образом можно кодировать вещи, которые в противном случае раздражали бы. - person Carl; 20.11.2013