Гетерогенные данные.Карта в Haskell

Можно ли сделать гетерогенный Data.Map в Haskell с GADT вместо Dynamic? Я попытался смоделировать разнородную коллекцию, как описано в этом ответе:

{-# LANGUAGE GADTs #-}

class Contract a where
   toString :: a -> String

data Encapsulated where
   Encapsulate :: Contract a => a -> Encapsulated

getTypedObject :: Encapsulated -> a
getTypedObject (Encapsulate x) = x

Идея заключалась в том, что Encapsulated можно было бы использовать для хранения различных объектов TypeClass a, а затем извлекать во время выполнения для определенного типа.

Я получаю сообщение об ошибке о том, что тип x не соответствует Contract a. Возможно, мне нужно указать какие-то ограничения класса, чтобы сообщить GHC, что тип x в Encapsulate x такой же, как a в Contract a?

T.hs:10:34:
    Couldn't match expected type ‘a’ with actual type ‘a1’
      ‘a1’ is a rigid type variable bound by
           a pattern with constructor
             Encapsulate :: forall a. Contract a => a -> Encapsulated,
           in an equation for ‘getTypedObject’
           at T.hs:10:17
      ‘a’ is a rigid type variable bound by
          the type signature for getTypedObject :: Encapsulated -> a
          at T.hs:9:19
    Relevant bindings include
      x :: a1 (bound at T.hs:10:29)
      getTypedObject :: Encapsulated -> a (bound at T.hs:10:1)
    In the expression: x
    In an equation for ‘getTypedObject’:
        getTypedObject (Encapsulate x) = x

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

Здесь подойдет библиотека Dynamic. Однако мне интересно узнать, существуют ли другие возможные подходы, такие как GADTs или datafamilies.


person Sal    schedule 01.04.2016    source источник
comment
Возможно, стоит отметить, что в этом случае вам даже не нужны GADT; достаточно включить ExistentialQuantification, чтобы написать что-то вроде data Encapsulated = forall a. Show a => Encapsulate a   -  person Bartek Banachewicz    schedule 01.04.2016
comment
Для многих (большинства?) целей, и почти наверняка для этой, Dynamic является концептуальным излишеством, а ограничения Typeable достаточно. data Box where Box :: Typeable a => a -> Box. Затем вы можете использовать функции от Data.Typeable до Maybe, чтобы получить значение из коробки.   -  person dfeuer    schedule 02.04.2016
comment
Ожидается, что в GHC 8.2 Typeable станет значительно более мощным, чем функциональность, предлагаемая в настоящее время Dynamic.   -  person dfeuer    schedule 02.04.2016


Ответы (2)


ваша проблема в том, что вы снова выталкиваете a (что не сработает) - вы можете использовать внутренний контракт следующим образом:

useEncapsulateContract :: Encapsulated -> String
useEncapsulateContract (Encapsulate x) = toString x

в основном компилятор говорит вам все, что вам нужно знать: внутри у вас есть forall a. Contract a (так что в основном ограничение a должно быть Contract)

На getTypedObject :: Encapsulated -> a у вас нет этого ограничения - вы говорите компилятору: "смотрите, это работает для каждого a, который я потребую"

Чтобы получить его там, вам нужно параметризовать Encapsulated в Encapsulated a, чего вы, очевидно, не хотите.

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


чтобы расширить это немного:

это

getTypedObject :: Contract a => Encapsulated -> a
getTypedObject (Encapsulate x) = x

также не будет работать, так как теперь у вас будет Contract a, но все же это могут быть два разных типа, которые просто разделяют этот класс.

И чтобы подсказать компилятору, что оба должны быть одинаковыми, вам придется снова параметризовать Encapsulate ....

прямо сейчас, сделав это:

Encapsulate :: Contract a => a -> Encapsulated

ты стираешь эту информацию

person Random Dev    schedule 01.04.2016
comment
да, я тоже думал так же, и мне было любопытно, есть ли способ сохранить эту информацию с помощью GADT, заставив тип карты быть однородным таким же образом (таким образом, функция GADT должна проверять тип). Dynamic кажется правильным путем. - person Sal; 01.04.2016

Ответ @Carsten, очевидно, правильный, но мои два цента помогли мне понять это раньше.

Когда вы пишете:

getTypedObject :: Encapsulated -> a

То, что вы "говорите", это:

getTypedObject – это функция, которая может принимать значение типа Encapsulated, и ее результат можно использовать всякий раз, когда требуется любой тип.

Вы явно не можете удовлетворить это, и компилятор не позволит вам попробовать. Вы можете использовать знание о значении внутри Encapsulated только для того, чтобы вывести что-то значимое на основе Contract. Другими словами, если бы Contract не было, у вас не было бы возможности сделать что-либо осмысленное с этим значением.

Эту концепцию можно кратко описать как стирание типов, а также она присутствует в других языках, один из которых мне известен C++. Следовательно, ценность заключается в стирании всей информации о типе, кроме вещей, которые вы хотите сохранить через контракт, который они удовлетворяют. Недостатком является то, что для возврата исходных типов требуется проверка во время выполнения.


В качестве бонуса вот как может работать динамический подход:

{-# LANGUAGE GADTs #-}

import Unsafe.Coerce

data Encapsulated where
   Encapsulate :: Show a => a -> Encapsulated

getTypedObject :: Encapsulated -> a
getTypedObject (Encapsulate x) = unsafeCoerce x

printString :: String -> IO ()
printString = print

x = Encapsulate "xyz"
y = getTypedObject x

main = printString y

Но очень легко понять, как это может сломаться, верно? :)

person Bartek Banachewicz    schedule 01.04.2016
comment
да, очень легко сломать на самом деле. Я думал об ограничении типа a типом Contract a, классом типов. Тем не менее, нам нужно проверить тип, чтобы убедиться, что он правильный. Итак, Dynamic кажется мне правильным подходом для этого. - person Sal; 01.04.2016