使数据类型成为 Functor 的实例以映射到参数类型的字段
Making a datatype an instance of Functor to map on a field which is of parametric type
在 上跟进关于 学习 Haskell 的好事。
作者在Chapter 8末尾声明了这个数据类型(略作简化,希望没问题)
data Barry t k p = BarryV p (t k) deriving (Show)
然后使它成为 Functor
的一个实例
instance Functor (Barry a b) where
fmap f (BarryV x y) = BarryV (f x) y
然后总结
There we go! We just mapped the f
over the first field.
是的。首先。所以我的问题是:如果我想映射第二个字段怎么办?
实际上第二个字段不能是Int
、Char
、Float
等简单的类型;它必须是可以作为应用于具体类型的类型构造函数获得的类型(斜体文本与“参数类型”相同,对吧? 不是,是参数化类型),如Just 3
、Right "hello"
、"hello"
、[1..10]
、很快;因此映射到 第二个字段 和映射到 第二个字段的内容 似乎不同。
我真的很困惑,但我想最后一段已经足够努力了。
最简单的方法是将您的类型创建为具体类型的函数:
mymap :: (t1 k1 -> t2 k2) -> Barry t1 k1 p -> Barry t2 k2 p
mymap f (BarryV x y) = BarryV x (f y)
如果你真的想用现有的类型类来做,你可能可以通过包装类型(如 Product
或 Compose
将足够多的转换链接在一起,以获得类似 Bifunctor
的东西弹出,但我认为在这种情况下不值得这样做。
Functor
类型 class 过于笼统,无法在第二个字段的类型 t k
上应用映射,但 可以 在第二个字段的类型 k
内应用映射。因此,使用您问题中的术语,我们不能使用 Functor
来映射 t k
类型的第二个字段,但我们可以使用它来映射 k
类型的内容在类型为 t k
的第二个字段中(前提是 t
是一种允许映射其内容的结构)。
关于尝试使用 Functor
映射类型 t k
,问题在于它允许违反 Barry
类型定义的转换。以下函数:
censor :: (Functor f) => f a -> f ()
censor = (() <$)
应该适用于任何仿函数实例,将目标类型 a
的字段替换为单元 ()
。例如:
> censor (Just 5)
Just ()
> censor [1..5]
[(),(),(),(),()]
如果 Barry
是其第二个字段的 t k
类型的函子,那么我将能够获取有效的 Barry
值:
> let myBarry = BarryV 10 "hello" :: Barry [] Char Int
并对其应用 censor
以审查其第二个字段:
> censor myBarry
BarryV 10 ()
但是这个值的类型是什么?对于某些 t
和 k
显然 Barry t k Int
这样 t k = ()
,但这是不可能的。无法将 "split" 类型 ()
分为两部分 t
和 k
。因此,BarryV 10 ()
不是有效 Barry
类型的值,它的存在意味着我们在程序中构造了一个无效的 Barry
类型。
另一方面,我们可以在k
参数中为Barry
创建一个Functor
实例。我们不能直接这样做,因为 Haskell 语法只允许我们为以其 "last" 参数为目标的类型表达式定义 Functor
个实例。所以 Barry t k p
可以通过为 Barry t k
定义一个 Functor
实例在最后一个参数 p
中成为 Functor
,但它不能成为 Functor
在中间参数k
.
如果我们有一个参数顺序不同的变体:
data Larry p t k = LarryV p (t k) deriving (Show)
然后我们可以定义 Functor
实例:
instance Functor (Larry p t) where
fmap f (LarryV p tk) = LarryV p (fmap f tk)
这给出了一个类型错误,说 t
没有 Functor
实例,但是如果我们限制自己只在有 Functor t
时定义这个实例,它工作正常:
instance Functor t => Functor (Larry p t) where
fmap f (LarryV p tk) = LarryV p (fmap f tk)
现在,只要 t
是一个 Functor
,我们就有 Larry p t
个 Functor
。例如:
> let myLarry = LarryV 10 "hello"
> :t myLarry
myLarry :: Num p => Larry p [] Char
> import Data.Char
> fmap toUpper myLarry
LarryV 10 "HELLO"
这是可行的,因为 t = []
是一个 Functor
,所以我们得到了我们需要的实例。
请注意,在实际代码中,不是引入新类型 Larry
,而是在 "middle" 参数中定义 Functor
实例的标准方法是使用 newtype
包装器,类似于:
newtype Barry' p t k = Barry' (Barry t k p)
instance Functor t => Functor (Barry' p t) where
fmap f (Barry' (BarryV p tk)) = Barry' (BarryV p (fmap f tk))
在
作者在Chapter 8末尾声明了这个数据类型(略作简化,希望没问题)
data Barry t k p = BarryV p (t k) deriving (Show)
然后使它成为 Functor
instance Functor (Barry a b) where
fmap f (BarryV x y) = BarryV (f x) y
然后总结
There we go! We just mapped the
f
over the first field.
是的。首先。所以我的问题是:如果我想映射第二个字段怎么办?
实际上第二个字段不能是Int
、Char
、Float
等简单的类型;它必须是可以作为应用于具体类型的类型构造函数获得的类型(斜体文本与“参数类型”相同,对吧? 不是,是参数化类型),如Just 3
、Right "hello"
、"hello"
、[1..10]
、很快;因此映射到 第二个字段 和映射到 第二个字段的内容 似乎不同。
我真的很困惑,但我想最后一段已经足够努力了。
最简单的方法是将您的类型创建为具体类型的函数:
mymap :: (t1 k1 -> t2 k2) -> Barry t1 k1 p -> Barry t2 k2 p
mymap f (BarryV x y) = BarryV x (f y)
如果你真的想用现有的类型类来做,你可能可以通过包装类型(如 Product
或 Compose
将足够多的转换链接在一起,以获得类似 Bifunctor
的东西弹出,但我认为在这种情况下不值得这样做。
Functor
类型 class 过于笼统,无法在第二个字段的类型 t k
上应用映射,但 可以 在第二个字段的类型 k
内应用映射。因此,使用您问题中的术语,我们不能使用 Functor
来映射 t k
类型的第二个字段,但我们可以使用它来映射 k
类型的内容在类型为 t k
的第二个字段中(前提是 t
是一种允许映射其内容的结构)。
关于尝试使用 Functor
映射类型 t k
,问题在于它允许违反 Barry
类型定义的转换。以下函数:
censor :: (Functor f) => f a -> f ()
censor = (() <$)
应该适用于任何仿函数实例,将目标类型 a
的字段替换为单元 ()
。例如:
> censor (Just 5)
Just ()
> censor [1..5]
[(),(),(),(),()]
如果 Barry
是其第二个字段的 t k
类型的函子,那么我将能够获取有效的 Barry
值:
> let myBarry = BarryV 10 "hello" :: Barry [] Char Int
并对其应用 censor
以审查其第二个字段:
> censor myBarry
BarryV 10 ()
但是这个值的类型是什么?对于某些 t
和 k
显然 Barry t k Int
这样 t k = ()
,但这是不可能的。无法将 "split" 类型 ()
分为两部分 t
和 k
。因此,BarryV 10 ()
不是有效 Barry
类型的值,它的存在意味着我们在程序中构造了一个无效的 Barry
类型。
另一方面,我们可以在k
参数中为Barry
创建一个Functor
实例。我们不能直接这样做,因为 Haskell 语法只允许我们为以其 "last" 参数为目标的类型表达式定义 Functor
个实例。所以 Barry t k p
可以通过为 Barry t k
定义一个 Functor
实例在最后一个参数 p
中成为 Functor
,但它不能成为 Functor
在中间参数k
.
如果我们有一个参数顺序不同的变体:
data Larry p t k = LarryV p (t k) deriving (Show)
然后我们可以定义 Functor
实例:
instance Functor (Larry p t) where
fmap f (LarryV p tk) = LarryV p (fmap f tk)
这给出了一个类型错误,说 t
没有 Functor
实例,但是如果我们限制自己只在有 Functor t
时定义这个实例,它工作正常:
instance Functor t => Functor (Larry p t) where
fmap f (LarryV p tk) = LarryV p (fmap f tk)
现在,只要 t
是一个 Functor
,我们就有 Larry p t
个 Functor
。例如:
> let myLarry = LarryV 10 "hello"
> :t myLarry
myLarry :: Num p => Larry p [] Char
> import Data.Char
> fmap toUpper myLarry
LarryV 10 "HELLO"
这是可行的,因为 t = []
是一个 Functor
,所以我们得到了我们需要的实例。
请注意,在实际代码中,不是引入新类型 Larry
,而是在 "middle" 参数中定义 Functor
实例的标准方法是使用 newtype
包装器,类似于:
newtype Barry' p t k = Barry' (Barry t k p)
instance Functor t => Functor (Barry' p t) where
fmap f (Barry' (BarryV p tk)) = Barry' (BarryV p (fmap f tk))