To encode choice between constructors GHC.Generics defines the following type:
data (:+:) f g p = L1 (f p) | R1 (g p)
The Generic class provides the method from to convert a Generic type to a representation:
from :: a -> Rep a x
To write a function that is type generic, I define a class that works on the representation:
class MyClass r where myFun :: r a -> Maybe Int
Assume I also have a class SomeClass for which I defined the instance:
instance (SomeClass (f p),SomeClass (g p)) => SomeClass ((:+:) f g p) where
someFun (R1 _) = Just 42
How would I add a SomeClass constraint to an MyClass instance of the Generic sum type? In other words, what is wrong with the following instance:
instance (SomeClass (f p), SomeClass (g p), MyClass f, MyClass g)
=> MyClass ((:+:) f g) where
myFun (L1 x) = myFun x
myFun y = someFun y -- Error: Could not deduce (SomeClass (f a))
-- arising from a use of ‘someFun’
A complete example I wrote is:
{-# LANGUAGE TypeOperators, DefaultSignatures, DeriveGeneric, FlexibleContexts,
UndecidableInstances, AllowAmbiguousTypes, RankNTypes #-}
module M where
import GHC.Generics
---
class SomeClass a where
someFun :: a -> Maybe Int
default someFun :: (Generic a, MyClass (Rep a)) => a -> Maybe Int
someFun x = myFun (from x)
instance (SomeClass (f p),SomeClass (g p)) => SomeClass ((:+:) f g p) where
someFun (R1 _) = Just 42
instance SomeClass Int where
someFun i = Just i
---
class MyClass r where
myFun :: r a -> Maybe Int
instance (SomeClass a) => MyClass (K1 i a) where
myFun (K1 x) = someFun x -- This is fine
instance (SomeClass (f p), SomeClass (g p), MyClass f, MyClass g) => MyClass ((:+:) f g) where
myFun (L1 x) = myFun x
myFun y = someFun y -- Error: Could not deduce (SomeClass (f a)) arising from a use of ‘someFun’
If you add a SomeClass a constraint to myFun there's really nothing more to do.
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE FlexibleContexts #-}
import Control.Applicative
import GHC.Generics
class SomeClass a where
someFun :: a -> Maybe Int
class MyClass f where
myFun :: SomeClass a => f a -> Maybe Int
default myFun :: (Generic1 f, MyClass (Rep1 f), SomeClass a) => f a -> Maybe Int
myFun f = myFun (from1 f)
You can write instances for all of the data types used in generic representations. The most interesting of these is Par1, which actually uses the SomeClass a constraint to use someFun on an occurrence of the parameter.
-- occurences of the parameter
instance MyClass Par1 where
myFun (Par1 p) = someFun p
-- recursions of kind *
instance SomeClass a => MyClass (K1 i a) where
myFun (K1 a) = someFun a
-- recursions of kind * -> *
instance MyClass f => MyClass (Rec1 f) where
myFun (Rec1 f) = myFun f
-- constructors with no arguments
instance MyClass U1 where
myFun (U1) = Nothing -- or Just 0 or Just 1 depending on what you're doing
-- constructors with multiple arguments
instance (MyClass f, MyClass g) => MyClass (f :*: g) where
myFun (f :*: g) = liftA2 (+) (myFun f) (myFun g) -- or howerever you are going to combine the Maybe Int
-- data types with multiple constructors
instance (MyClass f, MyClass g) => MyClass (f :+: g) where
myFun (L1 f) = myFun f
myFun (R1 g) = myFun g
-- metadata
instance (MyClass f) => MyClass (M1 i c f) where
myFun (M1 f) = myFun f
If you want to support composition of functors, we'll have to be a bit more clever. The obvious definition requires a SomeClass (Maybe Int) instance.
-- composition of functors
instance (MyClass f, MyClass g, Functor f) => MyClass (f :.: g) where
myFun (Comp1 fg) = myFun $ fmap myFun fg
We'll get SomeClass instances generically reusing MyClass to get them. Since MyClass's myFun requires a SomeClass instance, we'll need to prove that the parameter Par1 never occurs in a Rep. from' will prove that the parameter is empty.
class SomeClass a where
someFun :: a -> Maybe Int
default someFun :: (Generic a, MyClass (Rep a)) => a -> Maybe Int
someFun a = myFun (from' a)
The Void type from void represents a type that can't logically exist. The following proves that the parameter for a Generic is always empty
-- Prove that the parameter is always empty
from' :: Generic a => a -> Rep a Void
from' = from
To satisfy the SomeClass constraint for myFun we equip Void with a SomeClass instance. We can be sure someFun :: Void -> Maybe Int is never called because there's no value of type Void to pass to it.
instance SomeClass Void where
someFun = absurd
Now we can derive an instance for SomeClass (Maybe Int) assuming we have a SomeClass Int instance.
-- The following instances are needed for the composition of functors
instance SomeClass Int where
someFun = Just
instance SomeClass a => SomeClass (Maybe a)
You don't need to reuse MyClass with Void to derive SomeClass instances. Instead you can define another class for things that have a myFun regardless of what the parameter is.
class GSomeClass f where
gsomeFun :: f a -> Maybe Int
You'd write GSomeClass instances for everything except Par1 and Rec1 and use GSomeClass to derive SomeClass instances. The Generic instances never use the parameter, not even for types like Maybe a; instead each occurrence of the parameter a appears as a K1 i a p.
class SomeClass a where
someFun :: a -> Maybe Int
default someFun :: (Generic a, GSomeClass (Rep a)) => a -> Maybe Int
someFun a = gsomeFun (from a)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With