ТОП 10:

Композиция функций с несколькими параметрами



 
 

Ну а как насчёт функций, которые принимают несколько парамет- ров? Если мы хотим использовать их в композиции, обычно мы час- тично применяем их до тех пор, пока не получим функцию, прини- мающую только один параметр. Запись

sum (replicate 5 (max 6.7 8.9)) может быть преобразована так: (sum . replicate 5) (max 6.7 8.9)

или так :


 

 
 

sum . replicate 5 $ max 6.7 8.9

Функция replicate 5 применяется к результату вычисления max

6.7 8.9, после чего элементы полученного списка суммируются. Об- ратите внимание, что функция replicate частично применена так, чтобы у неё остался только один параметр, так что теперь результат max 6.7 8.9 передаётся на вход replicate 5; новым результатом оказы- вается список чисел, который потом передаётся функции sum.

Если вы хотите переписать выражение с кучей скобок, исполь- зуя функциональную композицию, можно сначала записать самую внутреннюю функцию с её параметрами, затем поставить перед ней знак $, а после этого пристраивать вызовы всех других функ- ций, записывая их без последнего параметра и разделяя точками.

 
 

Например, выражение

replicate 2 (product (map (*3) (zipWith max [1,2] [4,5])))

 
 

можно переписать так:

replicate 2 . product . map (*3) $ zipWith max [1,2] [4,5]

 
 

Как из одного выражения получилось другое? Ну, во-первых, мы посмотрели на самую правую функцию и её параметры как раз перед группой закрывающихся скобок. Это функция zipWith max [1,2] [4,5]. Так её и запишем:

zipWith max [1,2] [4,5]

 
 

Затем смотрим на функцию, которая применяется к zipWith max [1,2] [4,5], это map (*3). Поэтому мы ставим между ней и тем, что было раньше, знак $:

map (*3) $ zipWith max [1,2] [4,5]

 
 

Теперь начинаются композиции. Проверяем, какая функция применяется ко всему этому, и присоединяем её к map (*3):

product . map (*3) $ zipWith max [1,2] [4,5]

 
 

Наконец, дописываем функцию replicate 2 и получаем оконча- тельное выражение:

replicate 2 . product . map (*3) $ zipWith max [1,2] [4,5]


 

Если выражение заканчивалось на три закрывающие скобки, велики шансы, что у вас получится два оператора композиции.

 

Бесточечная нотация

 
 

Композиция функций часто используется и для так называемого бесточечного стиля записи функций. Возьмём, для примера, функ- цию, которую мы написали ранее:

sum' :: (Num a) => [a] –> a sum' xs = foldl (+) 0 xs

 
 

Образец xs представлен дважды с правой стороны. Из–за карри- рования мы можем пропустить образец xs с обеих сторон, так как foldl (+) 0 создаёт функцию, которая принимает на вход список. Если мы запишем эту функцию как sum' = foldl (+) 0, такая запись будет называться бесточечной. А как записать следующее выражение в бесточечном стиле?

fn x = ceiling (negate (tan (cos (max 50 x))))

 
 

Мы не можем просто избавиться от образца x с обеих правых сторон выражения. Образец x в теле функции заключен в скобки. Выражение cos (max 50) не будет иметь никакого смысла. Вы не мо- жете взять косинус от функции! Всё, что мы можем сделать, – это выразить функцию fn в виде композиции функций.

fn = ceiling . negate . tan . cos . max 50

Отлично! Во многих случаях бесточечная запись легче читает- ся и более лаконична; она заставляет думать о функциях, о том, как их соединение порождает результат, а не о данных и способе их передачи. Можно взять простые функции и использовать компо- зицию как «клей» для создания более сложных. Однако во многих случаях написание функций в бесточечном стиле может делать код менее «читабельным», особенно если функция слишком сложна. Вот почему я не рекомендую создавать длинные цепочки функций, хотя меня частенько обвиняли в пристрастии к композиции. Пред- почитаемый стиль – использование выражения let для присвое- ния меток промежуточным результатам или разбиение проблемы на подпроблемы и их совмещение таким образом, чтобы функции


 

имели смысл для того, кто будет их читать, а не представляли собой огромную цепочку композиций.

 
 

Ранее в этой главе мы решали задачу, в которой требовалось найти сумму всех нечётных квадратов меньших 10 000. Вот как бу- дет выглядеть решение, если мы поместим его в функцию:

oddSquareSum :: Integer

 
 

oddSquareSum = sum (takeWhile (<10000) (filter odd (map ( 2) [1..])))

Со знанием композиции функций этот код можно переписать так:

oddSquareSum :: Integer

 
 

oddSquareSum = sum . takeWhile (<10000) . filter odd $ map ( 2) [1..]

Всё это на первый взгляд может показаться странным, но вы быстро привыкнете. В подобных записях меньше визуального

«шума», поскольку мы убрали все скобки. При чтении такого кода можно сразу сказать, что filter odd применяется к результату map ( 2) [1..], что затем применяется takeWhile (<10000), а функция sum суммирует всё, что получилось в результате.


 

МОДУЛИ

 

В языке Haskell модуль – это набор взаимосвязанных функ- ций, типов и классов типов. Программа на Haskell – это набор модулей; главный мо- дуль подгружает все осталь- ные и использует функции, определённые в них, чтобы что-либо сделать. Разбиение кода на несколько модулей удобно по многим причинам.

