{-| Module : Data.Compat Description : Backwards Compatibility Schemes for Arbitrary Data Copyright : Travis Whitaker 2019 License : MIT Maintainer : pi.boy.travis@gmail.com Stability : Provisional Portability : Portable See <http://programmable.computer/compatible.hs> for a full exposition and worked examples. -} {-# LANGUAGE ConstraintKinds , FlexibleContexts , KindSignatures , RankNTypes , ScopedTypeVariables , TypeFamilies , TypeApplications #-} module Data.Compat where import Control.Applicative import Data.Constraint import Data.Proxy -- | A class for backwards-compatible data. class Compat a where -- | The predecessor for this type, i.e. the type for the data schema -- directly preceeding 'a'. type Pred a :: * -- | Any additional constraints required to yield data values. Typically -- this will be a class that provides a parser. type CompatConstraint a :: * -> Constraint -- | A type for wrapping migration results. It is most useful if this type -- has `Alternative` and `Monad` instances, enabling the use of -- `getCompatible`. `Maybe` is a good first choice. type CompatF a :: * -> * -- | How to migrate from a value of the preceeding schema to the current -- schema. migrate :: Pred a -> (CompatF a) a continue :: Proxy a -> Maybe (Dict ( Compat (Pred a) , (CompatConstraint a) (Pred a) , CompatConstraint a ~ CompatConstraint (Pred a) , CompatF a ~ CompatF (Pred a) ) ) -- | Recursively migrate a data value to the most recent schema, if possible. getCompatible :: forall a. ( Compat a , (CompatConstraint a) a , Alternative (CompatF a) , Monad (CompatF a) ) => (forall c. (Compat c, (CompatConstraint a) c) => (CompatF a) c) -> (CompatF a) a getCompatible f = let f' = case continue (Proxy :: Proxy a) of Nothing -> empty Just Dict -> getCompatible f >>= migrate in f <|> f'