Haskell实现范畴论时遇到Could not deduce约束推导错误
Hey there! Let's work through this type ambiguity issue you're hitting with your Comp functor instance. I remember how confusing category theory abstractions in Haskell can be when you're starting out, so let's break this down step by step.
First, let's format your code clearly for reference:
-- Hidden default id and (.) for clarity class Category c where id :: forall a. c a a (.) :: c b d -> c a b -> c a d instance Category (->) where id = \x -> x (.) = \f g x -> f $ g x class (Category c, Category d) => Functor c d f where fmap :: c a b -> d (f a) (f b) data Comp f g x = Comp { getComp :: f (g x) } -- The problematic instance instance (Category c, Category d, Category e, Functor d e f, Functor c d g) => Functor c e (Comp f g) where fmap = _
You're seeing this error:
• Could not deduce (Functor d0 e f) from the context: (Category c, Category d, Category e, Functor d e f, Functor c d g)
bound by an instance declaration: ...
type variable d0 is ambiguous.
What's Causing the Ambiguity?
When you leave fmap = _, the compiler knows it needs a function of type c a b -> e (Comp f g a) (Comp f g b) (which expands to c a b -> e (f (g a)) (f (g b))), but it doesn't have enough context to connect your instance constraints to the required implementation.
Your instance tells the compiler two key things:
gis a functor that converts arrows from categorycto categoryd(Functor c d g)fis a functor that converts arrows from categorydto categorye(Functor d e f)
To build the fmap for Comp f g, you need to compose these two functor operations: first use g's fmap to lift the c-arrow into a d-arrow (working on g a/g b), then use f's fmap to lift that d-arrow into an e-arrow (working on f (g a)/f (g b)).
The ambiguity happens because the compiler can't guess this composition on its own—it needs you to explicitly show that the d category used for f's functor is exactly the same one that connects c to e via g.
The Fix
Replace the _ with the composed fmap operations. Here's the concise version:
instance (Category c, Category d, Category e, Functor d e f, Functor c d g) => Functor c e (Comp f g) where fmap h = fmap (fmap h)
If you want to see exactly what's happening (great for learning!), here's the explicit, unwrapped version:
instance (Category c, Category d, Category e, Functor d e f, Functor c d g) => Functor c e (Comp f g) where fmap h = \compValue -> Comp $ fmap (fmap h) (getComp compValue)
Why This Works
Let's unpack the logic:
his your input arrow from categoryc:c a bfmap h(usingg's functor instance) converts this to ad-arrow:d (g a) (g b)fmap (fmap h)(usingf's functor instance) lifts thatd-arrow into ane-arrow:e (f (g a)) (f (g b))- The explicit version wraps/unwraps the
Compnewtype to match the required output type.
By writing this composition, you give the compiler the clue it needs to resolve the d type variable—no more ambiguity!
内容的提问来源于stack exchange,提问作者Joseph Cutler

