【问题标题】:Flip functor instance Haskell翻转函子实例 Haskell
【发布时间】:2021-02-01 03:48:32
【问题描述】:

我需要为 Flip 数据类型编写 Functor 实例:

data K a b = K a
newtype Flip f a b = Flip (f b a) deriving (Eq, Show)

instance Functor (Flip K a) where
fmap=undefined

我在课堂上得到的解决方案是:

instance Functor (Flip K a) where
    fmap f (Flip (K b)) = Flip (K (f b))

我真的不明白这里发生了什么,我开始怀疑我对数据类型和函子的全部理解。我所理解的是(如果有任何错误,请纠正我):

  • K 是一种将 2 个参数转换为结构 K a 的数据类型(仅保留第一个参数)
  • Flip 是一种将 3 个参数转换为一个结构的数据类型
  • 因为在fmap :: (a-> b) -> f a -> f bf a有一种*,要写Flip的Functor实例,我们把它写在Flip的最后一个类型上。又名fa 在某种程度上是“常量”,我们为b 类型编写函子。我会写这样的:
instance Functor (Flip f a) where
   fmap f (Flip x y z) = fmap Flip x y (f z)

我知道那是完全错误的,但我不知道为什么。

另外,我们为什么要将K 带入 Flip 的 Functor 实例中?有人可以彻底解释提出这个解决方案的过程以及为什么它是正确的吗?

