вторник, 16 декабря 2014 г.

О пользе возможности частичного применения функции

Маленький пример на тему практической пользы возможности частичного применения функций.


Предположим, что имеется некоторая функция, выводящая приветствие:

printHello::(String->String)->String->String
printHello f x = "Hello, " ++ f x ++ "!"

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

Поприветствуем Васю:

ghci> printHello (\x->x) "Vasya"
"Hello, Vasya!"

А что если мы захотим поприветствовать Васю более официально: с указанием его инициалов и фамилии? Пусть у нас даже имеется специальная функция для формирования нужной строки:

shortName::String->String->String->String
shortName [] _ xs = xs
shortName (x:_) [] xs = x : ". " ++ xs
shortName (x:_) (y:_) xs = x : '.' : y : ". " ++ xs

Параметры в shortName ожидают последовательно имя, отчество и фамилию, на основании которых и будет формироваться конечная строка. Попробуем функцию в деле:

ghci> shortName "Vasiliy" "Vasilievich" "Vasiliev"
"V.V. Vasiliev"
ghci> shortName "Vasiliy" "" "Vasiliev"
"V. Vasiliev"
ghci> shortName "" "Vasilievich" "Vasiliev"
"Vasiliev"
ghci> shortName "" "" "Vasiliev"
"Vasiliev"

Заметьте, сигнатура функции shortName отличается от сигнатуры той функции, которая первым параметром передаётся в printHello. В виду этого мы могли бы снова прибегнуть к помощи лямбда-выражения и использовать shortName при вызове printHello например так:

ghci> printHello (\x->x) $ shortName "Vasiliy" "Vasilievich" "Vasiliev"
"Hello, V.V. Vasiliev!"

Однако, благодаря возможности частичного применения функций, мы спокойно можем передать shortName первым параметром с двумя аргументами вместо трёх (третий [фамилия] будет передан ей функцией printHello):

ghci> let n = shortName "Vasiliy" "Vasilievich"
ghci> printHello n "Vasiliev"
"Hello, V.V. Vasiliev!"

или так:

ghci> printHello (shortName "Vasiliy" "Vasilievich") "Vasiliev"
"Hello, V.V. Vasiliev!"

Поскольку в данном случае в shortName передаётся два параметра вместо трёх, то генерируется промежуточная функция, сигнатура которой совпадает с ожидаемой. Этой сгенерированной функции printHello передаёт в качестве параметра фамилию, что собственно и приводит к запуску функции shortName. В результате получаем ожидаемую строку.

Вывод
Возможность частичного применения функций может представлять интерес тем, что позволяет нам "прозрачно" передавать и использовать функции, имеющие сигнатуру отличную от ожидаемой. За счёт этого код получается более кратким, т.к. не нужно создавать дополнительные варианты функций под различные требующиеся сигнатуры.

Комментариев нет: