与其开始尝试以某种方式适应 map,不如考虑如何简化和概括当前功能。从此开始:
dotProduct :: [(Float, Integer)] -> Float -> [(Float, Integer)]
dotProduct [] _ = []
dotProduct [(x,y)] z = [(x*z,y)]
dotProduct ((x,y):xys) z = (x*z,y):dotProduct (xys) z
首先,我们将使用(:) 构造函数重写第二种情况:
dotProduct ((x,y):[]) z = (x*z,y):[]
使用第一种情况扩展结果中的[]:
dotProduct ((x,y):[]) z = (x*z,y):dotProduct [] z
比较这与第三种情况,我们可以看到它们是相同的,除了当xys 是[] 时专用。因此,我们可以简单地完全消除第二种情况:
dotProduct :: [(Float, Integer)] -> Float -> [(Float, Integer)]
dotProduct [] _ = []
dotProduct ((x,y):xys) z = (x*z,y):dotProduct (xys) z
接下来,泛化函数。首先,我们重命名它,并让dotProduct 调用它:
generalized :: [(Float, Integer)] -> Float -> [(Float, Integer)]
generalized [] _ = []
generalized ((x,y):xys) z = (x*z,y):generalized (xys) z
dotProduct :: [(Float, Integer)] -> Float -> [(Float, Integer)]
dotProduct xs z = generalized xs z
首先,我们通过运算对其进行参数化,专门用于dotProduct的乘法:
generalized :: (Float -> Float -> Float) -> [(Float, Integer)] -> Float -> [(Float, Integer)]
generalized _ [] _ = []
generalized f ((x,y):xys) z = (f x z,y):generalized f (xys) z
dotProduct :: [(Float, Integer)] -> Float -> [(Float, Integer)]
dotProduct xs z = generalized (*) xs z
接下来,我们可以观察到两件事:generalized 不再直接依赖于算术,因此它可以在任何类型上工作;唯一一次使用z 是作为f 的第二个参数,所以我们可以将它们组合成一个函数参数:
generalized :: (a -> b) -> [(a, c)] -> [(b, c)]
generalized _ [] = []
generalized f ((x,y):xys) = (f x, y):generalized f (xys)
dotProduct :: [(Float, Integer)] -> Float -> [(Float, Integer)]
dotProduct xs z = generalized (* z) xs
现在,我们注意到f 仅用于元组的第一个元素。这听起来很有用,所以我们将其提取为一个单独的函数:
generalized :: (a -> b) -> [(a, c)] -> [(b, c)]
generalized _ [] = []
generalized f (xy:xys) = onFirst f xy:generalized f (xys)
onFirst :: (a -> b) -> (a, c) -> (b, c)
onFirst f (x, y) = (f x, y)
dotProduct :: [(Float, Integer)] -> Float -> [(Float, Integer)]
dotProduct xs z = generalized (* z) xs
现在我们再次观察到,在generalized 中,f 仅与onFirst 一起使用,因此我们再次将它们组合成一个函数参数:
generalized :: ((a, c) -> (b, c)) -> [(a, c)] -> [(b, c)]
generalized _ [] = []
generalized f (xy:xys) = f xy:generalized f (xys)
dotProduct :: [(Float, Integer)] -> Float -> [(Float, Integer)]
dotProduct xs z = generalized (onFirst (* z)) xs
我们再次观察到generalized 不再依赖于包含元组的列表,因此我们让它适用于任何类型:
generalized :: (a -> b) -> [a] -> [b]
generalized _ [] = []
generalized f (x:xs) = f x : generalized f xs
现在,将generalized 的代码与以下代码进行比较:
map :: (a -> b) -> [a] -> [b]
map _ [] = []
map f (x:xs) = f x : map f xs
事实证明,还存在一个稍微更通用的 onFirst 版本,所以我们将把它和 generalized 替换为它们的标准库等价物:
import Control.Arrow (first)
dotProduct :: [(Float, Integer)] -> Float -> [(Float, Integer)]
dotProduct xs z = map (first (* z)) xs