functor
Functor 类型类只需要一个定义: fmap
fmap 提供了一个适配器,如图 所示。请注意,我们使用的是 <$>,它是 fmap 的同义词(除了它是二元运算符而不是函数)。
学习 Functor
是理解函数式编程中的一个重要概念,尤其是在像 Haskell 这样的语言中。Functor
提供了一种抽象的方式来操作包含在上下文中的数据。它可以类比为一种容器或包装器,允许你对容器内的数据应用函数,而不必关心数据的具体结构。
Functor 的定义
Functor
是一个类型类,定义了一个 fmap
函数,它接受一个函数并将其应用到 Functor
内部的值上。
在 Haskell 中,Functor
的类型类定义如下:
class Functor f where
fmap :: (a -> b) -> f a -> f b
f
是一个容器或上下文,它包裹了一些值。比如列表、Maybe
、Either
都可以看作是Functor
。fmap
接受一个函数(a -> b)
,以及一个Functor
(f a
)并将该函数应用到Functor
内部的值,从而返回另一个Functor
(f b
)。
直观理解
可以把 Functor
想象成一种包装器。fmap
是一种工具,它帮助你打开包装,对里面的内容应用函数,然后再重新封装起来。
例如,在列表 []
上,fmap
就是把函数应用到列表的每一个元素上:
fmap (*2) [1, 2, 3]
-- 结果是 [2, 4, 6]
在 Maybe
上,fmap
允许你对一个可能包含值的结构进行操作:
fmap (*2) (Just 5)
-- 结果是 Just 10
fmap (*2) Nothing
-- 结果是 Nothing
Functor 的意义
Functor 抽象的核心思想是 在不改变结构的情况下操作其中的值。具体来说,fmap
会保留容器(或上下文)的结构,而只改变其中的内容。
具体例子
-
列表
[]
列表是一个最简单的
Functor
,fmap
就是把函数作用到列表的每个元素:fmap (+1) [1, 2, 3] -- [2, 3, 4]
-
Maybe
Maybe
是一个包含Just
或Nothing
的类型,fmap
会对Just
中的值应用函数,而Nothing
则保持不变:fmap (*2) (Just 5) -- Just 10 fmap (*2) Nothing -- Nothing
-
Either
Either
类型可以用来表示错误或成功的结果。Either
的Functor
实现只对Right
中的值应用函数,Left
中的值表示错误,保持不变:fmap (+1) (Right 10) -- Right 11 fmap (+1) (Left "Error") -- Left "Error"
Functor 定律
为了使 fmap
在不同类型中行为一致,Functor
必须遵循两个定律:
-
同一性定律(Identity Law)
对任意的
x
,fmap id x == x
。即应用id
(恒等函数)不会改变Functor
的内容。fmap id [1, 2, 3] == [1, 2, 3]
-
组合性定律(Composition Law)
对任意函数
f
和g
,fmap (f . g) == fmap f . fmap g
。即将组合后的函数应用在Functor
上,等价于先分别应用每个函数再组合结果。fmap ((+1) . (*2)) [1, 2, 3] == fmap (+1) (fmap (*2) [1, 2, 3]) -- 结果都为 [3, 5, 7]
为什么 Functor 很重要?
- 抽象化处理:通过
Functor
,你可以对不同的数据结构(列表、Maybe
、Either
等)应用相同的操作,而无需关心它们的具体实现细节。 - 提升代码的可组合性:
fmap
提供了一种标准化的方式将函数作用于各种容器中的值,这让代码更加简洁和可组合。 - Functor 是更高层次抽象的基础:
Functor
是Applicative
和Monad
等更复杂抽象的基础,因此理解Functor
是掌握 Haskell 高阶特性的第一步。
总结
Functor
是一个抽象概念,用于描述如何对包含值的上下文(如列表、Maybe
等)应用函数。fmap
是核心操作,它允许你对 Functor 内部的值进行操作,而不改变外部的结构。Functor
遵循两个定律:同一性定律和组合性定律,确保操作的一致性。
通过学习 Functor
,你可以在 Haskell 中更自然地理解和使用函数式编程中的抽象和组合理念。
Functor 类型类允许您将普通函数应用于容器(例如 List )或上下文(例如 IO 或 Maybe )内的值。如果您有一个函数 Int -> Double 和一个值 Maybe Int ,则可以使用 Functor 的 fmap (或 <$> 运算符)将 Int -> Double 函数应用于 Maybe Int 值,从而产生 Maybe Double 值。函子非常有用,因为它们允许您将单个函数与属于函子类型类的任何类型重用。 [Int]、 Maybe Int 和 IO Int 都可以使用相同的核心函数。