我有一个数据类型:
data Tree a = Leaf | Branch a (Tree a) (Tree a)
我想确定,不仅仅是这个数据类型,还有其他类型,比如String,这些数据类型是否是函数的合法示例(https://hackage.haskell.org/package/base-4.14.0.0/docs/Data-Functor.html)。该链接表明,如果类型具有函数fmap,则可以证明该类型是函子,对于给定的任何类型a和B,该函数允许您应用类型为的任何函数(a -〉B)把一个fa变成一个f b,同时保持f的结构。我该如何测试我的Tree数据类型或String数据类型?
3条答案
按热度按时间6yjfywim1#
简短的无应答
在您自己思考之前,尝试让GHC为您编写示例:
这恰好在这种情况下工作,然后你保证有一个守法的示例!
说真的,这是您通常应该为您的数据类型获取
Functor
示例的方式,但是您仍然应该知道什么时候它是有意义的!实际答案
我想确定(不仅是对于此数据类型,而且对于
String
等其他数据类型)这些数据类型是否是Functor
的合法示例因此,对于一个
Functor
示例,你首先需要一个 parametric type,也就是一个“container”,它不关心你在里面存储了什么类型。所以,严格地说,functor根本不应该是类型,而应该是一个 type constructor 或 * type-level function*★。实际上,你可以通过检查数据声明中是否有类型变量来发现:如果是Tree
,您会立即在代码中看到它如果你手边没有源代码,你可以向GHCi索取 kind:
如你所见,
String
甚至没有类型参数,所以它不可能是一个函子。接下来,你需要看看类型变量是如何在数据结构中被使用的,如果有多个类型参数,下面的所有内容都适用于最后一个参数,例如在
data Either a b = ...
中我们讨论的是b
参数。Functor
示例:也不要使用mapping-function。(然而,在这种情况下,也许你不应该写一个函子示例,因为幻像参数通常意味着常量唯一标签。)
fmap
-ped函数应该被应用于所有这些值。★类型构造函数实际上是一种非常特殊的类型级函数,特别是它们是内射的。从数学上讲,一个函子不需要内射地Map它的对象(在Hask的情况下,对象是类型),但在Haskell中,这对于类型检查器是必要的。
†这些语法扩展导致GHCi将类型显示为
Type
;过去它会显示*
,这仍然是旧GHC版本的默认值,但现在已弃用。‡然而,
String
实际上是[Char]
的同义词,也就是一个字符列表,而list * 是一个函子,所以你可以对一个字符串执行fmap
,但这并不意味着你使用的是“字符串函子”:你使用的是 *list函子 *,即使你从一个字符串开始,结果也可能不是字符串(而是一个整数列表)。qhhrdooz2#
尝试编写这样的函数。如果你成功了,它肯定是一个Functor。如果你失败了,它可能不是,或者可能你没有足够的创造力1。对于Tree来说,实现起来相对简单,当然初学者可能需要寻求帮助。
将
fmap
的签名特化为树,您需要一个具有以下签名的函数:1实际上有很多类型,你可以通过查看构造函数的字段来证明它们不是Functor,但由于这不适用于Tree,我们就不深入讨论了。
lzfw57am3#
警告:这不完整;我希望有人能填补我留下的关于不动点泛函性的漏洞。事实5和我对它的使用感觉不稳定。
String
不是一个函子,因为它有错误的种类String :: Type
,但是一个函子必须有种类Type -> Type
。在讨论
Tree
之前,让我们先确定一些事实。Constant a
(对于任何类型a
)是一个函子:Identity
是函子1.如果分量是函子,则求和类型是函子。
1.如果组件是函子,则乘积类型是函子
1.某些不动点是函子。
记住这些事实,我们将把我们的
Tree
类型分解成已知函子的和与积的组合,这将因此建立我们的类型同构于函子,因此也是函子本身。首先,
Leaf
只是()
的一个描述性别名,我们可以用另一个类型参数替换对Tree a
的递归引用。接下来,我们去掉
a
,注意类型()
同构于Constant () a
,a
同构于Identity a
,进一步地,一个三路乘积同构于两个二路乘积(即(a, b, c) ~ (a, (b, c))
):上面的事实1-4允许我们得出结论,只要
t
是函子,TreeF t
就是函子。最后,我们可以使用“fact”5来推断
Fix TreeF (Fix TreeF) ~ Tree
是函子。