Haskell 将数据类型设置为*->;*那';他不是函子
Brent Yorgey's提供了以下练习: 请举例说明一种类型的Haskell 将数据类型设置为*->;*那';他不是函子,haskell,functor,Haskell,Functor,Brent Yorgey's提供了以下练习: 请举例说明一种类型的*->*,它不能作为 函子的实例(不使用未定义的) 请告诉我“不能成为Functor的实例”是什么意思 另外,我想举一个例子,但也许作为一个扰流者,你可以,请,引导我找到答案。一个类型t的*->*可以成为Functor的实例,如果并且只有当它可以实现一个遵守法律的Functor类的实例时。这意味着您必须实现函子类,并且您的fmap必须遵守函子规则: fmap id x == x fmap f (fmap g x) == fmap
*->*
,它不能作为
函子的实例(不使用未定义的
)
请告诉我“不能成为Functor
的实例”是什么意思
另外,我想举一个例子,但也许作为一个扰流者,你可以,请,引导我找到答案。一个类型t
的*->*
可以成为Functor
的实例,如果并且只有当它可以实现一个遵守法律的Functor
类的实例时。这意味着您必须实现函子
类,并且您的fmap
必须遵守函子
规则:
fmap id x == x
fmap f (fmap g x) == fmap (f . g) x
所以基本上,要解决这个问题,你必须说出你选择的某种类型,并证明没有合法的fmap
实现
让我们从一个非示例开始,来确定基调(>)::*->*->*->*
是函数类型构造函数,如String->Int::*
等函数类型所示。在Haskell中,您可以部分应用类型构造函数,因此可以使用类似于(>)r::*->*
的类型。此类型是一个函子
:
instance Functor ((->) r) where
fmap f g = f . g
type Redaer a r = Redaer { runRedaer :: r -> a }
-- Not gonna work!
instance Functor (Redaer a) where
fmap f (Redaer g) = ...
直观地说,这里的Functor
实例允许您将f::a->b
应用于函数的返回值g::r->a
“在(可以说)您将g
应用于某些x::r
之前”。例如,如果这是返回其参数长度的函数:
length :: [a] -> Int
twiceTheLength :: [a] -> Int
twiceTheLength = fmap (*2) length
…那么这就是返回两倍于其参数长度的函数:
length :: [a] -> Int
twiceTheLength :: [a] -> Int
twiceTheLength = fmap (*2) length
有用的事实:阅读器
monad只是(>)
的一个新类型
:
是的,我所做的只是将名称向后拼写,更重要的是,翻转类型参数的顺序。我将让您尝试并找出为什么不能将此类型作为Functor的实例
这是基本概念。考虑类型<代码> a> b>代码>。我想让你们想象的是,这种类型类似于“拥有一个B
”和“拥有一个a
”。事实上,如果您偿还了A
,您将立即收到B
。函数在某种程度上类似于托管
“拥有”和“欠”的概念可以延伸到其他类型。例如,最简单的容器
newtype Box a = Box a
行为如下:如果您“拥有”一个框a
,那么您也“拥有”一个a
。我们考虑类型有“<代码> *> *> /代码>和“拥有”它们的参数为(协变)函子,我们可以将它们实例化为<代码>函子< /COD>
instance Functor Box where fmap f (Box a) = Box (f a)
如果我们考虑谓词类型的类型,比如会发生什么?
newtype Pred a = Pred (a -> Bool)
在这种情况下,如果我们“拥有”aPred a
,我们实际上“欠”aa
。这是由于(>)
箭头的左侧有a
。当Functor
的fmap
通过将函数传递到容器中并将其应用到我们“拥有”内部类型的所有位置来定义时,我们不能对Pred a
执行相同的操作,因为我们没有“拥有”和a
s
相反,我们将这样做
class Contravariant f where
contramap :: (a -> b) -> (f b -> f a)
现在,contracmap
就像一个“翻转的”fmap
?它将允许我们将该功能应用于我们在Pred b
中“拥有”ab
的位置,以便接收aPred a
。我们可以将contracmap
称为“物物交换”,因为它编码了这样一种思想:如果你知道如何从a
s获得b
s,那么你可以将b
s的债务变成a
s的债务
让我们看看它是如何工作的
instance Contravariant Pred where
contramap f (Pred p) = Pred (\a -> p (f a))
在将其传递到谓词函数之前,我们只需使用f
运行交易。太好了
现在我们有协变型和逆变型。从技术上讲,它们被称为协变和逆变“函子”。我还将立即声明,几乎所有的逆变函子都不是协变的。因此,这就回答了您的问题:存在一堆不能实例化为函子的逆变函子Pred
就是其中之一
不过,也有一些棘手的类型,既有逆变函子,也有协变函子。特别是常数函子:
data Z a = Z -- phantom a!
instance Functor Z where fmap _ Z = Z
instance Contravariant Z where contramap _ Z = Z
事实上,你可以从本质上证明,任何同时是反变的
和函子
的东西都有幻像参数
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
是不变的,既不能实例化函子
也不能实例化逆变
A函子f
具有fmap::(A->b)->fa->fb
,满足fmap x。fmap y=fmap(x.y)
。提出一种类型,其中(1)您不能定义fmap
,或者(2)您可以定义fmap
,但不能使其遵循规则。提示:一种常见的函子类型是容器,因此如果您发明了一种新的容器,它可能就是函子。尝试创建一个类型xa
,这样xa
就不包含a
,但可能会对a
做一些其他的事情。我将通过提醒您Haskell是一种函数语言来补充@DietrichEpp的提示。你有什么想法吗?哈斯克尔IRC我得到了一个提示,即回答-data Foo a=Foo(a->a)
fmap的类型签名是什么-Foo(a->a)
看起来像fb
。你能用a->b
把fooa
变成foob
吗?等价地