content: {-# LANGUAGE AllowAmbiguousTypes   #-}
  {-# LANGUAGE ConstraintKinds       #-}
  {-# LANGUAGE DataKinds             #-}
  {-# LANGUAGE ExplicitNamespaces    #-}
  {-# LANGUAGE FlexibleContexts      #-}
  {-# LANGUAGE FlexibleInstances     #-}
  {-# LANGUAGE MultiParamTypeClasses #-}
  {-# LANGUAGE ScopedTypeVariables   #-}
  {-# LANGUAGE TypeApplications      #-}
  {-# LANGUAGE TypeFamilies          #-}
  {-# LANGUAGE TypeOperators         #-}
  {-# LANGUAGE UndecidableInstances  #-}
  module Versioning.Upgrade
    ( Adapt (..)
      -- * Upgrading
    , Upgrade
    , upgrade
      -- * Downgrading
    , Downgrade
    , downgrade
    )
  where
  
  import           Data.Kind                    (Type)
  
  import           Versioning.Base
  import           Versioning.Internal.Equality (type (==))
  
  -- | Adapt from a version to another
  class Adapt (v :: V) (w :: V) (a :: V -> Type) where
      adapt :: a v -> a w
  
  -- | Upgrade from a lower to a higher version by calling 'adapt' on all
  --   the intermediary steps.
  upgrade :: forall v w a. Upgrade v w a => a v -> a w
  upgrade = upgrade' @(v == w)
  
  -- | This constraint specifies that a value of type 'a' can be upgraded
  --   from version 'v' to version 'w'.
  type Upgrade v w a = Upgrade' (v == w) v w a
  
  -- | Upgrade from a lower to a higher version by calling 'adapt' on all
  --   the intermediary steps.
  --   You do not need to define any instance.
  --   They are derived automatically if all the intermediary
  --   'Adapt' instances are defined.
  class Upgrade' (eq :: Bool) (v :: V) (w :: V) (a :: V -> Type) where
      upgrade' :: a v -> a w
  
  instance (v ~ w) => Upgrade' 'True v w a where
      upgrade' x = x
  
  instance (Adapt v (VSucc v) a, Upgrade' (VSucc v == w) (VSucc v) w a)
    => Upgrade' 'False v w a where
      upgrade' x = upgrade' @(VSucc v == w) @(VSucc v) @w (adapt @v @(VSucc v) x)
  
  -- | Downgrade from a higher to a lower version by calling 'adapt' on all
  --   the intermediary steps.
  downgrade :: forall v w a. Downgrade v w a => a v -> a w
  downgrade = downgrade' @(v == w)
  
  -- | This constraint specifies that a value of type 'a' can be downgraded
  --   from version 'v' to version 'w'.
  type Downgrade v w a = Downgrade' (v == w) v w a
  
  -- | Downgrade from a higher to a lower version by calling 'adapt' on all
  --   the intermediary steps.
  --   You do not need to define any instance.
  --   They are derived automatically if all the intermediary
  --   'Adapt' instances are defined.
  class Downgrade' (eq :: Bool) (v :: V) (w :: V) (a :: V -> Type) where
      downgrade' :: a v -> a w
  
  instance (v ~ w) => Downgrade' 'True v w a where
      downgrade' x = x
  
  instance (Adapt v (VPred v) a, Downgrade' (VPred v == w) (VPred v) w a)
    => Downgrade' 'False v w a where
      downgrade' x = downgrade' @(VPred v == w) @(VPred v) @w (adapt @v @(VPred v) x)
  