Brent Yorgey的Typeclassopedia给出了以下练习:
举一个类型
* -> *
的例子,它不能成为一种Functor
的实例(不使用undefined
)。
请告诉我“无法成为Functor
”的实例。
另外,我很欣赏一个例子,但也许作为一个剧透,以便你可以指导我找到答案。
答案 0 :(得分:18)
让我们谈谈差异。
这是基本概念。考虑类型A -> B
。我想让你想象的是,这种类型类似于"拥有B
"还有一个A
"事实上,如果您还清了A
,则会立即收到B
。功能就像托管一样。
"有"的概念和"欠"可以扩展到其他类型。例如,最简单的容器
newtype Box a = Box a
表现得像这样:如果你"有"一个Box a
那么你也有"有" a
。我们认为类型* -> *
和"有"他们的论点是(协变)仿函数,我们可以将它们实例化为Functor
instance Functor Box where fmap f (Box a) = Box (f a)
如果我们考虑一个类型的谓词类型会发生什么,比如
newtype Pred a = Pred (a -> Bool)
在这种情况下,如果我们"有"一个Pred a
,我们实际上"欠" a
。这是因a
位于(->)
箭头左侧。通过将函数传递到容器并将其应用于我们所拥有的所有位置来定义fmap
Functor
的位置。我们的内在类型,我们不能对Pred a
做同样的事情,因为我们没有"有"和a
s。
相反,我们会这样做
class Contravariant f where
contramap :: (a -> b) -> (f b -> f a)
现在contramap
就像一个"翻转" fmap
?它将允许我们将该功能应用于我们拥有的地方" b
中的Pred b
,以便获得Pred a
。我们可以致电contramap
"以物易物"因为它编码的想法是,如果你知道如何从b
获得a
s,那么你可以将b
的债务变成a
的债务。< / p>
让我们看看它是如何运作的
instance Contravariant Pred where
contramap f (Pred p) = Pred (\a -> p (f a))
我们只是在将f
传递给谓词函数之前使用Functor
进行交易。精彩!
所以现在我们有协变和逆变类型。从技术上讲,这些被称为协变和逆变&#34;仿函数&#34;。我还会立刻说明,几乎总是一个逆变函子也不是协变的。因此,这回答了你的问题:存在一堆逆变函子,它们无法实例化为Pred
。 data Z a = Z -- phantom a!
instance Functor Z where fmap _ Z = Z
instance Contravariant Z where contramap _ Z = Z
就是其中之一。
但是有一些棘手的类型,它们都是逆变和协变的函子。特别是,恒定的仿函数:
Contravariant
事实上,您基本上可以证明Functor
和isPhantom :: (Functor f, Contravariant f) => f a -> f b -- coerce?!
isPhantom = contramap (const ()) . fmap (const ()) -- not really...
都有幻像参数。
-- from Data.Monoid
newtype Endo a = Endo (a -> a)
另一方面,像
这样的类型会发生什么Endo a
在a
我们都欠{并且Endo
。这是否意味着我们没有债务?嗯,不,它只是意味着Endo
想要协变和逆变并且没有幻像参数。结果:Functor
不变并且既不能Contravariant
也不会{{1}}实例化。
答案 1 :(得分:11)
t
类型* -> *
可以成为Functor
的实例,当且仅当有可能实现Functor
类的守法实例时为了它。这意味着您必须实施Functor
课程,并且fmap
必须遵守Functor
法律:
fmap id x == x
fmap f (fmap g x) == fmap (f . g) x
所以基本上,要解决这个问题,你必须为自己选择的某种类型命名并证明fmap
没有合法的实现。
让我们从非 - 示例开始,设置音调。 (->) :: * -> * -> *
是函数类型构造函数,如String -> Int :: *
等函数类型中所示。在Haskell中,您可以部分应用类型构造函数,因此您可以使用类型(->) r :: * -> *
。此类型为Functor
:
instance Functor ((->) r) where
fmap f g = f . g
直观地说,此处的Functor
实例允许您将f :: a -> b
应用于函数g :: r -> a
的返回值“之前”(可以这么说)您应用g
一些x :: r
。例如,如果这是返回其参数长度的函数:
length :: [a] -> Int
...然后这是返回其参数长度两倍的函数:
twiceTheLength :: [a] -> Int
twiceTheLength = fmap (*2) length
有用的事实:Reader
monad只是newtype
的{{1}}:
(->)
现在我们已经完成了非示例,这里的类型不能成为newtype Reader r a = Reader { runReader :: r -> a }
instance Functor (Reader r) where
fmap f (Reader g) = Reader (f . g)
instance Applicative (Reader r) where
pure a = Reader (const a)
Reader f <*> Reader a = Reader $ \r -> f r (a r)
instance Monad (Reader r) where
return = pure
Reader f >>= g = Reader $ \r -> runReader g (f r) r
:
Functor
是的,我所做的就是向后拼写名称,更重要的是,翻转类型参数的顺序。我会让你试着弄清楚为什么这种类型不能成为type Redaer a r = Redaer { runRedaer :: r -> a }
-- Not gonna work!
instance Functor (Redaer a) where
fmap f (Redaer g) = ...
的实例。