purescript-heterogeneous
purescript-heterogeneous copied to clipboard
Support mapping/folding over generic representations
I think this is a missing piece in this library.
Here's a fork that implements mapping over generic representations of ADT's. It adds a test, too.
What do you think about it? If you agree I could go ahead and add the implementation for Folding and then create a PR.
I think the reason I never added it is I didn’t have a good use case to showcase it. Records are pretty easy and nice, because if you map over a record you get a new one that’s just as usable as the old. If you map over a generic spine, you probably don’t have anything particularly usable since it likely can’t be converted back into the original data type, or even to a new data type. Likewise for folding, you really want to fold based on some use case, like “over all the constructors” and “over all the fields”, not the whole spine on its own. Basically, if I were to add it, I would want to be able to point to some existing Generic use cases and show how it’s easier with this library. This is easily apparent with Records/rows. Do you have specific use cases where these instances would help you write better Generic code?
Below you find a commented sample of what I was doing some time ago when I wrote the issue. It compiles as it is with the fork that I referenced above. The main point is that after mapping over a generic representation of an ADT I only JSON encode the manipulated representation. So I don't turn it back into a data type, which would not work of course.
module SampleGeneric where
import Prelude
import Data.Argonaut.Core (Json, stringify)
import Data.Argonaut.Encode (class EncodeJson, encodeJson)
import Data.Argonaut.Encode.Encoders (encodeInt)
import Data.Argonaut.Encode.Generic (class EncodeRep, encodeRep, genericEncodeJson)
import Data.Generic.Rep (class Generic, from)
import Heterogeneous.Mapping (class Mapping, hmap)
-- Say we have a given type
data Foo
= Baz Int
| Bar String Boolean
derive instance Generic Foo _
-- And now we like to encode it as Json. One way to do is this:
instance EncodeJson Foo where
encodeJson = genericEncodeJson
-- however, what if we'd like to use a different encoder for the Boolean type:
newtype SpecialBoolean = SpecialBoolean Boolean
instance EncodeJson SpecialBoolean where
encodeJson (SpecialBoolean x) = encodeInt if x then 0 else 1
-- But let's assume it's not possible or wanted to change the Foo type to use SpecialBoolean instead of Boolean
-- We define the following Mapping:
data MyMap = MyMap
instance Mapping MyMap Boolean SpecialBoolean where
mapping _ = SpecialBoolean
else instance Mapping MyMap a a where
mapping _ = identity
-- Then we make sure that any representation can be Json encoded:
data RepWrap rep = RepWrap rep
instance (EncodeRep rep) => EncodeJson (RepWrap rep) where
encodeJson (RepWrap rep) = encodeRep rep
-- And finally we can write the following:
encodeFooSpecial :: Foo -> Json
encodeFooSpecial foo =
from foo
# hmap MyMap
# RepWrap
# encodeJson
--- And use it like this:
sample :: String
sample = stringify $ encodeFooSpecial (Bar "x" true)
-- In the REPL you can see that the special encoder was used:
-- > log sample
-- {"values":["x",0],"tag":"Bar"}
Thanks for the use case.
Another simpler case would be to turn back the new representation into another type afterwards:
module SampleGeneric2 where
import Prelude
import Data.Either (Either, hush)
import Data.Generic.Rep (class Generic, from, to)
import Data.Maybe (Maybe)
import Heterogeneous.Mapping (class Mapping, hmap)
data Foo f
= Bar (f Int)
| Baz (f String) (f Char) Boolean
derive instance Generic (Foo f) _
data MyMap = MyMap
instance Mapping MyMap (Either String a) (Maybe a) where
mapping _ = hush
else instance Mapping MyMap a a where
mapping _ = identity
fooEitherTofooMaybe :: Foo (Either String) -> Foo Maybe
fooEitherTofooMaybe = from >>> hmap MyMap >>> to
And actually I think MappingWithIndex should be possible, too. Whereas the index would be a Tuple of the constructor name as symbol and the product index as type level Int.