2013-11-20 4 views
0

Я определил универсальный тип данных, который может содержать что угодно (ну, с текущей реализацией не совсем ничего)!Невозможно правильно определить преобразование из универсального типа, заданного с помощью 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 и чтения (заменить предыдущие определения функций):

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 

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

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

ответ

1

Первый отказ: я не знаю весь контекст проблемы, которую вы пытаетесь решить, но первое впечатление, которое я получаю, это то, что это k ind использования экзистенций является неправильным инструментом для работы, и вы можете попытаться реализовать некоторый шаблон кода, который является общим в объектно-ориентированном языке, но плохо подходит для 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 
+0

Большое спасибо за ваш ответ! Речь идет не о решении конкретной проблемы, а о том, чтобы учиться и иметь больше инструментов в моем сознании –

1

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

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

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

+0

Не совсем верно, что единственный способ работать со значением типа с экзистенциальным количественным выражением - через класс типа. Вы также можете использовать несколько значений, количественно определенных по одному и тому же экзистенциальному типу вместе. Скажите «данные Foo b = forall a. Foo a (a -> b) '. Этот пример скучен, потому что он просто изоморфен 'data Foo b = Foo b', но можно кодировать вещи так, что в противном случае было бы неприятно. – Carl

1

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

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

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

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

+0

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

+0

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

 Смежные вопросы

  • Нет связанных вопросов^_^