| Copyright | © 2018 Mark Karpov |
|---|---|
| License | BSD 3 clause |
| Maintainer | Mark Karpov <markkarpov92@gmail.com> |
| Stability | experimental |
| Portability | portable |
| Safe Haskell | None |
| Language | Haskell2010 |
Data.Imprint
Description
This package provides a solution for serialization of arbitrary Haskell values, monomorphic functions, and closures without relying on remote tables or Template Haskell, with minimum boilderplate.
To use the package, be sure to enable the following language extensions:
The following form of import is recommended:
import Data.Imprint (Imprint, Col (..), Dict (..), (<:>)) import qualified Data.Imprint as I
To serialize a value, we must first create an Imprint of it. If the
value in question is an instance of the Binary type class, then the
binary function should be used:
intImprint :: Imprint 'Z Int intImprint = I.binary (static Dict) 4
The static keyword has to do with the concept of static pointers, see
the link above. We won't go into the details here, but it suffices to say
that we need to have an evidence of existence of Binary instance in
serializable form. static, being a keyword, not a function, has to be
used like this and cannot be put inside binary, because it creates a
pointer to a concrete thing that is passed to it. This little ceremony of
passing static Dict as the first argument of binary every time you
create an Imprint of a value that has a Binary instance is the only
boilderplate we have to put up with, though.
To create an Imprint of a function or indeed almost anything that has
no Binary instance, we use the static function:
funImprint :: Imprint 'Z (Int -> String -> String)
funImprint = I.static (static f)
where
f n str = str ++ show nThe f function we want to serialize may be defined anywhere. Note that
the resulting Imprint is opaque and has no sign of how it was created
(with binary or with static).
Finally, there is a way to apply an Imprint of a value to an Imprint
of a function with (:<:>)
closureImprint :: Imprint ('Z ':~> Int) (String -> String)
closureImprint = funImprint <:> intImprintNote how the applied arguments are collected in the phantom type (the
first argument of Imprint type constructor). There is no requirement to
apply all arguments, you may transmit a partially applied function all
right.
Now, to serialization. That is quite simple, because Imprint is an
instance of Binary and so it is perfectly serializable. On the
receiving site, you however must know the full type of Imprint,
including the collection of applied arguments in order to restore it.
If a more dynamic approach is desirable, we could adopt the
representation of closures used in distributed-process as a special
case with the following type of Imprint:
Imprint ('Z ':~> ByteString) (Process ())In that case we would need to serialize all the arguments beforehand and
put the deserializing code into the ByteString -> Process () function.
Finally, we give the guarantee that if you have a value of the type
, then you can have the Imprint as aa value back, see restore:
restore :: Imprint bs a -> a
Types
data Imprint (bs :: Col *) a Source #
is an image of Imprint bs aa that is isomorphic to a and
serializable.
This helper type is used to build the phantom type holding types of the
arguments applied to an Imprint of a function.
data Dict a :: Constraint -> * where #
Values of type capture a dictionary for a constraint of type Dict pp.
e.g.
Dict::Dict(EqInt)
captures a dictionary that proves we have an:
instance Eq 'Int
Pattern matching on the Dict constructor will bring this instance into scope.
Instances
| a :=> (Read (Dict a)) | |
| a :=> (Monoid (Dict a)) | |
| a :=> (Enum (Dict a)) | |
| a :=> (Bounded (Dict a)) | |
| () :=> (Eq (Dict a)) | |
| () :=> (Ord (Dict a)) | |
| () :=> (Show (Dict a)) | |
| a => Bounded (Dict a) | |
| a => Enum (Dict a) | |
| Eq (Dict a) | |
| (Typeable Constraint p, p) => Data (Dict p) | |
| Ord (Dict a) | |
| a => Read (Dict a) | |
| Show (Dict a) | |
| a => Monoid (Dict a) | |