method-0.3.0.0: src/Test/Method/Dynamic.hs
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
-- |
-- Module : Test.Method.Dynamic
-- Description:
-- License: BSD-3
-- Maintainer: autotaker@gmail.com
-- Stability: experimental
module Test.Method.Dynamic
( DynamicShow,
Dynamic,
castMethod,
dynArg,
DynamicLike (..),
FromDyn (..),
ToDyn (..),
Typeable,
)
where
import Control.Method (Method (Args, Base, Ret, curryMethod, uncurryMethod))
import Control.Method.Internal (type (:*))
import Data.Dynamic (Dynamic)
import qualified Data.Dynamic as D
import Data.Typeable (Proxy (Proxy), Typeable, typeRep)
import GHC.Generics
( Generic (Rep, from, to),
K1 (K1),
M1 (M1),
U1 (U1),
type (:*:) ((:*:)),
type (:+:) (L1, R1),
)
import Test.Method.Matcher (Matcher)
-- | Dynamic value whose content is showable.
-- Using this type instead of 'Dynamic' is recommended because it gives better error messages.
data DynamicShow = DynamicShow !Dynamic String
instance Show DynamicShow where
show (DynamicShow v s) = "<<" ++ s ++ " :: " ++ show (D.dynTypeRep v) ++ ">>"
-- | @FromDyn a b@ provides a function to convert type @a@ to type @b@,
-- where @b@ is a type whose dynamic type occurences are replaced by concrete types.
--
-- For example: @FromDyn (Int, Dynamic, Maybe Dynamic) (Int, Bool, Maybe String)@
class FromDyn a b where
-- | convert dynamic value to specified type. It thows runtime exception if
-- the dynamic value does not have specified type.
fromDyn :: a -> b
default fromDyn :: (Generic a, Generic b, FromDyn' (Rep a) (Rep b)) => a -> b
fromDyn = to . fromDyn' . from
-- | @ToDyn a b@ provides a function to convert type @b@ to type @a@, where
-- @b@ is a type whose dynamic type occurences are replaced by concrete types.
--
-- For example: @ToDyn (Int, Dynamic, Maybe Dynamic) (Int, Bool, Maybe String)@
class ToDyn a b where
-- | convert value of specified type to dynamic value
toDyn :: b -> a
default toDyn :: (Generic a, Generic b, ToDyn' (Rep a) (Rep b)) => b -> a
toDyn = to . toDyn' . from
class FromDyn' f g where
fromDyn' :: f a -> g a
class ToDyn' f g where
toDyn' :: g a -> f a
instance (FromDyn' f f', FromDyn' g g') => FromDyn' (f :+: g) (f' :+: g') where
{-# INLINE fromDyn' #-}
fromDyn' (L1 a) = L1 (fromDyn' a)
fromDyn' (R1 b) = R1 (fromDyn' b)
instance (FromDyn' f f', FromDyn' g g') => FromDyn' (f :*: g) (f' :*: g') where
{-# INLINE fromDyn' #-}
fromDyn' (a :*: b) = fromDyn' a :*: fromDyn' b
instance (FromDyn a a') => FromDyn' (K1 i a) (K1 i a') where
{-# INLINE fromDyn' #-}
fromDyn' (K1 a) = K1 (fromDyn a)
instance FromDyn' U1 U1 where
{-# INLINE fromDyn' #-}
fromDyn' _ = U1
instance (FromDyn' f f') => FromDyn' (M1 i t f) (M1 i t f') where
{-# INLINE fromDyn' #-}
fromDyn' (M1 a) = M1 (fromDyn' a)
instance Typeable a => FromDyn Dynamic a where
fromDyn = fromDynamic
instance (Typeable a, Show a) => FromDyn DynamicShow a where
fromDyn = fromDynamic
instance {-# INCOHERENT #-} FromDyn a a where
{-# INLINE fromDyn #-}
fromDyn = id
instance (ToDyn' f f', ToDyn' g g') => ToDyn' (f :+: g) (f' :+: g') where
{-# INLINE toDyn' #-}
toDyn' (L1 a) = L1 (toDyn' a)
toDyn' (R1 b) = R1 (toDyn' b)
instance (ToDyn' f f', ToDyn' g g') => ToDyn' (f :*: g) (f' :*: g') where
{-# INLINE toDyn' #-}
toDyn' (a :*: b) = toDyn' a :*: toDyn' b
instance (ToDyn a a') => ToDyn' (K1 i a) (K1 i a') where
{-# INLINE toDyn' #-}
toDyn' (K1 a) = K1 (toDyn a)
instance ToDyn' U1 U1 where
{-# INLINE toDyn' #-}
toDyn' _ = U1
instance (ToDyn' f f') => ToDyn' (M1 i t f) (M1 i t f') where
{-# INLINE toDyn' #-}
toDyn' (M1 a) = M1 (toDyn' a)
instance Typeable a => ToDyn Dynamic a where
toDyn = D.toDyn
instance (Typeable a, Show a) => ToDyn DynamicShow a where
toDyn = toDynamicShow
instance {-# INCOHERENT #-} ToDyn a a where
{-# INLINE toDyn #-}
toDyn = id
instance (FromDyn a a', ToDyn b b') => ToDyn (a -> b) (a' -> b') where
toDyn f a = toDyn $ f (fromDyn a)
instance (ToDyn a a', FromDyn b b') => FromDyn (a -> b) (a' -> b') where
fromDyn f a = fromDyn $ f (toDyn a)
instance (FromDyn a b, FromDyn c d) => FromDyn (a :* c) (b :* d)
instance (ToDyn a b, ToDyn c d) => ToDyn (a :* c) (b :* d)
instance (FromDyn a b) => FromDyn [a] [b]
instance (ToDyn a b) => ToDyn [a] [b]
instance (FromDyn a b) => FromDyn (Maybe a) (Maybe b)
instance (ToDyn a b) => ToDyn (Maybe a) (Maybe b)
instance (FromDyn a a', FromDyn b b') => FromDyn (Either a b) (Either a' b')
instance (ToDyn a a', ToDyn b b') => ToDyn (Either a b) (Either a' b')
instance (FromDyn a a', FromDyn b b') => FromDyn (a, b) (a', b')
instance (ToDyn a a', ToDyn b b') => ToDyn (a, b) (a', b')
instance (FromDyn a a', FromDyn b b', FromDyn c c') => FromDyn (a, b, c) (a', b', c')
instance (ToDyn a a', ToDyn b b', ToDyn c c') => ToDyn (a, b, c) (a', b', c')
instance (FromDyn a a', FromDyn b b', FromDyn c c', FromDyn d d') => FromDyn (a, b, c, d) (a', b', c', d')
instance (ToDyn a a', ToDyn b b', ToDyn c c', ToDyn d d') => ToDyn (a, b, c, d) (a', b', c', d')
instance (FromDyn a a', FromDyn b b', FromDyn c c', FromDyn d d', FromDyn e e') => FromDyn (a, b, c, d, e) (a', b', c', d', e')
instance (ToDyn a a', ToDyn b b', ToDyn c c', ToDyn d d', ToDyn e e') => ToDyn (a, b, c, d, e) (a', b', c', d', e')
instance (FromDyn a a', FromDyn b b', FromDyn c c', FromDyn d d', FromDyn e e', FromDyn f f') => FromDyn (a, b, c, d, e, f) (a', b', c', d', e', f')
instance (ToDyn a a', ToDyn b b', ToDyn c c', ToDyn d d', ToDyn e e', ToDyn f f') => ToDyn (a, b, c, d, e, f) (a', b', c', d', e', f')
instance (FromDyn a a', FromDyn b b', FromDyn c c', FromDyn d d', FromDyn e e', FromDyn f f', FromDyn g g') => FromDyn (a, b, c, d, e, f, g) (a', b', c', d', e', f', g')
instance (ToDyn a a', ToDyn b b', ToDyn c c', ToDyn d d', ToDyn e e', ToDyn f f', ToDyn g g') => ToDyn (a, b, c, d, e, f, g) (a', b', c', d', e', f', g')
-- | convert a dynamically-typed method to a polymorphic method.
--
-- @
-- fDyn :: String -> DynamicShow -> Dynamic -> IO [DynamicShow]
-- fDyn = ...
-- fPoly :: (Typeable a, Show a, Typeable b, Typeable c, Show c) => String -> a -> b -> IO [c]
-- fPoly = castMethod fDyn
-- @
castMethod ::
( ToDyn (Args method) (Args method'),
FromDyn (Ret method) (Ret method'),
Method method,
Method method',
Base method ~ Base method'
) =>
method ->
method'
castMethod method = curryMethod $ \args ->
fromDyn <$> uncurryMethod method (toDyn args)
fromDynamic :: forall a d. (Typeable a, DynamicLike d, Show d) => d -> a
fromDynamic v =
D.fromDyn (asDyn v) $
error $ "cannot cast " ++ show v ++ " to " ++ show (typeRep (Proxy :: Proxy a))
toDynamicShow :: (Typeable a, Show a) => a -> DynamicShow
toDynamicShow a = DynamicShow (D.toDyn a) (show a)
-- | Generalizes 'Dynamic' and 'DynamicShow'
class DynamicLike a where
asDyn :: a -> Dynamic
instance DynamicLike Dynamic where
asDyn = id
instance DynamicLike DynamicShow where
asDyn (DynamicShow a _) = a
-- | Convert given matcher to dynamic matcher. The dynamic matcher
-- matches a dynamic value only if the value has the type of given matcher.
dynArg :: (Typeable a, DynamicLike b) => Matcher a -> Matcher b
dynArg matcher dv =
maybe False matcher $ D.fromDynamic $ asDyn dv