2016-04-01 5 views
1

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

{-# 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 (нагруженного во время выполнения IO в main из файлов конфигурации и передается функции) и передавать его декодированные данные JSON того же типа.

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

+2

Возможно, стоит отметить, что в этом случае вам даже не нужны GADT; достаточно включить 'ExistentialQuantification', чтобы написать что-то вроде' data Encapsulated = forall a. Показать a => Инкапсулировать a' –

+0

Для многих (наиболее?) Целей и, почти наверняка, этого, 'Dynamic' является концептуальным излишеством и достаточно ограничение' Typeable'. 'data Box, где Box :: Typeable a => a -> Box'. Затем вы можете использовать функции из 'Data.Typeable', чтобы' Maybe' получить значение из поля. – dfeuer

+0

В GHC 8.2, 'Typeable', как ожидается, станет значительно более мощным, более чем обладая функциональностью, предлагаемой в настоящее время' Dynamic'. – dfeuer

ответ

4

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

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

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

на 0 Вы не имеете это ограничения - вы сообщаете компилятор: «смотреть это работает для каждого a я требую»

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

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


до степени это немного:

это

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

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

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

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

Encapsulate :: Contract a => a -> Encapsulated 

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

+0

yep, я тоже думал так же, и мне было любопытно, есть ли способ сохранить эту информацию с помощью GADT, одновременно создавая однородность типа карты (так что функция GADTs должна проверять тип). «Динамичный» кажется правильным путем. – Sal

4

@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 

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

+0

ну, я думаю, вы объяснили, что лучше ... спасибо – Carsten

+0

yep, очень легко сломать действительно. Я думал об ограничении типа 'a' с типом класса' Contract a'. Тем не менее, нам нужно проверить тип, чтобы убедиться, что он правильный. Итак, «Динамический» кажется правильным для меня для этого. – Sal