Вместо того, чтобы пытаться как-то вписать 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