-- | Dynamic values with support for rank-1 polymorphic types.
--
-- [Examples of fromDynamic]
--
-- These examples correspond to the 'Data.Rank1Typeable.isInstanceOf' examples
-- in "Data.Rank1Typeable".
--
-- > > do f <- fromDynamic (toDynamic (even :: Int -> Bool)) ; return $ (f :: Int -> Int) 0
-- > Left "Cannot unify Int and Bool"
-- >
-- > > do f <- fromDynamic (toDynamic (const 1 :: ANY -> Int)) ; return $ (f :: Int -> Int) 0
-- > Right 1
-- >
-- > > do f <- fromDynamic (toDynamic (unsafeCoerce :: ANY1 -> ANY2)) ; return $ (f :: Int -> Int) 0
-- > Right 0
-- >
-- > > do f <- fromDynamic (toDynamic (id :: ANY -> ANY)) ; return $ (f :: Int -> Bool) 0
-- > Left "Cannot unify Bool and Int"
-- >
-- > > do f <- fromDynamic (toDynamic (undefined :: ANY)) ; return $ (f :: Int -> Int) 0
-- > Right *** Exception: Prelude.undefined
-- >
-- > > do f <- fromDynamic (toDynamic (id :: ANY -> ANY)) ; return $ (f :: Int)
-- > Left "Cannot unify Int and ->"
--
-- [Examples of dynApply]
--
-- These examples correspond to the 'Data.Rank1Typeable.funResultTy' examples
-- in "Data.Rank1Typeable".
--
-- > > do app <- toDynamic (id :: ANY -> ANY) `dynApply` toDynamic True ; f <- fromDynamic app ; return $ (f :: Bool)
-- > Right True
-- >
-- > > do app <- toDynamic (const :: ANY -> ANY1 -> ANY) `dynApply` toDynamic True ; f <- fromDynamic app ; return $ (f :: Int -> Bool) 0
-- > Right True
-- >
-- > > do app <- toDynamic (($ True) :: (Bool -> ANY) -> ANY) `dynApply` toDynamic (id :: ANY -> ANY) ; f <- fromDynamic app ; return (f :: Bool)
-- > Right True
-- >
-- > > app <- toDynamic (const :: ANY -> ANY1 -> ANY) `dynApply` toDynamic (id :: ANY -> ANY) ; f <- fromDynamic app ; return $ (f :: Int -> Bool -> Bool) 0 True
-- > Right True
-- >
-- > > do app <- toDynamic ((\f -> f . f) :: (ANY -> ANY) -> ANY -> ANY) `dynApply` toDynamic (even :: Int -> Bool) ; f <- fromDynamic app ; return (f :: ())
-- > Left "Cannot unify Int and Bool"
--
-- [Using toDynamic]
--
-- When using polymorphic values you need to give an explicit type annotation:
--
-- > > toDynamic id
-- >
-- > <interactive>:46:1:
-- >     Ambiguous type variable `a0' in the constraint:
-- >       (Typeable a0) arising from a use of `toDynamic'
-- >     Probable fix: add a type signature that fixes these type variable(s)
-- >     In the expression: toDynamic id
-- >     In an equation for `it': it = toDynamic id
--
-- versus
--
-- > > toDynamic (id :: ANY -> ANY)
-- > <<ANY -> ANY>>
--
-- Note that these type annotation are checked by ghc like any other:
--
-- > > toDynamic (id :: ANY -> ANY1)
-- >
-- > <interactive>:45:12:
-- >     Couldn't match expected type `V1' with actual type `V0'
-- >     Expected type: ANY -> ANY1
-- >       Actual type: ANY -> ANY
-- >     In the first argument of `toDynamic', namely `(id :: ANY -> ANY1)'
-- >     In the expression: toDynamic (id :: ANY -> ANY1)
module Data.Rank1Dynamic
  ( Dynamic
  , toDynamic
  , fromDynamic
  , TypeError
  , dynTypeRep
  , dynApply
  , unsafeToDynamic
  ) where

import qualified GHC.Exts as GHC (Any)
import Data.Rank1Typeable
  ( Typeable
  , TypeRep
  , typeOf
  , isInstanceOf
  , TypeError
  , funResultTy
  )
import Unsafe.Coerce (unsafeCoerce)

-- | Encapsulate an object and its type
data Dynamic = Dynamic TypeRep GHC.Any

instance Show Dynamic where
  showsPrec _ (Dynamic t _) = showString "<<" . shows t . showString ">>"

-- | Introduce a dynamic value
toDynamic :: Typeable a => a -> Dynamic
toDynamic x = Dynamic (typeOf x) (unsafeCoerce x)

-- | Construct a dynamic value with a user-supplied type rep
--
-- This function is unsafe because we have no way of verifying that the
-- provided type representation matches the value.
--
-- Since 0.3.2.0.
unsafeToDynamic :: TypeRep -> a -> Dynamic
unsafeToDynamic typ x = Dynamic typ (unsafeCoerce x)

-- | Eliminate a dynamic value
fromDynamic :: Typeable a => Dynamic -> Either TypeError a
fromDynamic (Dynamic t v) =
  case unsafeCoerce v of
    r -> case typeOf r `isInstanceOf` t of
      Left err -> Left err
      Right () -> Right r

-- | Apply one dynamic value to another
dynApply :: Dynamic -> Dynamic -> Either TypeError Dynamic
dynApply (Dynamic t1 f) (Dynamic t2 x) = do
  t3 <- funResultTy t1 t2
  return $ Dynamic t3 (unsafeCoerce f x)

-- | The type representation of a dynamic value
dynTypeRep :: Dynamic -> TypeRep
dynTypeRep (Dynamic t _) = t