【问题讨论】:

    标签: haskell


    【解决方案1】:
    • K 是一种将 2 个参数转换为结构 K a 的数据类型(仅保留第一个参数)

    这不太对。 K a b 是使用两个参数形成的数据类型,但说它“将它们变成”任何东西并不正确。相反,它只是向世界表明现在存在一种新类型:K a b。 “所以呢?”你可能会问。好吧,数据类型的后半部分定义了如何生成这种类型的新值。那部分说:“您可以使用此函数创建K a b 类型的新值,我将调用K,其类型为a -> K a b。” 真的认识到 type Kconstructor K 之间存在区别。

    所以,并不是K“只保留第一个参数”——而是构造函数K(它是一个函数)碰巧不接受b类型的任何参数。


    • Flip 是一种将 3 个参数转换为一个结构的数据类型

    就像上面一样,这不太对。 Flip 声明指出可以有 Flip f a b 类型的值,唯一的方法是使用 f b a -> Flip f a b 类型的构造函数 Flip

    如果您想知道我是如何为构造函数 KFlip 提供类型签名的,它实际上并不神秘,您可以通过在其中键入 :t K:t Flip 来仔细检查GHCI。这些类型完全基于数据类型声明的右侧进行分配。另外,请注意类型名称和构造函数不必相同。例如,考虑这种数据类型:

    data Foo a = Bar Int a | Foo String | Baz a a
    

    这声明了一个带有三个构造函数的类型Foo a

    Bar :: Int -> a -> Foo a
    Foo :: String -> Foo a
    Baz :: a -> a -> Foo a
    

    基本上,构造函数名称后面的每个类型都是构造函数的参数。


    • 因为在fmap :: (a-> b) -> f a -> f b中,f a有一种*,要写Flip的Functor实例,我们把它写在Flip的最后一个类型上。又名fa 在某种程度上是“常量”,我们为b 类型编写函子。

    这基本上是对的!你也可以说f 有一种* -> *。由于Flip 具有类型(* -> *) -> * -> * -> *,因此您需要为其提供两个类型参数(第一个类型* -> * 和第二个类型*)以使其成为正确的类型。前两个参数在实例中成为固定的(某种意义上的“常量”)。


    我会这样写:...我知道那是完全错误的,但我不知道为什么。

    您的实例完全错误的原因是您将 typeconstructor 混淆了。将(Flip x y z) 放在您所做的模式位置是没有意义的,因为构造函数 Flip 只接受一个参数——记住,它的类型是Flip :: f b a -> Flip f a b!所以你想写这样的东西:

    instance Functor (Flip f a) where
       fmap f (Flip fxa) = ...
    

    现在,... 填写什么?你有一个值fxa :: f x a,你有一个函数f :: x -> y,你需要产生一个f y a类型的值。老实说,我不知道该怎么做。毕竟,f x a 的值 什么?我们不知道f是什么?!


    另外,我们为什么要将K 带入Flip 的Functor 实例?有人可以彻底解释提出这个解决方案的过程以及为什么它是正确的吗?

    我们刚刚在上面看到,我们不能为任意f 编写Functor 实例,但我们可以为特定 f 编写它。事实证明,K 就是这样一个特别有效的f。让我们试着让它工作:

    instance Functor (Flip K a) where
      fmap f (Flip kxa) = ...
    

    f 是任意的时,我们被困在这里,但现在我们知道kxa :: K x a。请记住,生成K x a 类型值的唯一方法是使用构造函数 K。因此,这个值kxa必须是使用该构造函数生成的,所以我们可以将其拆分为:kxa ⩳ K x' where x' :: x。让我们继续把它放到我们的模式中:

      fmap f (Flip (K x')) = ...
    

    现在我们可以取得进展了!我们需要产生一个Flip K a y 类型的值。唔。产生Flip 类型值的唯一方法是使用Flip 构造函数,所以让我们从它开始:

      fmap f (Flip (K x')) = Flip ...
    

    Flip K a y 类型的 Flip 构造函数采用 K y a 类型的值。产生其中之一的唯一方法是使用 K 构造函数,所以让我们添加:

      fmap f (Flip (K x')) = Flip (K ...)
    

    K y a 类型的K 构造函数接受y 类型的值,因此我们需要在此处提供y 类型的值。我们有一个值x' :: x 和一个函数f :: x -> y。将第一个插入第二个为我们提供了我们需要的值:

      fmap f (Flip (K x')) = Flip (K (f x'))
    

    只需将x' 重命名为b,您就拥有了老师提供的代码。

    【讨论】:

    • 这非常有帮助,非常感谢!
    【解决方案2】:

    DDub 在他们的回答中写道:

    你有一个值fxa :: f x a,你有一个函数f :: x -> y,你需要产生一个f y a类型的值。老实说,我不知道该怎么做。毕竟,f x a 类型的值是什么?我们不知道f是什么?!

    我同意,但我想补充一点。你的老师关于如何处理这个问题的想法很酷(当你试图写下一些反例时,像 K 这样的东西非常方便,比如 here),但是,我认为我们可以编写这段代码范围更广。我用Data.Bifunctor

    那么,Bifunctors 是什么?它们就像它们的名字所说的那样:* -> * -> * 类型(有时我们也称为双函子,但它们不是同一个东西),它允许映射其两个参数(来自the source 的 sn-p):

    class Bifunctor p where
      -- | Map over both arguments at the same time.
      --
      -- @'bimap' f g ≡ 'first' f '.' 'second' g@
      bimap :: (a -> b) -> (c -> d) -> p a c -> p b d
      bimap f g = first f . second g
      {-# INLINE bimap #-}
    
      -- | Map covariantly over the first argument.
      --
      -- @'first' f ≡ 'bimap' f 'id'@
      first :: (a -> b) -> p a c -> p b c
      first f = bimap f id
      {-# INLINE first #-}
    
      -- | Map covariantly over the second argument.
      --
      -- @'second' ≡ 'bimap' 'id'@
      second :: (b -> c) -> p a b -> p a c
      second = bimap id
      {-# INLINE second #-}
    

    所以,我会这样做:

    instance Bifunctor f => Functor (Flip f a) where
        fmap x2y (Flip fxa) = Flip (first x2y fxa)
    

    说到你老师的代码,这是一个非常好的主意,但更窄的一个,因为KBifunctor

    instance Bifunctor K where
        bimap f _g (K a) = K (f a)
    

    一个lawful一个:

    bimap id id (K a) = K (id a) = id (K a)
    

    正如link above 中所说,只写下bimap,这是我们唯一需要担心的法律。

    【讨论】:

      【解决方案3】:

      我们只需要使用 sanehelpful 命名,突然间一切都变得简单明了(而不是曲折和扭曲):

      data K b a = MkK b                     -- the type (K b a)      "is" just (b)
      newtype Flip f a b = MkFlip (f b a)    -- the type (Flip f a b) "is" (f b a)
                           deriving (Eq, Show)
      
      instance Functor (Flip K a) where
          -- fmap :: (b -> c) -> Flip K a b -> Flip K a c
          fmap g (MkFlip (MkK b)) = MkFlip (MkK (g b))
              --        MkK b :: K b a
              --  MkFlip (_ :: K b a) :: Flip K a b
      

      现在看到这一点,我们的脑海中甚至没有一个问题出现,没有一个疑问我们无法立即解决。

      对类型和数据构造函数使用相同的名称在教学时,以及对“f”unction 和“f”unctor 都使用f,是纯粹的虐待学生。

      只有当您厌倦了所有 Mks 并且认为它们对您没有任何帮助时,您才能像专家通常所做的那样安全轻松地将它们扔掉。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-03-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-05-30
        相关资源
        最近更新 更多