I'm trying to use type class to simulate ad-hoc polymorphism and solve generic cases involving higher kinded types and so far can't figure out the correct solution.
What I'm looking for is to define something similar to:
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
infixl 0 >>>
-- | Type class that allows applying a value of type #fn# to some #m a#
class Apply m a fn b | a fn -> b where
(>>>) :: m a -> fn -> m b
-- to later use it in following manner:
(Just False) >>> True -- same as True <$ ma
(Just True) >>> id -- same as id <$> ma
Nothing >>> pure Bool -- same as Nothing >>= const $ pure Bool
(Just "foo") >>> (\a -> return a) -- same as (Just "foo") >>= (\a -> return a)
So far I've tried multiple options, none of them working.
Just a straight forward solution obviously fails:
instance (Functor m) => Apply m a b b where
(>>>) m b = b <$ m
instance (Monad m) => Apply m a (m b) b where
(>>>) m mb = m >>= const mb
instance (Functor m) => Apply m a (a -> b) b where
(>>>) m fn = fmap fn m
instance (Monad m, a' ~ a) => Apply m a (a' -> m b) b where
(>>>) m fn = m >>= fn
As there are tons of fundep conflicts (all of them) related to the first instance that gladly covers all the cases (duh).
I couldn't work out also a proper type family approach:
class Apply' (fnType :: FnType) m a fn b | a fn -> b where
(>>>) :: m a -> fn -> m b
instance (Functor m) => Apply' Const m a b b where
(>>>) m b = b <$ m
instance (Monad m) => Apply' ConstM m a (m b) b where
(>>>) m mb = m >>= const mb
instance (Functor m, a ~ a') => Apply' Fn m a (a' -> b) b where
(>>>) m mb = m >>= const mb
instance (Functor m, a ~ a') => Apply' Fn m a (a' -> m b) b where
(>>>) m fn = m >>= fn
data FnType = Const | ConstM | Fn | FnM
type family ApplyT a where
ApplyT (m a) = ConstM
ApplyT (a -> m b) = FnM
ApplyT (a -> b) = Fn
ApplyT _ = Const
Here I have almost the same issue, where the first instance conflicts with all of them through fundep.
The end result I want to achieve is somewhat similar to the infamous magnet pattern sometimes used in Scala.
Update:
To clarify the need for such type class even further, here is a somewhat simple example:
-- | Monad to operate on
data Endpoint m a = Endpoint { runEndpoint :: Maybe (m a) } deriving (Functor, Applicative, Monad)
So far there is no huge need to have mentioned operator >>> in place, as users might use the standard set of <$ | <$> | >>= instead. (Actually, not sure about >>= as there is no way to define Endpoint in terms of Monad)
Now to make it a bit more complex:
infixr 6 :::
-- | Let's introduce HList GADT
data HList xs where
HNil :: HList '[]
(:::) :: a -> HList as -> HList (a ': as)
-- Endpoint where a ~ HList
endpoint :: Endpoint IO (HList '[Bool, Int]) = pure $ True ::: 5 ::: HNil
-- Some random function
fn :: Bool -> Int -> String
fn b i = show b ++ show i
fn <$> endpoint -- doesn't work, as fn is a function of a -> b -> c, not HList -> c
Also, imagine that the function fn might be also defined with m String as a result. That's why I'm looking for a way to hide this complexity away from the API user.
Worth mentioning, I already have a type class to convert a -> b -> c into HList '[a, b] -> c
If the goal is to abstract over HLists, just do that. Don't muddle things by introducing a possible monad wrapper at every argument, it turns out to be quite complicated indeed. Instead do the wrapping and lifting at the function level with all the usual tools. So:
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE UndecidableInstances #-}
data HList a where
HNil :: HList '[]
(:::) :: x -> HList xs -> HList (x : xs)
class ApplyArgs args i o | args i -> o, args o -> i where
apply :: i -> HList args -> o
instance i ~ o => ApplyArgs '[] i o where
apply i _ = i
instance (x ~ y, ApplyArgs xs i o) => ApplyArgs (x:xs) (y -> i) o where
apply f (x ::: xs) = apply (f x) xs
Related
I'm trying to figure out how does type inference works together with type classes and so far struggle to grasp it fully.
Let's define the following simple HList:
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE ScopedTypeVariables #-}
infixr 6 :::
data HList xs where
HNil :: HList '[]
(:::) :: a -> HList as -> HList (a ': as)
Now I'm going to define type class that allows to "uncurry" any function into the function of a single argument of type HList:
class FnToProduct fn ls out | fn ls -> out where
fromFunction :: fn -> HList ls -> out
instance (FnToProduct' (IsArity1 fn) fn ls out) => FnToProduct fn ls out where
fromFunction = fromFunction' #(IsArity1 fn)
class FnToProduct' (arity1 :: Bool) fn ls out | fn ls -> out where
fromFunction' :: fn -> HList ls -> out
instance FnToProduct' True (input -> output) '[input] output where
fromFunction' fn (a ::: tail) = fn a
instance (FnToProduct fnOutput tail out') => FnToProduct' False (input -> fnOutput) (input ': tail) out' where
fromFunction' fn (input ::: tail) = fromFunction (fn input) tail
type family IsArity1 fn where
IsArity1 (a -> b -> c) = False
IsArity1 (a -> b) = True
Now I'm going to break the compilation:
test = fromFunction (\a b -> a) (True ::: False ::: HNil)
• Ambiguous type variables ‘p0’, ‘p1’,
‘out0’ arising from a use of ‘fromFunction’
prevents the constraint ‘(FnToProduct'
'False (p1 -> p0 -> p1) '[Bool, Bool] out0)’ from being solved.
(maybe you haven't applied a function to enough arguments?)
Relevant bindings include test :: out0 (bound at src/HList.hs:98:1)
Probable fix: use a type annotation to specify what ‘p0’, ‘p1’,
‘out0’ should be.
These potential instance exist:
one instance involving out-of-scope types
(use -fprint-potential-instances to see them all)
• In the expression:
fromFunction (\ a b -> a) (True ::: False ::: HNil)
In an equation for ‘test’:
test = fromFunction (\ a b -> a) (True ::: False ::: HNil)
|
98 | test = fromFunction (\a b -> a) (True ::: False ::: HNil)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
But if I specify types of function explicitly:
test = fromFunction (\(a :: Bool (b :: Bool) -> a) (True ::: False ::: HNil)
It works just nice. How could I enforce type inference here so the compiler would pick up the types of HList to figure out types in function? I also tried to use infixl/r operators without any luck so far.
Typeclasses are "matchy". In your example, GHC says it is trying to solve the constraint
FnToProduct' 'False (p1 -> p0 -> p1) '[Bool, Bool] out0
Where unification variables p1 and p0 coming from the implicit expansion of the first argument:
(\(a :: _p1) (b :: _p0) -> a)
and unification variable out0 coming from the type of fromFunction. Essentially, GHC doesn't know what the argument types should be, nor what the fromFunction call should eventually return, so it creates three variables to represent them and tries to figure out what they should be.
No instance matches this constraint. The
instance _ => FnToProduct' False (input -> fnOutput) (input ': tail) out'
would require p1 and Bool to be the same, but they aren't. They could be, as you demonstrate with your type-annotated example, but GHC believes that they don't have to be. You could imagine adding
instance _ => FnToProduct' False (Int -> fnOutput) (Bool ': tail) out'
and now you don't know whether a :: Int or a :: Bool, because both instances "fit". But, with the open world assumption, you have to assume new instances like this can be added whenever.
One fix is to use ~ constraints:
instance (i ~ i', o ~ o') => FnToProduct' True (i -> o) '[i'] o'
instance (i ~ i', FnToProduct r t o) => FnToProduct' False (i -> r) (i' ': t) o
The second instance now matches, because the two i variables are different. It actually guides type inference now, because the i ~ i' requirement, in this case, translates to a p1 ~ Bool requirement, which is used to instantiate p1. Similarly for p0, by the first instance.
Or, add another functional dependency. This doesn't always work, but it seems to do the job here
class FnToProduct fn ls out | fn ls -> out, fn out -> ls
class FnToProduct' (arity1 :: Bool) fn ls out | fn ls -> out, fn out -> ls
Which tells GHC that fs (and therefore its argument types, here p1 and p0) can be figure out from ls (here [Bool, Bool]).
That aside, I think your function could be simplified to this:
class AppHList ts o f | ts f -> o, ts o -> f where
appHList :: f -> HList ts -> o
instance AppHList '[] o o where
appHList x HNil = x
instance AppHList ts o f => AppHList (t : ts) o (t -> f) where
appHList f (x ::: xs) = appHList (f x) xs
Artificially requiring the HList to supply all arguments is not particularly useful, and it can really blow up in polymorphic contexts, because you often can't tell what "supplying all the arguments" is supposed to mean. E.g. const can have any number of arguments, from 2 up. So, appHList const (id ::: 'a' ::: HNil) 6 works (where const has 3 arguments), but fromFunction fails in that context.
You can still impose the "returns a not-function" property externally to the more useful function, if you really want it.
type family IsFun f :: Bool where
IsFun (_ -> _) = True
IsFun _ = False
fullAppHList :: (IsFun o ~ False, AppHList ts o f) => f -> HList ts -> o
fullAppHList = appHList
Here's an option:
class (f ~ AbstractList ts o) => AppHList ts o f where
appHList :: f -> HList ts -> o
type family AbstractList xs o where
AbstractList '[] o = o
AbstractList (x ': xs) o =
x -> AbstractList xs o
instance f ~ o => AppHList '[] o f where
appHList x HNil = x
instance (AppHList ts o f', f ~ (t -> f')) => AppHList (t ': ts) o f where
appHList f (x ::: xs) = appHList (f x) xs
Although there's only one equality constraint, this expresses dependencies in several directions. How's that? The key is that the type family can reduce as soon as the structure of the list is known; nothing is needed from the second argument and none of the list elements are needed either.
This seems to have the same inference power as HTNW's simplified version, but it has two advantages:
It's possible to get equality evidence if you need it.
It plays better with the PartialTypeSignatures extension. GHC seems to trip over the fundep version in that context for some reason. See this ticket I just filed.
But wait! There's more! This formulation suggests that even having a class is optional! The class-based approach is likely to be the most efficient when everything specializes and inlines completely, but this version looks a lot simpler:
appHList' :: f ~ AbstractList ts o => f -> HList ts -> o
appHList' x HNil = x
appHList' f (x ::: xs) = appHList' (f x) xs
It's possible to mix classes with lenses to simulate overloaded record fields, up to a point. See, for example, makeFields in Control.Lens.TH. I'm trying to figure out if there's a nice way to reuse the same name as a lens for some types and a traversal for others. Notably, given a sum of products, each product can have lenses, which will degrade to traversals of the sum. The simplest thing I could think of was this**:
First try
class Boo booey where
type Con booey :: (* -> *) -> Constraint
boo :: forall f . Con booey f => (Int -> f Int) -> booey -> f booey
This works fine for simple things, like
data Boop = Boop Int Char
instance Boo Boop where
type Con Boop = Functor
boo f (Boop i c) = (\i' -> Boop i' c) <$> f i
But it falls on its face as soon as you need anything more complicated, like
instance Boo boopy => Boo (Maybe boopy) where
which should be able to produce a Traversal regardless of the choice of underlying Boo.
Second try
The next thing I tried, which sort of works, is to constrain the Con family. This gets kind of gross. First, change the class:
class LTEApplicative c where
lteApplicative :: Applicative a :- c a
class LTEApplicative (Con booey) => Boo booey where
type Con booey :: (* -> *) -> Constraint
boo :: forall f . Con booey f => (Int -> f Int) -> booey -> f booey
This makes Boo instances carry around explicit evidence that their boo produces a Traversal' booey Int. Some more stuff:
instance LTEApplicative Applicative where
lteApplicative = Sub Dict
instance LTEApplicative Functor where
lteApplicative = Sub Dict
-- flub :: Boo booey => Traversal booey booey Int Int
flub :: forall booey f . (Boo booey, Applicative f) => (Int -> f Int) -> booey -> f booey
flub = case lteApplicative of
Sub (Dict :: Dict (Con booey f)) -> boo
instance Boo boopy => Boo (Maybe boopy) where
type Con (Maybe boopy) = Applicative
boo _ Nothing = pure Nothing
boo f (Just x) = Just <$> hum f x
where hum :: Traversal' boopy Int
hum = flub
And the base Boop example works unchanged.
Why this still sucks
We now have boo producing a Lens or Traversal under appropriate circumstances, and we can always use it as a Traversal, but every time we want to do so, we have to first drag in the evidence that it really is one. This is, of course, far too inconvenient for the purpose of implementing overloaded record fields! Is there any nicer way?
** This code compiles with the following (may not be minimal):
{-# LANGUAGE PolyKinds, TypeFamilies,
TypeOperators, FlexibleContexts,
ScopedTypeVariables, RankNTypes,
KindSignatures #-}
import Control.Lens
import Data.Constraint
The following has worked for me before:
{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances #-}
import Control.Lens
data Boop = Boop Int Char deriving (Show)
class HasBoo f s where
boo :: LensLike' f s Int
instance Functor f => HasBoo f Boop where
boo f (Boop a b) = flip Boop b <$> f a
instance (Applicative f, HasBoo f s) => HasBoo f (Maybe s) where
boo = traverse . boo
It can be also scaled to polymorphic fields, if we make sure to enforce all the relevant functional dependencies (just like here). Leaving an overloaded field completely polymorphic is rarely useful or a good idea; I illustrate that case though because from there one can always monomorphize as necessary (or we can constrain polymorphic fields, for example a name field to IsString).
{-# LANGUAGE
UndecidableInstances, MultiParamTypeClasses,
FlexibleInstances, FunctionalDependencies, TemplateHaskell #-}
import Control.Lens
data Foo a b = Foo {_fooFieldA :: a, _fooFieldB :: b} deriving Show
makeLenses ''Foo
class HasFieldA f s t a b | s -> a, t -> b, s b -> t, t a -> s where
fieldA :: LensLike f s t a b
instance Functor f => HasFieldA f (Foo a b) (Foo a' b) a a' where
fieldA = fooFieldA
instance (Applicative f, HasFieldA f s t a b) => HasFieldA f (Maybe s) (Maybe t) a b where
fieldA = traverse . fieldA
One can also go a bit wild and use a single class for all "has" functionality:
{-# LANGUAGE
UndecidableInstances, MultiParamTypeClasses,
RankNTypes, TypeFamilies, DataKinds,
FlexibleInstances, FunctionalDependencies,
TemplateHaskell #-}
import Control.Lens hiding (has)
import GHC.TypeLits
import Data.Proxy
class Has (sym :: Symbol) f s t a b | s sym -> a, sym t -> b, s b -> t, t a -> s where
has' :: Proxy sym -> LensLike f s t a b
data Foo a = Foo {_fooFieldA :: a, _fooFieldB :: Int} deriving Show
makeLenses ''Foo
instance Functor f => Has "fieldA" f (Foo a) (Foo a') a a' where
has' _ = fooFieldA
With GHC 8, one can add
{-# LANGUAGE TypeApplications #-}
and avoid the proxies:
has :: forall (sym :: Symbol) f s t a b. Has sym f s t a b => LensLike f s t a b
has = has' (Proxy :: Proxy sym)
instance (Applicative f, Has "fieldA" f s t a b) => Has "fieldA" f (Maybe s) (Maybe t) a b where
has' _ = traverse . has #"fieldA"
Examples:
> Just (Foo 0 1) ^? has #"fieldA"
Just 0
> Foo 0 1 & has #"fieldA" +~ 10
Foo {_fooFieldA = 10, _fooFieldB = 1}
unsafeVacuous in Data.Void.Unsafe and .# and #. in Data.Profunctor.Unsafe both warn about the dangers of using those functions with functors/profunctors that are GADTs. Some dangerous examples are obvious:
data Foo a where
P :: a -> Foo a
Q :: Foo Void
instance Functor Foo where
fmap f (P x) = P (f x)
fmap f Q = P (f undefined)
Here, unsafeVacuous Q will produce a Q constructor with a bogus type.
This example isn't very troubling because it doesn't look even remotely like a sensible Functor instance. Are there examples that do? In particular, would it be possible to construct useful ones that obey the functor/profunctor laws when manipulated only using their public API, but break horribly in the face of these unsafe functions?
I don't believe there's any true functor where unsafeVacuous would cause a problem. But if you write a bad Functor you can make your own unsafeCoerce, which means it should to labeled with {-# LANGUAGE Unsafe #-}. There was an issue about it in void.
Here's an unsafeCoerce I came up with
{-# LANGUAGE GADTs #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
import Data.Void
import Data.Void.Unsafe
type family F a b where
F a Void = a
F a b = b
data Foo a b where
Foo :: F a b -> Foo a b
instance Functor (Foo a) where
fmap = undefined
unsafeCoerce :: forall a b. (F a b ~ b) => a -> b
unsafeCoerce a = (\(Foo b) -> b) $ (unsafeVacuous (Foo a :: Foo a Void) :: Foo a b)
main :: IO ()
main = print $ (unsafeCoerce 'a' :: Int)
which prints 97.
I have a list of heterogeneous types (or at least that's what I have in mind):
data Nul
data Bits b otherBits where
BitsLst :: b -> otherBits -> Bits b otherBits
NoMoreBits :: Bits b Nul
Now, given an input type b, I want to go through all the slabs of Bits with type b and summarize them, ignoring other slabs with type b' /= b:
class Monoid r => EncodeBit b r | b -> r where
encodeBit :: b -> r
class AbstractFoldable aMulti r where
manyFold :: r -> aMulti -> r
instance (EncodeBit b r, AbstractFoldable otherBits r) =>
AbstractFoldable (Bits b otherBits ) r where
manyFold r0 (BitsLst bi other) = manyFold (r0 `mappend` (encodeBit bi)) other
manyFold b0 NoMoreBits = b0
instance AbstractFoldable otherBits r =>
AbstractFoldable (Bits nb otherBits ) r where
manyFold r0 (BitsLst _ other) = manyFold r0 other
manyFold b0 NoMoreBits = b0
But the compiler wants none of it. And with good reason, since both instance declarations have the same head. Question: what is the correct way of folding over Bits with an arbitrary type?
Note: the above example is compiled with
{-# LANGUAGE MultiParamTypeClasses,
FunctionalDependencies,
GADTs,
DataKinds,
FlexibleInstances,
FlexibleContexts
#-}
Answering your comment:
Actually, I can do if I can filter the heterogeneous list by type. Is that possible?
You can filter the heterogeneous list by type if you add a Typeable constraint to b.
The main idea is we will use Data.Typeable's cast :: (Typeable a, Typeable b) => a -> Maybe b to determine if each item in the list is of a certain type. This will require a Typeable constraint for each item in the list. Instead of building a new list type with this constraint built in, we will make the ability to check if All types in a list meet some constraint.
Our goal is to make the following program output [True,False], filtering a heterogeneous list to only its Bool elements. I will endevour to place the language extensions and imports with the first snippet they are needed for
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}
example :: HList (Bool ': String ': Bool ': String ': '[])
example = HCons True $ HCons "Jack" $ HCons False $ HCons "Jill" $ HNil
main = do
print (ofType example :: [Bool])
HList here is a fairly standard definition of a heterogeneous list in haskell using DataKinds
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
data HList (l :: [*]) where
HCons :: h -> HList t -> HList (h ': t)
HNil :: HList '[]
We want to write ofType with a signature like "if All things in a heterogeneous list are Typeable, get a list of those things of a specific Typeable type.
import Data.Typeable
ofType :: (All Typeable l, Typeable a) => HList l -> [a]
To do this, we need to develop the notion of All things in a list of types satisfying some constraint. We will store the dictionaries for satisfied constraints in a GADT that either captures both the head constraint dictionary and constraints for All of the the tail or proves that the list is empty. A type list satisfies a constraint for All it's items if we can capture the dictionaries for it.
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ConstraintKinds #-}
-- requires the constraints† package.
-- Constraint is actually in GHC.Prim
-- it's just easier to get to this way
import Data.Constraint (Constraint)
class All (c :: * -> Constraint) (l :: [*]) where
allDict :: p1 c -> p2 l -> DList c l
data DList (ctx :: * -> Constraint) (l :: [*]) where
DCons :: (ctx h, All ctx t) => DList ctx (h ': t)
DNil :: DList ctx '[]
DList really is a list of dictionaries. DCons captures the dictionary for the constraint applied to the head item (ctx h) and all the dictionaries for the remainder of the list (All ctx t). We can't get the dictionaries for the tail directly from the constructor, but we can write a function that extracts them from the dictionary for All ctx t.
{-# LANGUAGE ScopedTypeVariables #-}
import Data.Proxy
dtail :: forall ctx h t. DList ctx (h ': t) -> DList ctx t
dtail DCons = allDict (Proxy :: Proxy ctx) (Proxy :: Proxy t)
An empty list of types trivially satisfies any constraint applied to all of its items
instance All c '[] where
allDict _ _ = DNil
If the head of a list satisfies a constraint and all of the tail does too, then everything in the list satisfies the constraint.
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE UndecidableInstances #-}
instance (c h, All c t) => All c (h ': t) where
allDict _ _ = DCons
We can now write ofType, which requires foralls for scoping type variables with ScopedTypeVariables.
import Data.Maybe
ofType :: forall a l. (All Typeable l, Typeable a) => HList l -> [a]
ofType l = ofType' (allDict (Proxy :: Proxy Typeable) l) l
where
ofType' :: forall l. (All Typeable l) => DList Typeable l -> HList l -> [a]
ofType' d#DCons (HCons x t) = maybeToList (cast x) ++ ofType' (dtail d) t
ofType' DNil HNil = []
We are zipping the HList together with its dictionaries with maybeToList . cast and concatenating the results. We can make that explicit with RankNTypes.
{-# LANGUAGE RankNTypes #-}
import Data.Monoid (Monoid, (<>), mempty)
zipDHWith :: forall c w l p. (All c l, Monoid w) => (forall a. (c a) => a -> w) -> p c -> HList l -> w
zipDHWith f p l = zipDHWith' (allDict p l) l
where
zipDHWith' :: forall l. (All c l) => DList c l -> HList l -> w
zipDHWith' d#DCons (HCons x t) = f x <> zipDHWith' (dtail d) t
zipDHWith' DNil HNil = mempty
ofType :: (All Typeable l, Typeable a) => HList l -> [a]
ofType = zipDHWith (maybeToList . cast) (Proxy :: Proxy Typeable)
†constraints
I have some contrived type:
{-# LANGUAGE DeriveFunctor #-}
data T a = T a deriving (Functor)
... and that type is the instance of some contrived class:
class C t where
toInt :: t -> Int
instance C (T a) where
toInt _ = 0
How can I express in a function constraint that T a is an instance of some class for all a?
For example, consider the following function:
f t = toInt $ fmap Left t
Intuitively, I would expect the above function to work since toInt works on T a for all a, but I cannot express that in the type. This does not work:
f :: (Functor t, C (t a)) => t a -> Int
... because when we apply fmap the type has become Either a b. I can't fix this using:
f :: (Functor t, C (t (Either a b))) => t a -> Int
... because b does not represent a universally quantified variable. Nor can I say:
f :: (Functor t, C (t x)) => t a -> Int
... or use forall x to suggest that the constraint is valid for all x.
So my question is if there is a way to say that a constraint is polymorphic over some of its type variables.
Using the constraints package:
{-# LANGUAGE FlexibleContexts, ConstraintKinds, DeriveFunctor, TypeOperators #-}
import Data.Constraint
import Data.Constraint.Forall
data T a = T a deriving (Functor)
class C t where
toInt :: t -> Int
instance C (T a) where
toInt _ = 0
f :: ForallF C T => T a -> Int
f t = (toInt $ fmap Left t) \\ (instF :: ForallF C T :- C (T (Either a b)))