我正在学习haskell并尝试理解泛化在函数式编程中的含义。
请考虑以下代码段:
twiceLifted :: (Functor f1, Functor f) =>
f (f1 a) -> f (f1 Char)
twiceLifted = (fmap . fmap) replaceWithP
twiceLifted' :: [Maybe String] -> [Maybe Char]
twiceLifted' = twiceLifted
如您所见,第一个函数没有具体类型,必须实现仿函数。
第二个功能看起来更有趣。它重用了第一个函数,但它有一个具体的类型 因此,第一个功能为第二个功能奠定了基础 这是函数式编程的全部概括,重用已经存在并构建于其上的东西吗?
答案 0 :(得分:3)
概括是指使概念限制性降低。考虑一下我们可能拥有的函数,它通过[Int]
并向每个元素添加5
:
mapAdd5 :: [Int] -> [Int]
mapAdd5 [] = []
mapAdd5 (x:xs) = (x + 5) : mapAdd5 xs
好的,如果我们希望能够执行更多而不仅仅是向每个元素添加5
,该怎么办?如果我们想要查看整个列表,而是将每个元素乘以2
?
mapMult2 :: [Int] -> [Int]
mapMult2 [] = []
mapMult2 (x:xs) = (x * 2) : mapMult2 xs
您可能注意到的一点是,我们实质上是将Int -> Int
类型的函数应用于每个元素。好?
mapInts :: (Int -> Int) -> [Int] -> [Int]
mapInts f [] = []
mapInts f (x:xs) = (f x) : mapInts f xs
很好,我们现在已经将内部功能概括掉了。但是,如果我们希望能够将此概念应用于Double -> Double
甚至String -> String
,该怎么办?
mapMore :: (a -> a) -> [a] -> [a]
mapMore f [] = []
mapMore f (x:xs) = (f x) : mapMore f xs
如果我们不将函数限制为a -> a
类型,该怎么办?也就是说,我们当前强制输出类型与输入类型相同。如果它们可能不同,如a -> b
,该怎么办?虽然可能非常好a
与b
相同,但与a -> a
不同,我们并不强迫它如此:
map :: (a -> b) -> [a] -> [b]
map f [] = []
map f (x:xs) = (f x) : map f xs
现在我们已经从mapAdd5
推广到map
。
<强>加成强>
如果我们不想限制自己使用列表怎么办?事实证明,我们可以将列表抽象/概括为Functor
的实例,这些实例只是使函数能够应用于包装/保存其他项的类型。想一想,这正是列表的作用!
fmap :: Functor f => (a -> b) -> f a -> f b
fmap
如何定义?其定义将根据您拥有的容器f
的类型而有所不同。但是,对于列表(也称为[]
),我们将其定义为here:
instance Functor [] where
fmap = map