繁体   English   中英

翻转函子实例 Haskell

[英]Flip functor instance Haskell

我需要为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

class给我的解决方案是:

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

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

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

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

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

  • K是一种将 2 个参数转换为结构K a (仅保留第一个参数)的数据类型

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

因此,并不是K “只保留第一个参数”——而是构造函数K (它是一个函数)恰好没有采用任何类型为b的 arguments。


  • Flip是一种数据类型,它将 3 arguments 转换为一个结构

正如上面所说,这不太对。 Flip声明声明可以有Flip fab类型的值,唯一的方法是使用具有fba -> Flip fab类型的构造函数Flip

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

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

基本上,构造函数名称后面的每个类型都是 arguments,按顺序排列到构造函数。


  • 因为在fmap:: (a-> b) -> fa -> fb中, fa有种类* ,要编写Flip的 Functor 实例,我们将其写在Flip的最后一个类型上。 aka fa在某种程度上是“常量”,我们为类型b编写仿函数。

这基本上是对的! 你也可以说f有种类* -> * 因为Flip有种类(* -> *) -> * -> * -> * ,你需要给它提供两个类型 arguments (第一个种类* -> *和第二个种类* )让它正确种类。 前两个 arguments 在实例中变为固定值(在某种程度上是“常量”)。


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

您的实例完全错误的原因是您将类型构造函数混淆了。 (Flip xyz)放在模式 position 中是没有意义的,因为构造函数Flip只接受一个参数——记住,它的类型是Flip:: fba -> Flip fab :所以你想写点东西像:

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

现在,您要为...填写什么? 你有一个值fxa:: fxa ,你有一个 function f:: x -> y ,你需要生成一个fya类型的值。 老实说,我不知道该怎么做。 毕竟,typ fxa值是多少? 我们不知道f是什么?!


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

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

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

f是任意的时,我们被困在这里,但现在我们知道kxa:: K xa 请记住,生成类型K xa的值的唯一方法是使用构造函数K 因此,这个值kxa必须是使用该构造函数创建的,因此我们可以将其拆分为: kxa ⩳ K x' where x':: x 让我们先输入 go 并将其放入我们的模式中:

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

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

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

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

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

K ya类型的K构造函数采用类型y的值,因此我们需要在此处提供类型y的值。 我们有一个值x':: x和一个 function f:: x -> y 将第一个插入第二个可以得到我们需要的值:

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

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

DDub在他们的回答中写道:

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

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

那么,什么是Bifunctor 它们正如它们的名字所说: * -> * -> *类型(我们有时也称其为双函子,但它们不是一回事),它允许映射到它的两个 arguments(来自源代码的片段):

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 #-}

所以,这就是我 go 对此的看法:

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

说到你老师的代码,这是一个非常好的想法,但由于K是一个Bifunctor ,所以范围更窄:

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

合法的:

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

正如上面链接中所说,只写下bimap ,这是我们唯一需要担心的法则。

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

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”函数和“f”函数使用f ,这纯粹是对学生的滥用

只有当您厌倦了所有的Mk并且觉得它们对您没有任何帮助时,您才可以像专家通常做的那样安全、轻松地扔掉它们。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM