Можно ли создать в Haskell функцию, работающую с набором типов?

Некоторое время я искал в Google, но не нашел ответа:

Скажем, у меня есть Tree ADT, который является полиморфным, тип базовой суммы полезной нагрузки и два типа суммы расширения:

--Base.hs
data Tree a = Node a [Tree a] | Empty
data BasePayload = BaseA String | BaseB Int

--Extention1.hs
data Extention1 = Ext1A String String | Ext1B Int

--Extention2.hs
data Extention2 = B | A

Я не могу изменить базовый тип и не знаю, используются ли во время компиляции и сколько типов расширения. Можно ли создать функции, которые работают так:

--Base.hs
type GeneralTree = Tree SomeBoxType

transform :: Tree (SomeBoxType (any | BasePayload)) -> Tree (SomeBoxType any)

--Extention1.hs
transform :: Tree (SomeBoxType (any | Extention1)) -> Tree (SomeBoxType any)

--Extention2.hs
transform :: Tree (SomeBoxType (any | Extention2)) -> Tree (SomeBoxType any)

Возможно ли что-то подобное? Когда я искал, я нашел GADT, который мне не нужен, а также DataKinds и TypeFamilies, которые я не понял на 100%, но не думаю, что здесь поможет. Это полиморфизм строк? Спасибо за вашу помощь.


person Jan van Brügge    schedule 28.07.2017    source источник
comment
Что такое SomeBoxType?   -  person leftaroundabout    schedule 28.07.2017
comment
Вот в чем вопрос, есть ли какой-то тип Box, который допускает такое поведение?   -  person Jan van Brügge    schedule 28.07.2017
comment
Что опять Tree (SomeBoxType (any | Extention2))? Что вы имеете в виду под any | Extention2 там? Это сигнатура функции, а не объявление данных, и здесь вы используете синтаксис объявления данных.   -  person Bartek Banachewicz    schedule 28.07.2017
comment
И как вы собираетесь использовать все это? Обычно это самая полезная информация.   -  person leftaroundabout    schedule 28.07.2017
comment
@BartekBanachewicz в основном Этот тип может содержать все, что угодно, плюс, необязательно, типы из Extention2 @leftaroundabout Приложение для преобразования дерева на основе подключаемого модуля, дерево представляет данные, которые находятся в полезной нагрузке. В зависимости от контекста мне нужны дополнительные данные, которые можно преобразовать в базовые данные, поэтому у меня есть плагин, который принимает расширенное определение и преобразует все дополнительные данные в базовые данные. Поскольку базовую программу не следует перекомпилировать для каждого параметра, я не могу изменять базовый ADT, в основном это компилятор.   -  person Jan van Brügge    schedule 28.07.2017
comment
Если он может содержать что-нибудь, то он также может содержать Extension2, не так ли? Зачем нужно это выделять? И к комментарию выше, почему вы не можете просто параметризовать свой тип дерева на этих дополнительных данных? Вам все равно нужно все вместе строить, не так ли?   -  person Bartek Banachewicz    schedule 28.07.2017
comment
Просто чтобы показать, что после преобразования его там не осталось, вы правы, это должно быть transform:: Tree (SomeBoxType any) -> Tree (SomeBoxType (any without Extention2)). Меня не очень волнуют типы, потому что это все равно происходит во время выполнения. Теоретически any -> any тоже было бы хорошо, я просто хочу сопоставить шаблону типов Extention2   -  person Jan van Brügge    schedule 28.07.2017
comment
Любая оболочка типов, хотя и возможна, не очень полезна, поскольку таким образом вы стираете всю информацию о типах. Любой вариант без расширений тоже не имеет смысла. Опять же, почему не Tree AnnotatedData -> Tree BaseData? Вы не можете сопоставить шаблон для любого значения, это, по сути, динамическая типизация, которую вы просите.   -  person Bartek Banachewicz    schedule 28.07.2017
comment
Поскольку два плагина можно использовать вместе, это будет Tree AnnotatedData1and2 -> Tree AnnotatedData2 и это со всеми возможными перестановками. Невозможно, потому что я хочу добавить плагины позже, не изменяя все остальные плагины.   -  person Jan van Brügge    schedule 28.07.2017
comment
Что ж, если все это можно объединить в BaseData, вам просто понадобится класс с class CombinesWithBase where combine :: a -> BaseData -> BaseData. Затем вы можете создать polywrapper для расширений: newtype Polyextension = forall a. CombinesWithBase a => Polyextension a, сделать его экземпляром CombineWithBase: instance CombineWithBase Polyextension where combine (Polyextension a) b = combine a b и, наконец, сохранить их в списке: data ComplexData = CD BaseData [Polyextension]. Предположительно, это антипаттерн (поэтому это не ответ), но он работал у меня раньше.   -  person Bartek Banachewicz    schedule 28.07.2017
comment
Вы можете прочитать типы данных по выбору. Я подозреваю, что это то, к чему вы стремитесь.   -  person dfeuer    schedule 28.07.2017
comment
@dfeuer Я думаю, что вы правы, но в настоящее время я понимаю только половину статьи и не могу перенести эту технику на свой пример дерева. Я должен перечитать это несколько раз   -  person Jan van Brügge    schedule 28.07.2017
comment
Вам также следует прочитать кое-что из того, что Олег Киселев написал о окончательном стиле без тегов для альтернативный подход.   -  person dfeuer    schedule 28.07.2017


Ответы (1)


Я подозреваю, что вам просто нужен Functor экземпляр. Таким образом:

instance Functor Tree where
    fmap f (Node x children) = Node (f x) (fmap (fmap f) children)
    fmap f Empty = Empty

Теперь fmap можно присвоить любой из этих типов:

fmap :: (Either any BasePayload -> any) -> Tree (Either any BasePayload) -> Tree any
fmap :: (Either any Extention1  -> any) -> Tree (Either any Extention1 ) -> Tree any
fmap :: (Either any Extention2  -> any) -> Tree (Either any Extention2 ) -> Tree any

Кстати, я считаю конструктор Empty очень подозрительным. В чем разница, например, между Node 0 [] и Node 0 [Empty]? Есть ли вообще Node 0 [Empty, Empty] разумная ценность? Рассмотрите возможность использования Data.Tree, который поставляется с вашим компилятор, не имеет этой проблемы и уже имеет Functor экземпляр.

person Daniel Wagner    schedule 28.07.2017