2015-07-12 4 views
4

У меня есть несколько типов данных, представляющих состояние приложения. В разных местах в типе данных у меня есть встроенные функции или монадические действия, например.Можно ли в общих чертах удалить типы функций из типа данных, чтобы позволить getiveJSON?

data Foo = Foo Int (ActionM String) 
data Bar = Bar Foo (Maybe Bar) (ActionM()) 

Мне нужно закодировать большинство из этих типов данных, как JSON, так что я могу отправить его в браузер для отображения. Использование deriveJSON (из пакета Aeson) не работает, потому что экземпляры ActionM не могут быть получены. Тем не менее, я фактически не хочу, чтобы эти биты были отправлены в любом случае. В настоящее время у меня есть подход, который работает, но в основном копирует полный набор типов данных и вручную удаляет вложенные поля ActionM.

Мне (думаю, я) нужна одна из двух вещей. Либо

  • способ сказать deriveJSON просто игнорировать поля, которые он не может понять, и, возможно, разобрать их обратно в undefined. Насколько я могу судить, этого не существует
  • способ автоматического создания параллельного набора типов данных с удалением этих полей. Поэтому я хочу, чтобы написать что-то вроде

applyMagic Bar

и получить назад

data Foo' = Foo' Int 
data Bar' = Bar' Foo' (Maybe Bar') 

Является ли все это возможно, и как бы я это сделать?

+0

Если 'ActionM' - это' data' или 'newtype', вы можете просто определить экземпляр« ToJSON »для него, который кодирует его как некоторое простое значение JSON (например, 0 или« ».) Затем вы можете автоматически вывести ToJSON для 'Foo'. Он все еще содержит дополнительную простоту, но это сэкономит вам много работы. – ErikR

ответ

1

Это простейшее решение, но не могли бы вы сделать что-то вроде

data Foo' = Foo' Int 

type Foo = (ActionM String, Foo') 

и просто получить второй элемент кортежа, когда вы хотите сериализовать?

Кортежи - это пример ComonadEnv, поэтому вы также можете использовать такие функции, как ask и extract.

Редактировать.Bar - более сложный случай, поскольку он является рекурсивным типом. Но это может быть обработано с помощью CofreeT комонады трансформатора:

import Data.Functor.Identity 
import Data.Bifunctor (second) 
import Control.Comonad -- from 'comonad' 
import Control.Comonad.Hoist.Class 
import Control.Comonad.Trans.Cofree -- from 'free' 

-- Orphan ComonadHoist instance that will likely be added in future 
-- versions of free 
instance Functor f => ComonadHoist (CofreeT f) where 
    cohoist g = CofreeT . fmap (second (cohoist g)) . g . runCofreeT 

type Bar = CofreeT Maybe ((,) (ActionM())) Foo 
type Bar' = Cofree Maybe Foo' 

applyMagic :: Bar -> Bar' 
applyMagic = cohoist (Identity . extract) . fmap extract 

CofreeT Maybe ((,) (ActionM())) Foo не является пустым списком Foo значений, которые были снабженными ActionM() значений.

Cofree Maybe Foo' не является пустым списком Foo' значений, без дополнительных аннотаций (Cofree Maybe Foo является синонимом CofreeT Maybe Identity Foo', где Identity работает как тривиальная комонада.).

Чтобы преобразовать друг в друга, applyMagic Первый использует fmap extract, чтобы преобразовать все Foo S в Foo' с, а затем использует cohoist из ComonadHoist, чтобы удалить «слой аннотаций» под CofreeT.

В целом, значения с «дополнительным контекстом» часто могут быть смоделированы с помощью comonads.

+0

как бы вы распространили это, чтобы также накрыть «Бар»? – ajp

+0

@ajp Я расширил свой ответ, чтобы накрыть «Бар». – danidiaz