[英]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 f
和a
在某种程度上是“常量”,我们为类型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
。
如果您想知道我是如何得出构造函数K
和Flip
的类型签名的,它实际上并不神秘,您可以通过在 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
的最后一个类型上。 akaf
和a
在某种程度上是“常量”,我们为类型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
,你有一个 functionf:: 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.