自定义Or类型的Functor实例使用as-pattern时类型检查失败
Hey there! Let's figure out why your as-pattern rewrite of the Or Functor instance is failing, and how to fix it.
First, let's recap the working version for reference:
data Or a b = First a | Second b deriving (Eq, Show) instance Functor (Or a) where fmap _ (First x) = First x fmap f (Second b) = Second $ f b
The Problem with the As-Pattern Version
When you write:
instance Functor (Or a) where fmap _ first@(First _) = first fmap f (Second b) = Second $ f b
the type checker throws a fit because of a mismatch between the input and output types of fmap. Let's break down fmap's type for Or a:
fmap :: (b -> c) -> Or a b -> Or a c
The input is an Or a b, and the output needs to be an Or a c (note that b and c are distinct type variables).
When you use first@(First _), the variable first gets inferred as having type Or a b (the input type). But fmap expects you to return an Or a c. Since b and c are separate rigid type variables (they can't be unified unless explicitly allowed), the compiler can't reconcile Or a b with Or a c—hence the error about not being able to match a1 (the compiler's name for b) with b (its name for c).
In the working version, when you write First x on the right-hand side, the compiler knows it can construct an Or a c from the a value x—the First constructor doesn't care about the second type parameter, so it happily builds the correct output type. But when you return first directly, you're trying to reuse the input value's type, which doesn't match what fmap needs to return.
Fixing the As-Pattern Version
You have a couple of clean options here:
Reconstruct the
Firstvalue (even with the as-pattern)
You can keep the as-pattern for clarity (if you need to reference the whole value elsewhere) but still rebuild theFirstconstructor to get the right type:instance Functor (Or a) where fmap _ first@(First x) = First x fmap f (Second b) = Second $ f bThis works because
First xis inferred asOr a c(the output type), whilefirstisOr a b—the constructor lets us "cast" the value to the correct type by reusing theapayload.Use a type annotation with
ScopedTypeVariables
If you really want to returnfirstdirectly, you can use theScopedTypeVariablesextension to explicitly tell the compiler thatfirstcan be treated asOr a c:{-# LANGUAGE ScopedTypeVariables #-} data Or a b = First a | Second b deriving (Eq, Show) instance Functor (Or a) where fmap _ (first@(First _) :: Or a c) = first fmap f (Second b) = Second $ f bThe type annotation forces the compiler to unify the input's second type parameter with the output's, making the type check pass.
Why This Matters
Haskell's type system treats Or a b and Or a c as distinct types when b ≠ c, even though they share the same First constructor. Reconstructing the First value lets the type system know you intend to produce the output type, whereas returning the input value directly locks you into the input type.
内容的提问来源于stack exchange,提问作者Matthias Braun

