为类型同义词Music实现Functor实例遇到问题
问题描述
我正在实现一个类环结构,并将其应用到Paul Hudak所著《The Haskell School of Music》一书中描述的音乐数据结构。相关代码如下(省略了大量Semigroup/Monoid相关实现):
newtype Duo a b = Duo {duo1 :: a} -- A ring-like structure data Song a = Primitive a | Song a :+: Song a -- 按顺序组合音乐 | Song a :=: Song a deriving Eq -- 并行组合音乐 instance Functor Song where fmap f (x :+: y) = fmap f x :+: fmap f y fmap f (x :=: y) = fmap f x :=: fmap f y fmap f (Primitive x) = Primitive $ f x newtype Concurrent a = Concurrent {fromConcurrent :: Song a} deriving (Show) newtype Sequential a = Sequential {fromSequential :: Song a} deriving (Show) type Music a = Duo (Maybe (Concurrent a)) (Maybe (Sequential a))
我尝试为Music编写Functor实例,由于Duo没有Functor实例,我认为这不会有问题,于是写下了如下实现:
instance Functor Music where fmap :: (a -> b) -> Music a -> Music b fmap f = Duo . fmap (fmap f . fromConcurrent) . duo1
但出现了如下错误:
• The type synonym ‘Music’ should have 1 argument, but has been given none • In the instance declaration for ‘Functor Music’ | 167 | instance Functor Music where | ^^^^^^^^^^^^^
我猜测问题在于我本质上只为Duo的子集编写Functor实例,或许只能将Music改为newtype而非类型同义词才能解决,但我希望避免新增包装类型。请问是否有合理的Duo Functor实例实现方案来解决此问题?另外我不理解为何Show实例可以正常定义:
instance (Show a) => Show (Music a) where show (Duo Nothing) = "Silence" show (Duo (Just (Concurrent x))) = show x
解决方案
1. 两类实例的合法性差异
Haskell的类型系统对实例的要求有明确区分:
Show是单参数类,你实例化的是Show (Music a)——Music a是完全应用的具体类型,类型同义词的具体实例可以直接用于这类单参数类的实例声明,所以你的Show代码合法。Functor是构造器类,要求实例必须是未完全应用的类型构造器(比如[]、Maybe、Song这类接受一个类型参数的构造器)。但Music是类型同义词,本质是Duo (Maybe (Concurrent a)) (Maybe (Sequential a))的别名,它本身不是合法的类型构造器,因此直接写instance Functor Music会报错。
2. 为Duo编写适配的Functor实例
你的Duo定义只持有第一个类型参数a的值,完全忽略第二个参数b。我们可以固定第二个参数,为Duo编写针对第一个参数的Functor实例:
instance Functor (Duo b) where fmap f (Duo x) = Duo (f x)
这里Duo b是一个接受单个类型参数的构造器(固定了第二个参数b),完全符合Functor的要求。
同时,为Concurrent和Sequential补充Functor实例(也可以用DeriveFunctor自动推导):
instance Functor Concurrent where fmap f (Concurrent s) = Concurrent (fmap f s) instance Functor Sequential where fmap f (Sequential s) = Sequential (fmap f s)
3. 实现Music的Functor实例
有了上述实例后,Music的Functor实例可以通过链式fmap简洁实现:
instance Functor Music where fmap f = fmap (fmap (fmap f))
链式调用的逻辑是:
- 最内层
fmap f作用于Song a(已有Functor实例) - 中间层
fmap作用于Concurrent a/Sequential a(我们刚实现的实例) - 外层
fmap作用于Maybe(自带Functor实例) - 最外层
fmap作用于Duo(我们编写的实例)
如果你偏好更具体的实现逻辑,也可以写成:
instance Functor Music where fmap f = Duo . fmap (fmap (Concurrent . fmap f . fromConcurrent)) . duo1
内容的提问来源于stack exchange,提问作者New_Caird
相关产品推荐
相关产品推荐

