Нэш Трей - C# 2010. Ускоренный курс для профессионалов (2010) (1160865), страница 140
Текст из файла (страница 140)
Более подробные сведения о каррировэнии доступны по адресу ьгср: О ги. и1 к1рес) 1 в . о гд/ и1К1/Квррирование. 522 Гааза ) 5 рпЬ11с с1авв ВбпбегЕхаир1е ( Всапбс чо1б Мабп() ( чаг иу11вя = пен 11зо<боцвте> ( 1. О, 3. 4, 5. 4, б. 54 ); чаг пен11вс = пен 11вс<бопЬ1е> () ) // Исходное выражение. Гцпс<бопЬ1е, бопЬ1е, боцЬ1е> Ецпс = (х, у) => х + у) // Каррированная функция. чаг Еппсвоцпб = Гппс.Втпб2пб( 3.2 ); гогеасп( чаг 1геи Еп иу11вг ) ( Сопво1е.иг1ое( "(О), ", бгеи ); пен11вг.абб( гппсВоцпб(бсеи) )4 ) Сопво1е.кгтяеьфпе()) гогеасЬ( чаг Есеи тп пен11вс ) ( Сопво1е.кг1ое( "(О), ", 1яеи ); Основа этого примера — расширяющий метод Вбпб2пб<>, который выделен полужирным.
Как видите. он создает замыкание и возвращает новый делегат, принимающий только один параметр. Когда этот новый делегат вызывается, он передает свой единственный параметр исходному делегату в качестве первого параметра и предоставленную константу — в качестве второго. В примере выполняется итерация по списку пувфзс с построением нового списка в переменной пен'-бвс, при этом используется каррированная версия исходного метода для добавления константы 3. 2 к каждому элементу. Для сравнения рассмотрим другой способ каррирования, несколько отличающийся от показанного в предыдущем примере: цвтпд гузеевы цвгпд Бувсеи.11пд) нвбпд Яуяеет.со11есс1опв.оепег1с4 рпЬ11с всас1с с1авв СпггуЕхсепяуопв ( риь1гс веаефс Ропс<твгд2, ецпс<тдгд1, твевп1е» В1пб2пб<тлгд1, тлгд2, твеац1е>( еь1в Рппс<тдгд1, тдгд2, твево1е> Ецпс ) гееагп (у) => (х) > Таас( х, у ); ) рцЬ11с с1авя В1пбегЕхаир1е ( всас1с човб Мабп() ( чаг иу11яь = пен 11вь<боць1е> ( 1.0, 3.4, 5.4, 6.54 ); чаг пен11яг = пен Егвс<бопЬ1е>(); // Исходное выражение.
Ецпс<боиЬ1е, боцЬ1е, бопЬ1е> Ьцпс = (х, у) => х + у; // Каррированная функция. чаг Тцпсвоцпб = Тцпс.В1пб2пб()(3.2); гогеасЬ( чаг бяеи 1п иу11вс ) ( Сопяо)е.иггсе( "(О), ", Есеи ); пен11яс.лбб( ГцпсВоцпб(усев) )4 Сопво1е.кг1се11пе(); ))ямбда-выражения 523 Тогеас)г( тат Тгеа Тл пеи11зв ) ( Солзс1е.нггсе( "(О), ", Тсеа ) ) ) ) Код, отличающийся от предыдущего примера, выделен полужирным. В первом примере метод В1о<(2ог)<> возвращал делегат, принимающий один параметр 101 и возвращающий значение 1ог. В данном примере В1ос(2лг)<> изменен так, чтобы он возвращал делегат, который принимает один параметр (значение для привязки второго параметра в исходной функции) и возвращает другой делегат, представляющий собой каррированную функцию. Обе формы совершенно допустимы. Тем не менее, приверженцы чистоты стиля могут отдать предпочтение второй форме.
Анонимная рекурсия В разделе "Замыкание (захват переменной) и мемоизация" ранее в главе была показана форма рекурсии, использующая замыкания при вычислении чисел Фибоначчи. В продолжение дискуссии давайте рассмотрим похожее замыкание, которое можно использовать для вычисления факториала числа: Гипс<зов, Тот> гаев = ли11) Гзсв (х) > х > 1 ? х * Тзсв(х — 1): 1; Этот код работает, потому что Тасс формирует замыкание на самом себе и также вызывает себя. То есть вторая строка, в которой Гасе присваивается лямбда-выражение для вычисления факторизла, захватывает сам делегат Гасе.
Несмотря на то что такая рекурсия работает, она является весьма хрупкой. и во время ее применения в таком виде, как она показана, следует соблюдать предельную осторожность. Причины будут описаны ниже. Вспомните, что невзирая на то, что замьгкание захватывает переменную для использования внутри анонимного метода, который реализован здесь в виде лямбда-выражения, захваченная переменная остается доступной для изменения вне контекста захватывающего анонимного метода или лямбда-выражения. Например, посмотрим, что произойдет, если поступить следующим образом: Галс<гав, глав> Тасс = оо11? гасе = (х) => х > 1 ? х * гасе(х-1): 1? Гавс<але, аве> леекехтогесе = гасе) Поскольку объекты в СЬК являются ссылочными типами, оеикегтогасг и Таст теперь ссылаются на один и тот же делегат.
Теперь предположим, что сделано такое: Галс<гав, 1лв> Тасс = оо11? Тасе = (х) => х > 1 ? х * Гасе(х-1) : 1; Галс<асс, 1лг> пеняегтсгасг = ТасСГ тесе (х) => х + 1; Запланированная рекурсия разрушена! Заметили, почему? Причина связана с модификацией захваченной переменной Тасв.
Ей присвоена ссылка на новый делегат, основанный на лямбда-выражении (х) => хя1. Но пеикеттогасс по-прежнему ссылается налямбда-выражение (х) => х > 1? х * Тасс(х-1): 1. Однакокогдаделегат, накоторый ссылается пеинегтогасг, обращается к Таст, вместо рекурсии он выполняет новое выражение (х) => х+1, поведение которого отличается от имевшейся ранее рекурсии. В конечном счете, проблема вызвана тем фактом, что замыкание, заключающее в себе рекурсию, позволяет модифицировать захваченную переменную (делегат Тоос) извне.
Если захваченная переменная изменяется, рекурсия может быть нарушена. 524 Глава 15 Существует несколько способов решить описанную проблему,но наиболее типичный состоит в использовании анонимной рекурсии". Для этого лямбда-выражение вычисления факториала потребуется изменить, чтобы оно принимало еще один параметр — делегат, который должен вызываться во время рекурсии. Это приводит к удалению замыкания и превращению захваченной переменной в параметр делегата. В итоге получается нечто вроде следующего: бе1еоаге тйеэн1с Апопкес<тлгд,ткезн11>( Апопкес<тлгч,таеэп11> 1, тлго агд ) Апопкео<апс, 1пс> Тасс = (Г, х) => х > 1 ? х * Т(Г, х-1): 1; Суть в том,что вместо организации рекурсии на основе захваченной переменной, которая является делегатом, делегат рекурсии передается в качестве параметра.
То есть, захваченная переменная заменяется переменной, переданной в стеке (в данном случае параметром Т делегата Тасс). В рассматриваемом примере делегат рекурсии представлен параметром Т. Поэтому обратите внимание, что Тасс не только принимает Т как параметр, но вызывает его для организации рекурсии и затем передает Т в следующую итерацию делегата. По сути, захваченная переменная теперь находится в стеке, так как она передается каждой рекурсии выражения. Однако поскольку она в стеке, опасность ее модификации извне механизма рекурсии исключается. Чтобы подробнее ознакомиться с этой техникой, почитайте статью "Апопупюцэ Кесигз)оп 1п СМ" (" Анонимная рекурсия в СФ") в блоге Веса Дайера (й/еэ Т)уег) по адресу Асср: //)>1одз ппэг)п.
сов/неэбуег. В этой статье объясняется, как реализовать комбинатор неподвижной точки, обобщающий описанную выше анонимную рекурсию'е. Резюме В этой главе был представлен синтаксис лямбда-выражений, которые в основном являются заменой анонимных методов. Очень жаль, что лямбда-выражения не появились еще в версии СФ 2А), потому что тогда не возникла бы необходимость в анонимных методах. На примерах было показано, как преобразовать лямбда-выражения с н без тел операторов в делегаты. Вдобавок вы увидели, как лямбда-выражения беэ тел операторов преобразуются в деревья выражений на основе типа Ехргезэаоп<Т>, определенного в пространстве имен Яуэсет. 1.1ги).
Ехргеээаоп, Перед компиляцией в делегат и вызовом к деревьям выражений можно применять трансформации. В завершение главы были приведены примеры полезного применения лямбда-выраженнй. К ним относятся; создание обобщенных итераторов, мемонзация с использованием замыканий, привязка параметров делегатов с помощью каррирования, а также представление концепции анонимной рекурсии. Почти все перечисленные концепции являются основой функционального программирования. Несмотря на то что все эти приемы можно было реализовать в С)) с использованием анонимных методов, добавление в язык лямбда-синтаксиса сделало их применение более естественным и менее сложным.
Следующая глава посвящена языку 1Л)ЧЯ. Кроме того, будет продолжено рассмотрение связанных с ним аспектов функционального программирования. ' Дополнительные сведения об анонимной рекурсии (иа английском языке) доступны по адресу асср:Оеп.н1хгреб1а.ого/н1К1/Апопуиопэ геснгз1оп. и дополнительные сведения о комбинаторе неподвижной точки доступны по адресу Асср: // ннн.нгх1гпап1е.гп/гп-нг/1пбех.рпр/Комбинатор неподвижной точки. глава 16 ЫИЯ: язык интегрированных запросов я зыки, подобные С (включая С№), императивны по природе — в том смысле, что упор делается на состояние системы и изменениях, которые она претерпевает со временем.
Языки запросов данных, такие как БЯЬ, по своей природе функциональны — в том смысле, что упор делается на операцию, и в этом процессе используется совсем немного изменяемых данных (либо они вообще не используются). Ы(чЯ (Ьапапале 1птеагагег( Яцегу — язык интегрированных запросов) заполняет пробел между императивным и функциональным стилями программирования. Это обширная тема, которая достойна того, чтобы посвящать ей отдельные книги'. Доступно несколько реализаций Ы(ЧЯ; ЫХЯ то ОЬ)естэ, ЫНЯ 1о БЯ1., ЫНЯ го Оагавей Ы(ЧЯ то Епрй(ез и ЫНЯ то ХМЬ.










