在Haskell中如何确定一个类型是否是函子?

jecbmhm3  于 2022-12-19  发布在  其他
关注(0)|答案(3)|浏览(138)

我有一个数据类型:

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数据类型?

6yjfywim

6yjfywim1#

简短的无应答

在您自己思考之前,尝试让GHC为您编写示例:

{-# LANGUAGE DeriveFunctor #-}

data Tree a = Leaf | Branch a (Tree a) (Tree a)
  deriving (Functor)

这恰好在这种情况下工作,然后你保证有一个守法的示例!
说真的,这您通常应该为您的数据类型获取Functor示例的方式,但是您仍然应该知道什么时候它是有意义的!

实际答案

我想确定(不仅是对于此数据类型,而且对于String等其他数据类型)这些数据类型是否是Functor的合法示例
因此,对于一个Functor示例,你首先需要一个 parametric type,也就是一个“container”,它不关心你在里面存储了什么类型。所以,严格地说,functor根本不应该是类型,而应该是一个 type constructor 或 * type-level function*★。实际上,你可以通过检查数据声明中是否有类型变量来发现:如果是Tree,您会立即在代码中看到它

data Tree a = ... ✓

如果你手边没有源代码,你可以向GHCi索取 kind

Prelude> :set -XTypeInType -XNoStarIsType†
Prelude> :k Maybe
Maybe :: Type -> Type  ✓
Prelude> :k String
String :: Type         ✗

如你所见,String甚至没有类型参数,所以它不可能是一个函子。
接下来,你需要看看类型变量是如何在数据结构中被使用的,如果有多个类型参数,下面的所有内容都适用于最后一个参数,例如在data Either a b = ...中我们讨论的是b参数。

  • 如果它根本没有被使用(例如,如果它是一个 phantom 类型的参数),那么你可以写一个合法的Functor示例:也不要使用mapping-function。
data Tough a = Tough String

instance Functor Tough where
  fmap _ (Tough s) = Tough s

(然而,在这种情况下,也许你不应该写一个函子示例,因为幻像参数通常意味着常量唯一标签。)

  • 如果它被直接用作类型构造函数中某个字段的一部分,那么你可以写一个functor示例,然后fmap-ped函数应该被应用于所有这些值。
data Maybe a = Nothing
             | Just a

instance Functor Maybe where
  fmap _ Nothing = Nothing
  fmap f (Just a) = Just $ f a
  • 如果它被用于数据结构中更深层次的嵌套,但所有的嵌套都在函子本身中,那么它就是一个函子(如果你只是试图定义自己的同一个函子,也就是递归类型,这也成立!)
data TwoLists a = TwoLists {listL :: [a], listR :: [a]}

instance Functor TwoLists where
  fmap f (TwoLists ll lr) = TwoLists (fmap f ll) (fmap f lr)
  • [高级,最好现在忽略这个]如果嵌套不是由正规(协变)函子组成,而是由 * 偶数个逆变函子 * 组成,那么你的整个类型也是协变函子。

★类型构造函数实际上是一种非常特殊的类型级函数,特别是它们是内射的。从数学上讲,一个函子不需要内射地Map它的对象(在Hask的情况下,对象是类型),但在Haskell中,这对于类型检查器是必要的。
†这些语法扩展导致GHCi将类型显示为Type;过去它会显示*,这仍然是旧GHC版本的默认值,但现在已弃用。
‡然而,String实际上是[Char]的同义词,也就是一个字符列表,而list * 是一个函子,所以你可以对一个字符串执行fmap,但这并不意味着你使用的是“字符串函子”:你使用的是 *list函子 *,即使你从一个字符串开始,结果也可能不是字符串(而是一个整数列表)。

qhhrdooz

qhhrdooz2#

尝试编写这样的函数。如果你成功了,它肯定是一个Functor。如果你失败了,它可能不是,或者可能你没有足够的创造力1。对于Tree来说,实现起来相对简单,当然初学者可能需要寻求帮助。
fmap的签名特化为树,您需要一个具有以下签名的函数:

mapTree :: (a -> b) -> Tree a -> Tree b
mapTree f Leaf = _
mapTree f (Branch value left right) = _

1实际上有很多类型,你可以通过查看构造函数的字段来证明它们不是Functor,但由于这不适用于Tree,我们就不深入讨论了。

lzfw57am

lzfw57am3#

警告:这不完整;我希望有人能填补我留下的关于不动点泛函性的漏洞。事实5和我对它的使用感觉不稳定。
String不是一个函子,因为它有错误的种类String :: Type,但是一个函子必须有种类Type -> Type
在讨论Tree之前,让我们先确定一些事实。

  1. Constant a(对于任何类型a)是一个函子:
-- From Data.Functor.Constant
newtype Constant a b = Constant { getConstant :: a }
instance Functor (Constant a) where
    fmap _ (Constant x) = Constant x
  1. Identity是函子
-- Adapted from Data.Functor.Identity
newtype Identity a = Identity { runIdentity :: a }
instance Functor Identity where
    fmap f (Identity x) = Identity (f x)

1.如果分量是函子,则求和类型是函子。

-- From Data.Functor.Sum
data Sum f g a = InL (f a) | InR (g a)
instance (Functor f, Functor g) => Functor (Sum f g) where
    fmap f (InL x) = InL (fmap f x)
    fmap f (InR y) = InR (fmap f y)

1.如果组件是函子,则乘积类型是函子

-- From Data.Functor.Product
data Product f g a = Pair (f a) (g a)
instance (Functor f, Functor g) => Functor (Product f g) where
    fmap f (Pair x y) = Pair (fmap f x) (fmap f y)

1.某些不动点是函子。

-- From Data.Functor.Fixedpoint
newtype Fix f = Fix { unFix :: f (Fix f) }

instance (Functor f, Functor t) => Functor (Fix (f t)) where
    fmap g (Fix h) = Fix (fmap g (unfix h))

记住这些事实,我们将把我们的Tree类型分解成已知函子的和与积的组合,这将因此建立我们的类型同构于函子,因此也是函子本身。
首先,Leaf只是()的一个描述性别名,我们可以用另一个类型参数替换对Tree a的递归引用。

-- This is slightly different from some explanations of
-- recursive types, where t would be the subtree type itself, not
-- a type constructor.
data TreeF t a = () | Branch a (t a) (t a)

接下来,我们去掉a,注意类型()同构于Constant () aa同构于Identity a,进一步地,一个三路乘积同构于两个二路乘积(即(a, b, c) ~ (a, (b, c))):

-- Algebraically, T a = 1 + a*T*T
data TreeF t = Sum (Constant ()) (Product Identity (Product t t))

上面的事实1-4允许我们得出结论,只要t是函子,TreeF t就是函子。
最后,我们可以使用“fact”5来推断Fix TreeF (Fix TreeF) ~ Tree是函子。

相关问题