Главная » C# » Суть функционального программирования

0

Для полного понимания функционального программирования необходимо разбаться в его четырех основных характеристиках (http://en.wikipedia.org/wiki/ Functional_programming).

•   Функции высшего порядка. Разрешают определять функции в виде аргументов и возвращать их в виде результатов. Это позволяет выполнять с функциями опацию карринга, как будет объяснено далее.

•   Чистые функции. Концепция чистой функции связана с программированием, свободным от побочных эффектов. В общем, она сводится к тому, что чистая функция с чистыми аргументами не будет склонна к различным результатам при разных  порядках  вычисления.  Скажем,  что  мы  вызвали  функцию X один  раз, а потом вызвали ее с теми же аргументами опять. Второй вызов функции  не должен зависеть от первого. Результаты, выдаваемые функцией X, всегда одаковые, независимо от того, сколько раз она вызывается.

•   Рекурсия. Операция цикла выполняется с помощью рекурсии, когда функция осуществляет вычисления и для вычисления следующего элемента вызывает су себя.

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

Рассмотрим каждую из этих характеристик функционального языка программирания в контексте языка С#.

Функции высшего порядка

Как было упомянуто ранее, функции высшего порядка позволяют выполнение опации   карринга1      (currying).   Брайан   Бекман   (Brian   Beckman)   в   своем   блоге

‘ Менее распространенным переводом этого термина на русский язык является каррирование. — Пер.

(http://weblogs.asp.net/brianbec/archive/2OO6/O6/Ol/Lambdas_2COO_-Closures_2COO_

-Currying_2C00_-and-All-That.aspx)  дает  следующее  объяснение   карринга:

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

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

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

Теоретический пример

Карринг для функций был возможен  в  более  ранних  версиях  языка  С#,  но  код для его реализации был довольно сложным. С появлением в языке лямбда-выражений написание функций с каррингом стало довольно легкой задачей. Вкратце, проиодная функция (curried function) представляет собой функцию с дополнительной возможностью, о которой клиент  функции не знает. Такое оснащение функции долнительной    возможностью   еще    называется   декорированием   функции   (decorating a  function).

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

(surrounding, core) => surrounding + "_" + core + "_" + surrounding

//(окружающий, внутренний) => окружающий +"_" + внутренний + "_" +

// окружающий

Клиент, вызывающий данное лямбда-выражение, использовал бы код, подобный следующему, при условии, что  лямбда-выражение было присвоено переменному выражению:

expression("+++", "hello");

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

ПРИМЕЧАНИЕ

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

Первый параметр можно преобразовать в переменную, к которой можно обращатя из разных участков кода, как показано в следующем коде:

string surrounding = "+++"; expression(surrounding, "hello");

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

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

surrounding => core => surrounding + " " + core + "  " + surrounding,-

С первого взгляда это лямбда-выражение может показаться устрашающим, но по сравнению со следующей версией анонимного метода оно довольно простое:

delegate (string surrounding) { return delegate (string core) {

return surrounding + " " + core + " " + surrounding;

} ;

} ;

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

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

Func<string, Func<string, string» curry =

surrounding => core => surrounding + " " + core + " " + surrounding;

Мы передаем строковое значение и  получаем лямбда-выражение, которое  принает строковое значение и возвращает строковое значение.

Производная функция вызывается таким образом:

curry("+++")("hello");

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

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

Func<string, string> curriedFunction = curry("+++"); curriedFunction("hello");

Переменная curriedFunction содержит состояние. При ее вызове метод генерирует строку, точно так, как это делалось первоначальным лямбда-выражением, показаым в начале этого раздела.

Практический пример: вычисление налога с продаж

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

interface ICalculate { double SalesTax { get; }

double Calculate(double total);

}

Данный интерфейс имеет свойство SalesTax, которое представляет текущий урень налога с продаж, и метод Calculatef), который вычисляет общую сумму пупки, включающую налог с продаж. Реализация интерфейса ICalculate может быть выполнена следующим образом:

class LocalSalesTax : ICalculate { double _salesTax;

public LocalSalesTax(double amount) {

_salesTax = amount;

}

public double SalesTax { get { return _salesTax; }

}

public double Calculate(double total) { return total + total * SalesTax;

}

}

Реализация класса LocalsaiesTax имеет параметр конструктора, который опредяет уровень налога с продаж. В следующем коде вычисляется налог с продаж для уровня налога в 16,5%:

ICalculate country = new LocalSalesTax(0.165); double amount = 100.0;

Console.WriteLine("Я купил товаров на сумму (" + amount +

"), а общая сумма с налогом будет (" + country.Calculate(amount) + ")");

Данный интерфейс, его реализация и применение демонстрируют решение прлемы посредством подхода императивного программирования.

Теперь посмотрим, как та же проблема решается применением подхода функциального программирования:

Func<double, Func<double, double» salesTax =

localSalesTax => totalBought => totalBought + totalBought * localSalesTax;

Func<double, double> country = salesTax(0.165); double amount = 100.0;

Console.WriteLine("Я купил товаров на сумму (" + amount +

") а общая сумма с налогом будет (" + country(amount) + ")");

Первым делом, обратите внимание,  насколько компактнее этот код — всего лишь 8 строчек. Функциональный код намного компактней потому, что он только решает проблему и не делает ничего лишнего. Переменная salesTax является производной функцией, которая вычисляет налог с продаж на основе суммы, переданной встрнной функции. А переменная country представляет вычисление налога с продаж для определенной страны.

Источник: Гросс  К. С# 2008:  Пер. с англ. — СПб.:  БХВ-Петербург, 2009. — 576 е.:  ил. — (Самоучитель)

По теме:

  • Комментарии