Если модуль достаточно общий, экспортируемые им функции мо- гут быть использованы во множестве программ. Если ваш код раз- делён на несколько самостоятельных модулей, не очень зависящих один от другого (мы говорим, что они слабо связаны), модули могут многократно использоваться в разных проектах. Это отчасти об- легчает непростую задачу написания кода, разбивая его на несколь- ко частей, каждая из которых имеет некоторое назначение.

Стандартная библиотека языка Haskell разбита на модули, каж- дый из которых содержит взаимосвязанные функции и типы, слу- жащие некоторой общей цели. Есть модуль для работы со списками, модуль для параллельного программирования, модуль для работы с комплексными числами и т. д. Все функции, типы и классы типов, с которыми мы имели дело до сих пор, были частью стандартного


ИМПОРТ МОДУЛЕЙ 123

 

модуля Prelude – он импортируется по умолчанию. В этой главе мы познакомимся с несколькими полезными модулями и их функция- ми. Но для начала посмотрим, как импортировать модули.

 

Импорт модулей

Синтаксис для импорта модулей в программах на языке Haskell – import ModuleName. Импортировать модули надо прежде, чем вы при- ступите к определению функций, поэтому обычно импорт делается в начале файла. Конечно же, одна программа может импортиро- вать несколько модулей. Для этого вынесите каждый оператор import в отдельную строку.

 
 

Давайте импортируем модуль Data.List, который содержит мас- су функций для работы со списками, и используем экспортируемую им функцию для того, чтобы написать свою – вычисляющую, сколь- ко уникальных элементов содержит список.

import Data.List

 
 

numUniques :: (Eq a) => [a] –> Int numUniques = length . nub

Когда выполняется инструкция import Data.List, все функ- ции, экспортируемые модулем Data.List, становятся доступными в глобальном пространстве имён. Это означает, что вы можете вы- зывать их из любого места программы. Функция nub определена в модуле Data.List; она принимает список и возвращает список, из которого удалены дубликаты элементов исходного списка. Компо- зиция функций length и nub создаёт функцию, которая эквивалент- на \xs –> length (nub xs).

ПРИМЕЧАНИЕ.Чтобы найти нужные функции и уточнить, где они определены, воспользуйтесь сервисом Hoogle, который досту- пен по адресу http://www.haskell.org/hoogle/. Это поистине уди- вительный поисковый механизм для языка Haskell, который поз- воляет вести поиск по имени функции, по имени модуля и даже по сигнатуре.

В интерпретаторе GHCi вы также можете подключить функ- ции из модулей к глобальному пространству имён. Если вы работа- ете в GHCi и хотите вызывать функции, экспортируемые модулем Data.List, напишите следующее:


 

 
 

ghci> :m + Data.List

Если требуется подгрузить программные сущности из несколь- ких модулей, не надо вызывать команду :m + несколько раз, так как можно загрузить ряд модулей одновременно:

 
 

ghci> :m + Data.List Data.Map Data.Set

Кроме того, если вы загрузили скрипт, который импортирует модули, то не нужно использовать команду :m +, чтобы получить к ним доступ.

 
 

Если вам необходимо всего несколько функций из модуля, вы можете выборочно импортировать только эти функции. Если бы вам были нужны только функции nub и sort из модуля Data.List, им- порт выглядел бы так:

import Data.List (nub, sort)

 
 

Также вы можете осуществить импорт всех функций из модуля за исключением некоторых. Это бывает полезно, когда несколько модулей экспортируют функции с одинаковыми именами, и вы хо- тите избавиться от ненужных повторов. Предположим, у вас уже есть функция с именем nub и вы хотите импортировать все функ- ции из модуля Data.List, кроме nub, определённой в нём:

import Data.List hiding (nub)

 
 

Другой способ разрешения конфликтов имён – квалифици- рованный импорт. Модуль Data.Map, который содержит структуру данных для поиска значения по ключу, экспортирует несколько функций с теми же именами, что и модуль Prelude, например filter и null. Если мы импортируем модуль Data.Map и вызовем функцию filter, язык Haskell не будет знать, какую функцию использовать. Вот как можно обойти такую ситуацию:

import qualified Data.Map

Если после такого импорта нам понадобится функция filter из модуля Data.Map; мы должны вызывать её как Data.Map.filter – просто идентификатор filter ссылается на обычную функцию из модуля Prelude, которую мы все знаем и любим. Но печатать строку Data.Map перед именем каждой функции может и поднадоесть! Вот


 

 
 

почему желательно переименовать модуль при импорте во что-ни- будь более короткое:

import qualified Data.Map as M

Теперь, чтобы сослаться на функцию из Data.Map, мы вызываем её как M.filter.

Как вы видите, символ . используется для обращения к функ- циям, импортированным из модулей с указанием квалификатора, например: M.filter. Мы также помним, что он используется для обозначения композиции функций. Как Haskell узнаёт, что мы имеем в виду? Если мы помещаем символ . между квалифициро- ванным именем модуля и функцией без пробелов – это обраще- ние к функции из модуля; во всех остальных случаях – композиция функций.

ПРИМЕЧАНИЕ.Отличный способ узнать Haskell изнутри – про- смотреть документацию к стандартной библиотеке и исследо- вать все стандартные модули и их функции. Также можно изучить исходные тексты всех модулей. Чтение исходных текстов некото- рых модулей – отличный способ освоить язык и прочувствовать его особенности1.







Последнее изменение этой страницы: 2017-02-17; Нарушение авторского права страницы

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 34.239.172.52 (0.007 с.)