This utility module provides DynT
, a dynamically-typed value that restricts the injected value to be a Functor
. It can be considered an improved Data.Dynamic.Dynamic
.
Below we will refer the Dynamic
provided by base
as Base.Dynamic
:
import qualified Data.Dynamic as Base
import Data.DynT
Let's say we have a list of Base.Dynamic
, where each value wrapped inside must be actually of type [a]
:
let dyns :: [Base.Dynamic]
dyns = [ Base.toDyn [True, False]
, Base.toDyn "abcdefghijklmnopqrstuvwxyz"
, Base.toDyn ([] :: [Int])
, Base.toDyn (["abc", "def"] ^? ix 0)
]
We can unwrap a Base.Dynamic
with Base.fromDynamic
or Base.fromDyn
:
λ> Base.fromDynamic (dyns !! 0) :: Maybe [Bool]
Just [True,False]
However, it is also completely possible to accidentally write:
λ> Base.fromDynamic (dyns !! 2) :: Maybe (Vector Int)
Nothing
Oops!
The problem is that the interface of Data.Dynamic
:
Base.toDyn :: Typeable a => a -> Base.Dynamic
Base.fromDynamic :: Typeable a => Base.Dynamic -> Maybe a
doesn't really restrict what a
could be. Ideally, we would like to a restricted version of Data.Dynamic
that captures the "static" part of the structure inside a dynamically-typed value, and the conversion functions should respect said structure.
By the way, did you notice that dyns !! 3
is actually a Maybe String
instead of a String
? The type checker didn't catch that either.
DynT
transforms a Functor
so that it maps over a dynamically-typed value.
data DynT :: (* -> *) -> * where
DynT :: f Any -> TypeRep -> DynT f
toDynT :: (Functor f, Typeable a) => f a -> DynT f
fromDynT :: (Functor f, Typeable a) => DynT f -> Maybe (f a)
dynTypeRep :: DynT f -> TypeRep
Now, the example above won't compile:
let dyns :: [DynT []]
dyns = [ toDynT [True, False]
, toDynT "abcdefghijklmnopqrstuvwxyz"
, toDynT ([] :: [Int])
, toDynT $ ["abc", "def"] ^? ix 0
]
Couldn't match type ‘Maybe’ with ‘[]’
Expected type: DynT []
Actual type: DynT Maybe
In the expression: toDynT $ ["abc", "def"] ^? ix 0
Removing the offending element, and we may now convert a DynT []
back with fromDynT
:
λ> fromDynT (dyns !! 0) :: Maybe [Bool]
Just [True,False]
Attempting to convert a DynT []
to something that is not a list results in a type error:
λ> fromDynT (dyns !! 2) :: Maybe (Vector Int)
Couldn't match type ‘[]’ with ‘Vector’
Expected type: [DynT Vector]
Actual type: [DynT []]
type Dyn = DynT Identity
toDyn :: Typeable a => a -> Dyn
fromDyn :: Typeable a => Dyn -> Maybe a
Dyn
is functionally equivalent to Data.Dynamic.Dynamic
. Note that it isn't a drop-in replacement however.
Base.fromDynamic = fromDyn
Base.fromDyn dyn def = fromMaybe def $ fromDyn dyn
runDynT :: Functor f => DynT f -> f Dyn
Convert a DynT f
to a plain Dyn
wrapped under f
.
wrapDyn :: (Alternative m, Monad m) => m Dyn -> TypeRep -> DynT m
wrapDynFail :: Monad m => m Dyn -> TypeRep -> DynT m
Convert an existing Dyn
under a m
to DynT m
. Unfortunately, it isn't possible to access the TypeRep
under m Dyn
without m
being Copointed
, so users must supply the correct TypeRep
manually. These two functions differ in how they report the error when the supplied TypeRep
does not match the actual TypeRep
inside Dyn
. wrapDyn
returns an empty
, and wrapDynFail
reports the mismatch with fail
.
An example could be passing a DynT f
to a function of type DynT f -> f Dyn
where you would like to wrap the result back to DynT f
again:
let f :: DynT f -> f Dyn
foo :: (Alternative f, Monad f) => DynT f -> DynT f
foo d = wrapDyn (f d) (dynTypeRep d)