【问题标题】:how do you determine if a type is a functor or not in haskell?你如何确定一个类型是否是一个函子在haskell中?
【发布时间】:2020-07-21 05:38:17
【问题描述】:

我有一个数据类型:

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

我想确定,不仅是针对这种数据类型,还针对其他数据类型,例如 String,这些数据类型是否是 functor (https://hackage.haskell.org/package/base-4.14.0.0/docs/Data-Functor.html) 的守法实例。该链接表明,如果一个类型具有函数 fmap,您可以证明它是函子,给定任何类型 a 和 b,它允许您应用任何类型的函数 (a -> b) 将 fa 转换为 fb,保留f 的结构我将如何测试我的 Tree 数据类型或 String 数据类型?

【问题讨论】:

  • 你可以继续分析,通过证明Tree可以用更原始的函子来定义。例如,type TreeF t = Sum (Constant ()) (Product Identity (Product t t))type Tree = Fix TreeF(您可以证明此 Tree 与您的定义同构)。既然Constant ()Identity、函子的和与积都是函子,那么TreeF t也是函子(只要t是函子)。我不确定声称TreeF t 的不动点本身就是函子的确切论据。
  • @chepner 你的别名不会丢失a 参数吗?
  • @amalloy 我想是的;我试图充实我的评论作为答案,并发现自己在原始草案中使用 t 作为类型和仿函数。欢迎对答案发表评论和更正。

标签: haskell functor


【解决方案1】:

简短的不回答

在你自己做任何思考之前,试着让 GHC 为你编写实例:

{-# LANGUAGE DeriveFunctor #-}

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

这恰好在这种情况下有效,然后保证您有一个守法的实例!

说真的,这您通常应该为您的数据类型获取Functor 实例的方式。但是你仍然应该在有意义的时候了解自己!

实际答案

我想确定,不只是针对这种数据类型,还针对String 等其他数据类型,这些数据类型是否是Functor 的守法实例

因此,对于Functor 实例,您首先需要一个参数类型,即一个“容器”,它不关心您在其中存储什么类型。所以,严格来说,函子根本不应该是一个类型,而是一个类型构造函数类型级函数。实际上,您可以通过检查数据声明是否具有类型变量来看到这一点:如果是 Tree,您会立即在代码中看到它

data Tree <b>a</b> = ... ✓

如果手头没有源代码,可以向 GHCi 索要种类

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

如您所见,String 甚至没有类型参数,所以它不可能是函子。

接下来,你需要看看类型变量是如何在数据结构中使用的。如果有多个类型参数,则以下所有内容都适用于它们的最后一个(最右边),例如在data Either a b = ... 中,我们将讨论b 参数。

  • 如果根本不使用它(即,如果它是 phantom 类型的参数),那么您可以简单地编写一个守法的Functor 实例:也不要使用映射函数。

    data Tough a = Tough String
    
    instance Functor Tough where
      fmap _ (Tough s) = Tough s
    

    (但是,在这种情况下,您可能不应该编写仿函数实例,因为幻像参数通常是常量唯一标签。)

  • 如果它直接用作类型构造函数中某个字段的一部分,那么您可以编写一个仿函数实例。然后应该将fmap-ped 函数应用于所有这些值。

    data Maybe a = Nothing<br>             | Just <b>a</b><br><br>instance Functor Maybe where<br>  fmap _ Nothing = Nothing<br>  fmap f (Just a) = Just $ <b>f a</b>
  • 如果它用在数据结构中嵌套更深的地方,但所有嵌套都在函子本身中,那么它就是函子。 (如果它是你试图定义自己的同一个函子,这也成立,即对于递归类型!)

    data TwoLists a = TwoLists {listL :: [a], listR :: [a]}<br><br>instance Functor TwoLists where<br>  fmap f (TwoLists ll lr) = TwoLists (fmap f ll) (fmap f lr)
  • [高级,如果你暂时忽略这个可能最好]如果嵌套不是由普通(协变)函子组成,而是由 偶数个逆变函子组成,那么你的整个类型也是协变函子。


类型构造函数实际上是一种非常特殊的类型级函数,特别是它们是单射的。从数学上讲,仿函数不需要单射地映射其对象(在 Hask 的情况下,对象是类型),但在 Haskell 中,这对于类型检查器来说是必需的。

这些句法扩展导致 GHCi 将类型显示为Type;从历史上看,它会显示*,这仍然是旧 GHC 版本中的默认设置,但现在已弃用。

不过,String 实际上是[Char] 的同义词,即字符列表,而列表函子。所以你可以实际上在一个字符串上执行fmap,但这并不意味着你使用“字符串仿函数”:你正在使用列表仿函数,甚至如果你从一个字符串开始,结果可能不是一个字符串(而是一个整数列表)。

【讨论】:

  • 感谢@leftaroundabout。我真的很喜欢简短的不回答方法。不要误会我的意思,我很欣赏实际答案背后的逻辑,但是在考试中-执行时间太长了。我的问题是,对于已经定义好的数据类型,如String、Maybe、IO、Gen等,如何执行short non-answer方法?
  • 那将是一个孤儿实例,这通常是个坏主意。一般来说,你可以相信,如果一个类型有一个 Functor 实例是有意义的,那么它确实在它自己的模块中定义了一个。如果没有,那么可能有一个很好的理由。也就是说,总是有-XStandaloneDeriving。 — 在考试中……最好确保这实际上是一个公认的答案!如果我提出一个关于如何在某处实施Functor 的问题,我不会给deriving (Functor) 打满分。
【解决方案2】:

尝试编写这样的函数。如果你成功了,那绝对是一个 Functor。如果你失败了,那可能不是,或者你的创造力不够1。对于 Tree,实现起来相对简单,当然初学者可能需要寻求帮助。

fmap 的签名专门用于您的树,您需要一个具有此签名的函数:

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

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

【讨论】:

  • 仍然不确定如何完成此功能。我的意思是,目标是什么?
  • 目标已在您的问题中给出。 “一个函数 fmap,在给定任何类型 a 和 b 的情况下,您可以应用任何类型 (a -> b) 的函数来将 f a 转换为 f b,同时保留 f 的结构。”使用 f = Tree,您正在寻找一个保留树结构但修改其中包含的元素的函数。
【解决方案3】:

警告:这是不完整的;我希望有人能填补我留下的关于固定点功能性的漏洞。事实 5 和我对它的使用感觉不稳定。


String 不是函子,因为它的种类不对。 String :: Type,但函子必须有善意的Type -&gt; 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
    
  2. Identity 是一个仿函数

    -- Adapted from Data.Functor.Identity
    newtype Identity a = Identity { runIdentity :: a }
    instance Functor Identity where
        fmap f (Identity x) = Identity (f x)
    
  3. 如果组件是函子,则求和类型是函子。

    -- 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)
    
  4. 如果组件是函子,则产品类型是函子

    -- 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)
    
  5. 某些不动点是函子。

    -- 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)

接下来,我们注意到()Constant () a 同构,aIdentity a 同构,从而摆脱了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 是一个函子。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-08-17
    • 2011-01-19
    • 1970-01-01
    • 2019-09-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多