如何将Either构造为针对第一个类型参数的Functor
嘿,这个问题戳中了Haskell里Functor类型类一个很关键的点——类型构造器的kind匹配!咱们先回顾默认情况,再一步步实现你想要的效果。
首先,默认的Either是二元类型构造器,它的kind是* -> * -> *,而Functor要求的是一元类型构造器(kind * -> *)。所以标准库的做法是固定第一个类型参数,得到Either a(kind * -> *),这样fmap就只能作用在Right包裹的值上:
instance Functor (Either a) where fmap f (Right x) = Right (f x) fmap f (Left x) = Left x
而你想要让fmap作用在Left包裹的值上,也就是固定第二个类型参数,让第一个参数成为fmap的映射目标。但直接给Either写另一个实例是行不通的——Haskell不允许同一个类型构造器有多个同类型类的实例(除非用扩展,那是另一个话题了)。
解决办法很简单:用newtype给Either做一个「参数翻转」的包装,把原来的Either a b转换成以b为固定参数、a为可变参数的一元构造器。
具体实现步骤
- 定义一个
newtype,翻转Either的参数顺序:
newtype LeftFunctorEither b a = LeftFunctorEither (Either a b)
这里LeftFunctorEither b a对应的底层类型是Either a b——也就是说,LeftFunctorEither b是一个一元类型构造器(kind * -> *),固定了原来Either的第二个参数b,剩下的a就是我们要映射的目标。
- 给这个
newtype实现Functor实例:
instance Functor (LeftFunctorEither b) where fmap f (LeftFunctorEither (Left x)) = LeftFunctorEither (Left (f x)) fmap f (LeftFunctorEither (Right x)) = LeftFunctorEither (Right x)
这样一来,fmap就会精准作用在Left包裹的值上,完全符合你想要的行为!
额外小技巧
如果你不想自己写这个newtype,可以用base库中Data.Functor.Flip这个现成的类型,它的作用就是翻转二元类型构造器的参数顺序:
import Data.Functor.Flip -- 直接用Flip包装Either实现目标实例 instance Functor (Flip Either b) where fmap f (Flip (Left x)) = Flip (Left (f x)) fmap f (Flip (Right x)) = Flip (Right x)
效果和我们自定义的newtype完全一致,只是少写了几行代码~
总结一下:核心就是通过newtype调整类型构造器的参数顺序,让我们想要映射的类型参数成为Functor要求的「可变参数」,从而实现自定义的fmap行为。
内容的提问来源于stack exchange,提问作者kishlaya

