Add information about Data.Data to the README

This commit is contained in:
Anupam Jain 2023-07-06 21:46:42 +05:30
parent f3dd0c0fb1
commit f1a4681709

122
README.md
View File

@ -1,7 +1,7 @@
# Purescript-Typeable
Reified types for Purescript
This is an implementation of indexed typereps for Purescript, similar to the [corresponding implementation in Haskell](https://hackage.haskell.org/package/base-4.10.0.0/docs/Type-Reflection.html#t:TypeRep).
This is an implementation of indexed typereps for Purescript, similar to the [corresponding implementation in Haskell](https://hackage.haskell.org/package/base/docs/Type-Reflection.html#t:TypeRep).
[Slides for a talk about Purescript-Typeable](https://speakerdeck.com/ajnsit/purescript-typeable), presented at the Purescript semi-monthly meetup on 18 January 2021, are available.
@ -25,10 +25,10 @@ Instances are provided for common data types.
We can recover the unindexed representation by making it existential -
```purescript
data SomeTypeRep = SomeTypeRep (Exists TypeRep)
data SomeTypeRep
```
We can also test typereps for equality -
We can also test any two typereps for equality -
```purescript
eqTypeRep :: forall a b. TypeRep a -> TypeRep b -> Boolean
@ -40,6 +40,35 @@ We can compare two typeReps and extract a witness for type equality.
eqT :: forall a b. TypeRep a -> TypeRep b -> Maybe (a ~ b)
```
## Deriving `Typeable` for custom data types
It's extremely easy. You just need to create a mechanical `Taggable` class instance for your datatype. The instance will always use the provided `makeTag` function. There is no other possible way to create an instance.
For example -
```purescript
data Person = Person {name::String, age::Int}
instance Taggable Person where tag = makeTag unit
```
This is valid even for data types that take parameters. For example -
```purescript
data Optional a = Some a | None
instance Taggable Optional where tag = makeTag unit
```
**Don't worry about getting it wrong since the type system will prevent you from writing an invalid instance.**
> #### CAVEAT
> *Do not add any extra constraints to the instances*. For example don't do `Foo => Taggable Person`. This currently cannot be caught by the type checker, but will break typerep comparisons for your data type.
And that's it! You are done! Now your datatype will have a `Typeable` instance.
Note that you will have `Typeable` instances even for unsaturated types. For example, with the `tagTOptional` instance above, you have instances for `TypeRep (Optional a)` as well as for `TypeRep Optional`.
## Data.Dynamic
We can have dynamic values which holds a value `a` in a context `t` and forgets the type of `a`
@ -51,7 +80,6 @@ data Dynamic t
We can wrap a value into a dynamic
```purescript
-- Wrap a value into a dynamic
dynamic :: forall a t. Typeable a => t a -> Dynamic t
```
@ -61,31 +89,85 @@ We can recover the value from a dynamic if supply the type we expect to find in
unwrapDynamic :: forall a. TypeRep a -> Dynamic t -> Maybe a
```
## Deriving `Typeable` for custom data types
## Data.Data
It's extremely easy. You just need to create a mechanical `TagT` class instance for your datatype.
For example -
This is an implementation of the Data class Purescript, similar to the [corresponding implementation of Data in Haskell](https://hackage.haskell.org/package/base/docs/Data-Data.html). Check the documentation there for more information on the API. A brief overview is provided below.
```purescript
data Person = Person {name::String, age::Int}
instance tagTPerson :: TagT Person where tagT = proxyT
class Typeable a <= Data a where
dataDict :: DataDict a
```
This is valid even for data types that take parameters. For example -
Where `DataDict` is a manually reified dictionary because PureScript has trouble with constraints inside records.
Common instances are provided for `Data`. You can define `Data` instances for your own datatypes though it is slightly involved due to the dictionary reification.
When you cut through the dictionary reification noise, the definition of `Data` looks like the following -
```purescript
data Optional a = Some a | None
instance tagTOptional :: TagT Optional where tagT = proxyT
class Typeable a => Data a where
gmapT :: (forall b. Data b => b -> b) -> a -> a
```
**Don't worry about getting it wrong since the type system will prevent you from writing an invalid instance.**
Basically `Data` encodes a traversal through the structure of the data.
> #### CAVEAT
> *Do not add any extra constraints to the instances*. For example don't do `Foo => TagT Person`. This currently cannot be caught by the type checker, but will break typerep comparisons for your data type.
Let's say you have the following data structure -
And that's it! You are done! Now your datatype will have a `Typeable` instance.
```purescript
data Foo = Foo Bar Baz
```
You would define a `Data` instance for `Foo` like the following -
```purescript
instance Data Foo where
gmapT k (Foo a b) = Foo (k a) (k b)
```
i.e. You apply the supplied function to the immediate children of the top level structure.
Now because of the dictionary reification in PureScript, you can't directly write the instance that way. You need to do the following instead -
```purescript
instance Data Foo where
dataDict = DataDict \k z (Foo a b) -> z Foo `k` a `k` b
```
i.e. You are doing the same traversal, but start with the `z`, and intersperse all the immediate children of the data structure with `k`.
If your data structure has multiple branches, for example -
```purescript
data Foo = Bar Baz | Buzz Int
```
Simply handle the branches separately, for example -
```purescript
instance Data Foo where
dataDict = DataDict \k z foo -> case foo of
Bar b -> z Bar `k` b
Buzz i -> z Buzz `k` i
```
### Using Data.Data
Once you have a `Data` instance for your datatype, you can use `gmapT`, and functions that depend on `gmapT`, such as `everywhere`.
For example, to apply a function `f :: forall a. Typeable a => a -> a` to all leaves of your data structure, you can do `everywhere f`. Inside `f`, you can use the `Typeable` instance to decide when to change the data structure. For example, if you want the function to increment all integers, but leave all other data intact, use -
```purescript
f :: forall a. Typeable a => a -> a
f a =
-- Check if `a` is an int
case (typeRep :: _ a) `eqT` (typeRep :: _ Int) of
-- Return unmodified if `a` is not an int
Nothing -> a
Just witness -> do
-- If `a` is an int, we get access to bidirectional conversion functions
let aToI = coerce witness
let iToA = coerce (symm witness)
-- Increment and return
iToA (aToI a + 1)
```
Note that you will have typeable instances even for unsaturated types. For example, with the `tagTOptional` instance above, you have instances for `TypeRep (Optional a)` as well as for `TypeRep Optional`.