,("юля","853–24-92")
,("юля","555–21-11")
]
Если мы просто вызовем fromList
, чтобы поместить всё это в отображение, то потеряем массу номеров! Вместо этого воспользуемся другой функцией из модуля Data.Map
, а именно функцией fromListWith
. Эта функция действует почти как fromList
, но вместо отбрасывания повторяющихся ключей вызывает переданную ей функцию, которая и решает, что делать.
phoneBookToMap :: (Ord k) => [(k, String)] -> Map.Map k String
phoneBookToMap xs = Map.fromListWith add xs
where add number1 number2 = number1 ++ ", " ++ number2
Если функция fromListWith
обнаруживает, что ключ уже существует, она вызывает переданную ей функцию, которая соединяет оба значения в одно, а затем заменяет старое значение на новое, полученное от соединяющей функции:
ghci> Map.lookup "катя" $ phoneBookToMap phoneBook
"827–91-62, 943–29-29, 493–29-28"
ghci> Map.lookup "надя" $ phoneBookToMap phoneBook
"939-82-82"
ghci> Map.lookup "оля" $ phoneBookToMap phoneBook
"342-24-92, 555-29-38"
А ещё можно было бы сделать все значения в ассоциативном списке одноэлементными списками, а потом скомбинировать их операцией ++
, например:
phoneBookToMap :: (Ord k) => [(k, a)] -> Map.Map k [a]
phoneBookToMap xs = Map.fromListWith (++) $ map (\(k,v) -> (k, [v])) xs
Проверим в GHCi:
ghci> Map.lookup "катя" $ phoneBookToMap phoneBook
["827–91-62","943–29-29","493–29-28"]
Превосходно!
Ещё примеры. Допустим, мы делаем отображение из ассоциативного списка чисел и при обнаружении повторяющегося ключа хотим, чтобы сохранилось наибольшее значение. Это можно сделать так:
ghci> Map.fromListWith max [(2,3),(2,100),(3,29),(3,11),(4,22),(4,15)]
fromList [(2,100),(3,29),(4,22)]
Или хотим, чтобы значения с повторяющимися ключами складывались:
ghci> Map.fromListWith (+) [(2,3),(2,100),(3,29),(3,11),(4,22),(4,15)]
fromList [(2,103),(3,40),(4,37)]
Ну что ж, модуль Data.Map
, да и другие модули из стандартной библиотеки языка Haskell довольно неплохи. Далее посмотрим, как написать свой собственный модуль.
Написание собственных модулей
Практически все языки программирования позволяют разделять код на несколько файлов, и Haskell – не исключение. При написании программ очень удобно помещать функции и типы, служащие схожим целям, в отдельный модуль. Таким образом, можно будет повторно использовать эти функции в других программах, просто импортировав нужный модуль.
Мы говорим, что модуль экспортирует функции. Это значит, что когда мы его импортируем, то можем использовать экспортируемые им функции. Модуль может определить функции для внутреннего использования, но извне модуля мы видим только те, которые он экспортирует.
Модуль Geometry
Давайте разберём процесс создания модулей на простом примере. Создадим модуль, который содержит функции для вычисления объёма и площади поверхности нескольких геометрических фигур. И начнём с создания файла
В начале модуля указывается его имя. Если мы назвали файл Geometry
. Затем следует перечислить экспортируемые функции, после чего мы можем писать сами функции:
module Geometry
( sphereVolume
, sphereArea
, cubeVolume
, cubeArea
, cuboidArea
, cuboidVolume
) where
Как видите, мы будем вычислять площади и объёмы для сфер (sphere
), кубов (cube
) и прямоугольных параллелепипедов (cuboid
). Сфера – это круглая штука наподобие грейпфрута, куб – квадратная штука, похожая на кубик Рубика, а прямоугольный параллелепипед – точь-в-точь пачка сигарет. (Дети, курить вредно!)
Продолжим и определим наши функции:
module Geometry
( sphereVolume , sphereArea
, cubeVolume
, cubeArea
, cuboidArea
, cuboidVolume
) where
sphereVolume :: Float –> Float
sphereVolume radius = (4.0 / 3.0) * pi * (radius 3)
sphereArea :: Float –> Float