<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:atom="https://www.w3.org/2005/Atom" xmlns:sy="https://purl.org/rss/1.0/modules/syndication/" xmlns:dc="https://purl.org/dc/elements/1.1/" xmlns:content="https://purl.org/rss/1.0/modules/content/" xmlns:wfw="https://wellformedweb.org/CommentAPI/" xmlns:slash="https://purl.org/rss/1.0/modules/slash/"
  version="2.0">
  <channel>
    <title>Блог Василия Воронкова</title>
    <atom:link
      href="https://docs.microsoft.com/archive/blogs/vorov/feed.xml"
      rel="self"
      type="application/rss+xml" />
    <link>https://docs.microsoft.com/archive/blogs/vorov/feed.xml</link>
    <description>О программировании и не только.</description>
    <lastBuildDate>Wed, 17 Apr 2019 03:44:33 GMT</lastBuildDate>
    <language>en-US</language>
    <sy:updatePeriod>hourly</sy:updatePeriod>
    <sy:updateFrequency>1</sy:updateFrequency>
    <item>
      <title>Алгебраические типы. Часть 3. Полиморфные варианты</title>
      <link>https://docs.microsoft.com/archive/blogs/vorov/3-10</link>
      <pubDate>Tue, 31 May 2011 07:00:00 GMT</pubDate>
      <dc:creator><![CDATA[Василий Воронков]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/vorov/2011/05/31/3-10/</guid>
      <description><![CDATA[Главный недостаток алгебраических типов явным образом следует из их главного преимущества перед...]]></description>
      <content:encoded><![CDATA[<p>Главный недостаток алгебраических типов явным образом следует из их главного преимущества перед классами в ООП. В прошлой заметке мы обсуждали тот факт, что алгебраические типы являются "закрытыми", и вы не можете добавить новый конструктор, не изменив само объявление алгебраического типа, что позволяет еще на этапе компиляции получить полную информацию о том, какие конструкторы включает наш алгебраический тип. Это позволяет нам избавиться от лишней косвенности в виде виртуальных функций, и в целом делает наш код более предсказуемым, жестко просчитываемым еще на этапе компиляции. Но и недостатки подобного подхода очевидны. </p>
<p>Помните наш пример с компанией, которая умеет продавать только мобильные телефоны и ноутбуки? Ну ради бога, разве у реальной компании будут такие нелепые ограничения? Скорее наоборот - сегодня мы продаем телефоны и ноутбуки, а завтра - предметы женского гардероба. Очевидно, что алгебраические типы в таком случае окажутся не слишком полезны.</p>
<p>На самом деле проблема невозможности расширения алгебраических типов не дает покоя лучшим умам уже много лет. Ей даже придумали название - expression problem. Ну раз у проблемы даже есть название, то должно быть и какое-нибудь решение - ведь не может быть такого, что "лучшие умы" за столько лет так ничего и не придумали? И решение действительно есть.</p>
<p>Начнем как обычно издалека.</p>
<p>Есть такой язык программирования OCaml, дальний (впрочем, не такой уж и дальний) родственник F#. (Скажу по секрету - F# первоначально фактически и представлял собой версию OCaml под платформу .NET, но впоследствии их пути немного разошлись). OCaml, как и подобает функциональному языку из семейства ML, поддерживает "классические" алгебраические типы. Но в какой-то момент в OCaml появился и другой тип данных - с интригующим названием <em>полиморфный вариант</em>. </p>
<p>Что же это такое? Давайте посмотрим на примере. Попробуем переписать код из предыдущей заметки на OCaml с использованием этих самых полиморфных вариантов:</p>
<div>
<pre class="scroll"><code style="font-family: Consolas; font-size: 8pt;" class="csharp">let l = `Laptop 14.2;;

let res = match l with
          | `Laptop _    -&gt; "We have a laptop"
          | `Cellphone _ -&gt; "We have a cellphone";;</code></pre>
<code style="font-family: Consolas; font-size: 8pt;" class="csharp">
</code></div>
<p>На первый взгляд все очень похоже на F#, если не считать дополнительных "знаков препинания". Но погодите, а мы точно ничего не забыли? Где же объявление нашего алгебраического типа? (Ну или, как его, полиморфного варианта?). А в том-то и дело, что никакого объявления нет.</p>
<p>Отвечая на вопрос "каким образом сделать алгебраические типы расширяемыми", OCaml приходит к довольно-таки неожиданному решению. А давайте представим, что во всей нашей программе - да что там "программе", во всем мире! - есть лишь один-единственный алгебраический тип. Этот тип включает все возможные конструкторы - даже те, которые вам только предстоит придумать. По этой причине нет никакой необходимости заранее декларировать алгебраический тип - с полиморфными вариантами он как бы объявляется на ходу. </p>
<p>Возвращаясь к нашему примеру - что теперь нужно сделать, если мы захотим добавить к числу реализуемых товаров нашей фирмы еще и мониторы? Да практически ничего. Просто считайте, что мониторы у нас уже есть:</p>
<div>
<pre class="scroll"><code style="font-family: Consolas; font-size: 8pt;" class="csharp">let res = match l with
          | `Laptop _    -&gt; "We have a laptop"
          | `Cellphone _ -&gt; "We have a cellphone"
          | `Monitor _   -&gt; "We have a monitor";;</code></pre>
</div>
<p>Однако в вышеприведенном коде есть одна серьезная проблема. В случае с закрытыми алгебраическими типами нам всегда точно известно, какие конструкторы включает в себя тот или иной алгебраический тип. Соответственно, предыдущая версия кода по анализу продукта (написанная на F#) была полностью корректна и безопасна - ведь нам же известно, что компания не продает ничего, кроме ноутбуков и мобильных телефонов, более того, сам компилятор гарантирует нам это!</p>
<p>Здесь же ситуация в корне изменяется. Перечислить все конструкторы мы попросту не можем, а соответственно, код, приведенный выше, уже не так безопасен как раньше. Что будет если кто-нибудь вызовет его с вариантом `Tablet? Будет ошибка времени исполнения. Чтобы избежать этого нам придется переделать этот код так:</p>
<div>
<pre class="scroll"><code style="font-family: Consolas; font-size: 8pt;" class="csharp">let res = match l with
          | `Laptop _    -&gt; "We have a laptop"
          | `Cellphone _ -&gt; "We have a cellphone"
          | `Monitor _   -&gt; "We have a monitor"
          | _ -&gt; "We have an unknown product";;</code></pre>
</div>
<p>Думаю, что смысл изменений должен быть понятен, даже если вы не знакомы с ML-подобным синтаксисом. Фактически мы просто добавили специальный паттерн, который будет срабатывать во всех случаях, когда вместо монитора, ноутбука или телефона нам передают что-либо другое. Проблема в том, что теперь нам придется писать так всегда - ну или попросту смириться с тем, что неосторожное обращение с вариантами может привести к неожиданным ошибкам во время исполнения.</p>
<p class="CodeFragmentCxSpLast">Вторая проблема явно проистекает из того факта, что варианты не требуют предварительного объявления. А раз объявление необязательно, то компилятор никак не сможет сам разобраться, "вводите" ли вы новый полиморфный вариант или же обращаетесь к уже "объявленному" ранее. А это приводит к таким вот ошибкам (совсем не свойственным статически-типизированным языкам):</p>
<div>
<pre class="scroll"><code style="font-family: Consolas; font-size: 8pt;" class="csharp">let l = `Monitor 24.0

let res = match l with
          | `Laptop _    -&gt; "We have a laptop"
          | `Cellphone _ -&gt; "We have a cellphone"
          | `Monitol _   -&gt; "We have a monitor"
          | _ -&gt; "We have an unknown product";;</code></pre>
</div>
<p>Как видите, при написании конструкции match я допустил опечатку в слове Monitor, и компилятор никак не сможет мне тут помочь. Код будет скомпилирован успешно и приведет к ошибочному поведению в ран-тайме.</p>
<p>Очевидно, что полиморфные варианты так же не кажутся панацеей. Если проблема классических алгебраических типов заключается в невозможности расширения, то полиморфные варианты как раз напротив - чрезмерно расширяемые, без возможности как-то проконтролировать и ограничить эту их расширяемость.</p>
<p>Есть такое мнение, что язык с динамической типизацией - это на самом деле язык со статической типизацией, в котором есть всего лишь один тип. Надо сказать, что данная позиция не лишена основания. И что мы имеем с полиморфными вариантами? Фактически и получается, что везде, где мы их используем, мы работаем с одним и тем же типом, что сводит все преимущества статической типизации на нет. Получается весьма резкий переход от алгебраических типов, которые в известном смысле куда более статическим типизированы, чем классы в ООП, к "без пяти минут динамике" под видом полиморфных вариантов. При этом стоит заметить, что OCaml, язык, в котором дебютировала концепция полиморфных вариантов, - это очень строгий, если можно так выразиться, статически-типизированный язык, в котором даже используются разные арифметические операторы для целых и вещественных чисел. Очевидно, что, хотя полиморфные варианты и решают вышеозначенную проблему "закрытости" алгебраических типов, в такой язык как OCaml они не очень хорошо вписываются.</p>
<p>А что если бы у нас был динамически-типизированный язык?</p>
<p><a href="https://blogs.msdn.com/controlpanel/blogs/posteditor.aspx/Ela" title="https://code.google.com/p/elalang/" target="_blank">Ela</a>, о которой я уже упоминал ранее, так же поддерживает концепцию вариантов и при этом является динамически-типизированным языком. Вышеприведенный код выглядел бы на Ela следующим образом:</p>
<pre class="scroll"><code style="font-family: Consolas; font-size: 8pt;" class="csharp">let l = Monitor 24.0

let res = match l with
                Laptop = "We have a laptop"
                Cellphone = "We have a cellphone"
                Monitor = "We have a monitor"
                _ = "We have an unknown product"</code></pre>
<p>
Весьма похоже, правда? Но есть и пара отличий. Например, в Ela не используется апостроф перед названием конструктора полиморфного варианта - в нем просто нет нужды, так как нет необходимости отличать конструкторы алгебраических типов от конструкторов полиморфных вариантов. Классические алгебраические типы Ela не поддерживает - да они и невозможны в рамках динамической типизации. Поэтому в Ela используется весьма простое, традиционное для функциональных языков соглашение - любой идентификатор, начинающийся с заглавной буквы, считается вариантом.</p>
<p>Фактически вариант в Ela - это очень простая концепция. У вас попросту есть возможность прикрепить произвольную метку к любому значению (или даже создать одиночную метку, без связанного значения). Данная метка впоследствии может быть проанализирована с помощью паттерн-матчинга. Так как Ela динамически-типизированный язык, и у нас и так в каком-то смысле есть лишь один-единственный тип, концепция полиморфных вариантов не приносит в язык "лишней" динамики.</p>
<p>Простейший пример использования вариантов мог бы выглядеть так:</p>
<div>
<pre class="scroll"><code style="font-family: Consolas; font-size: 8pt;" class="csharp">let getOdd x | r &gt; 0 = Odd r
             | else  = Even
             where r = x % 2</code></pre>
</div>
<p>
Данная функция проверяет, является ли переданное в нее число четным и возвращает результат, используя вариант.&nbsp;Здесь несложно усмотреть некоторые параллели с nullable-типами в C#. И действительно - nullable-тип это просто более "специализированная" версия "функционального" варианта.</p>]]></content:encoded>
    </item>
    <item>
      <title>Алгебраические типы. Часть 2. ООП как путь к динамической типизации</title>
      <link>https://docs.microsoft.com/archive/blogs/vorov/2-1</link>
      <pubDate>Thu, 26 May 2011 08:58:00 GMT</pubDate>
      <dc:creator><![CDATA[Василий Воронков]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/vorov/2011/05/26/2-1/</guid>
      <description><![CDATA[Возможно, вам доводилось слышать такое утверждение о языке программирования Хаскелл - Хаскелл это...]]></description>
      <content:encoded><![CDATA[<p>Возможно, вам доводилось слышать такое утверждение о языке программирования Хаскелл - Хаскелл это <em>полностью</em> статически типизированный язык. Строго говоря, это, конечно же, не совсем так, но сейчас речь не об этом. Согласитесь, что это утверждение в каком-то плане интригует? Нам как бы намекают, что есть простые языки со статической типизацией (вроде C#), а есть такие особенные как Хаскелл, которые "полностью" статически типизированы.</p>
<p>Так что же означает эта полная статическая типизация?</p>
<p>Попробуем подойти к этой проблеме с другого конца. Возьмем тот же C#. С# как известно хорошо поддерживает объектно-ориентированную парадигму. Наверняка вам не раз приходилось писать на C# код, аналогичный следующему:</p>
<pre class="scroll"><code class="csharp" style="font-family: Consolas; font-size: 8pt;">public abstract class Foo
{
  void DoSomething();
}

public void CallFoo(Foo foo)
{
   foo.DoSomething();
}</code></pre>
<p>Что мы можем сказать об этом коде? (Ну кроме того, что он демонстрирует фундаментальный механизм, на основе которого строится полиморфизм в ООП-языках). Вернее, давайте зададим вопрос иначе - может ли данный код быть статически типизирован?</p>
<p>На первый взгляд ответ очевиден. Собственно, тут и типизировать-то нечего - все аннотации типов указаны, компилятор прекрасно знает, где и какие типы используются. Вы, к примеру, не сможете вызвать функцию CallFoo, передав в нее строку или целое число. А что мы можем передать в CallFoo?</p>
<p>И тут-то начинается самое интересное. Ведь стоит немного задуматься, как понимаешь, что мы в действительности понятия не имеем, экземпляр какого <em>конкретного</em> типа может быть передан в Foo. Этот тип может быть реализован вообще спустя несколько лет после того, как вы напишите CallFoo. Все, что нам известно об этом типе, это то, что он должен быть наследником класса Foo - причем необязательно прямым, сойдет и "дальний родственник" - как говорится, седьмая вода на киселе.</p>
<p>Следствием всего этого является один простой факт - компилятор попросту не знает, какой конкретно метод под названием DoSomething будет вызван. Собственно, в нашем примере этот метод вообще не реализован. А определять, какая именно реализация DoSomething должна быть вызвана внутри CallFoo, будет уже среда исполнения. Проще говоря, конкретный тип аргумента foo станет нам известен только в рантайме. Так, секундочку, а чем там статическая типизация отличается от динамической?</p>
<p>Фактически, в объектно-ориентированном языке (таком как C#), при написании кода с использованием полиморфизма классов, компилятору доступа лишь часть информации о типе - известен общий тип, но не известен частный.</p>
<p>Что же должно происходить в языке с "полной" статической типизацией?</p>
<p>Как вы помните, в прошлой заметке мы пытались понять, что же представляют собой алгебраические типы и обнаружили немало общих черт между ними и обычным наследованием в объектно-ориентированных языках. Но и немало существенных отличий. Основное из них заключается в том, что алгебраический тип по сути представляет собой закрытую одноуровневую иерархию наследования - вы не можете "дописать" новый конструктор к существующему алгебраическому типу "из вне" - как вы бесспорно можете отнаследоваться от класса, переопределить его методы и при этом не менять уже написанный код, который с этим классом работает.</p>
<p>Следствие этой особенности достаточно очевидно. При использовании алгебраических типов конкретный тип всегда известен полностью, компилятор всегда знает, какая именно реализация DoSomething будет вызвана в тот или иной момент. А отсюда происходит и утверждение о "полной" статической типизации.</p>
<p>Бесспорно, у такой особенности алгебраических типов есть множество положительных следствий. Компилятор обладает большей информацией о типах, наш код становится более предсказуемым, легко просчитываемым на этапе компиляции. Так как нам заранее известно количество продукций того или иного алгебраического типа, мы всегда можем с уверенностью сказать, учитывает ли наш код всевозможные случаи. В чем мы не можем быть никогда уверены в случае с классами в объектно-ориентированных языках, когда мы по большому счету даже не знаем, какую конкретно реализацию метода мы вызываем (если, разумеется, этот метод помечен как виртуальный и находится в открытом для наследования классе).</p>
<p>Алгебраические типы напрямую поддерживаются многими функциональными языками. Помимо Хаскелла, здесь можно упомянуть так же и F#. Вот как наш пример из предыдущей заметки - с ноутбуками и мобильными телефонами - может быть переписан на F#:</p>
<pre class="scroll"><code class="csharp" style="font-family: Consolas; font-size: 8pt;">type Product = 
    | Laptop of double 
    | Cellphone of bool
    
let l = Laptop 14.2

let res = match l with
          | Laptop _    -&gt; "We have a laptop"
          | Cellphone _ -&gt; "We have a cellphone"</code></pre>
<p>Как видите, кода нам пришлось написать куда меньше. Да и выглядит он куда проще, чем имитация алгебраического типа на C#, даже если вы не знакомы с ML-подобным синтаксисом.</p>
<p>На примере F# так же очень удобно показать "закрытость" алгебраических типов. Как видите, в примере выше есть код, который проверяет экземпляр алгебраического типа и присваивает переменной res значение, в зависимости от того, с каким экземпляром мы имеем дело. Давайте попробуем добавить новую продукцию к типу Product, при этом код для обработки оставим без изменений:</p>
<pre class="scroll"><code class="csharp" style="font-family: Consolas; font-size: 8pt;">type Product = 
    | Laptop of double 
    | Cellphone of bool
    | Monitor of bool</code></pre>
<p>А теперь попробуем скомпилировать нашу программу:</p>
<pre class="scroll"><code class="csharp" style="font-family: Consolas; font-size: 8pt;">warning FS0025: Incomplete pattern matches on this <br />expression. For example, the value 'Monitor (_)' may <br />indicate a case not covered by the pattern(s).</code></pre>
<p>Как видите, заботливый комплиятор F# тут же предупреждает нас, что имеющийся код для обработки неполон и не учитывает наличие продукции Monitor. Удобно, правда? И к тому же весьма сильно отличается от того, к чему мы привыкли в объектно-ориентированных языках.</p>
<p>Итак, полная статическая типизация - это, бесспорно, "жирный плюс" в копилку алгебраических типов. Но как всегда тут не обойдись и без не менее жирных "минусов". Об этом я расскажу в следующей, заключительной, части.</p>]]></content:encoded>
    </item>
    <item>
      <title>Алгебраические типы. Часть 1</title>
      <link>https://docs.microsoft.com/archive/blogs/vorov/1</link>
      <pubDate>Wed, 25 May 2011 01:27:00 GMT</pubDate>
      <dc:creator><![CDATA[Василий Воронков]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/vorov/2011/05/25/1/</guid>
      <description><![CDATA[В теории типов алгебраическим типом данных называют такой тип, который представляет собой...]]></description>
      <content:encoded><![CDATA[<p>В теории типов алгебраическим типом данных называют такой тип, который представляет собой объединение различных значений, причем каждое из этих значений является отдельным типом данных. C#-программисту на первый взгляд такая формулировка может показаться весьма туманной. (И не в последнюю очередь потому что я ее на редкость туманно сформулировал). К тому же в большинстве популярных объектно-ориентированных языков, включая C#, понятие алгебраический тип попросту отсутствует. </p>
<p>Но не все так печально. Хотя алгебраических типов в C# действительно нет, но зато есть другой тип данных, во многом с ними схожий. Этот тип называется <em>перечисление</em> (enumeration).</p>
<p>Но все-таки - что же на самом деле представляют собой алгебраические типы? Невнятные определения - это, конечно, хорошо, но куда лучше во всем по-настоящему разобраться.</p>
<p>Давайте представим, что мы работаем на некую компанию, которая продает различные продукты. У компании огромная сеть магазинов, однако ассортимент реализуемых товаров достаточно ограничен. В итоге мы можем с легкостью составить перечень всех предлагаемых нами продуктов, используя простое перечисление вида:</p>
<pre class="scroll"><code class="csharp" style="font-size: 8pt; font-family: Consolas;">public enum Product
{
    Cellphone,
    Laptop
}</code></pre>
<p>Наша задача - разработать приложение, через которое будет доступна статистика продаж - количество проданных телефонов и компьютеров.</p>
<p>На первый взгляд использование перечисления для того, чтобы представить тип продукта, кажется хорошей идеей. Но как только мы начинаем разработку, то сразу осознаем серьезные ограничения такого подхода. Основное из них - невозможность ассоциировать какие-либо дополнительные данные с типом продукта.</p>
<p>Первая же идея, которая приходит в голову, - написать собственный тип данных (назовем его Product), который будет представлять собой тип продаваемого продукта.</p>
<p>Это кажется неплохим решением, но опять же - как только мы приступаем к реализации типа Product, нам неожиданно становится ясно, что и этот вариант решения нам не подходит. Ведь продукты наши совершенно разные и имеют различный набор атрибутов.</p>
<p>Ладно, пожалуй, я слишком драматизирую. Все, что нам нужно - использовать одну из корневых возможностей объектно-ориентированного программировния под названием наследование. И вот что у нас получается в итоге:</p>
<pre class="scroll"><code class="csharp" style="font-size: 8pt; font-family: Consolas;">public abstract class Product
{

}

public sealed class Cellphone : Product
{
    bool TouchScreen { get; set; }
}

public sealed class Laptop : Product
{
    double ScreenSize { get; set; }
}</code></pre>
<p>(Не пытайтесь анализировать набор атрибутов - я их выбрал практически случайным образом).</p>
<p>Итак, мы почти завершили дизайн наших типов данных. Однако осталось одно требование, которому представленная выше иерархия продуктов по-прежнему не удовлетворяет. Как вы помните, по некой странной причине наша компания продает только ноутбуки и мобильные телефоны и ничего, кроме этих двух продуктов. Более того, у нас есть, можно сказать, пожизненная гарантия того, что компания никогда не расширит ассортимент предлагаемых товаров. А следовательно, нам нужно убедиться, что никакие другие классы, кроме Laptop и Cellphone, не смогу наследоваться от Product.</p>
<p>И, после некоторых размышлений, мы приходим к следующему решению:</p>
<pre class="scroll"><code class="csharp" style="font-size: 8pt; font-family: Consolas;">public abstract class Product
{
    private Product() { }

    public sealed class CellphoneProduct : Product
    {
        public bool TouchScreen { get; set; }
    }

    public sealed class LaptopProduct : Product
    {
        public double ScreenSize { get; set; }
    }

    public static LaptopProduct Laptop(double screenSize)
    {
        return new LaptopProduct { ScreenSize = screenSize };
    }

    public static CellphoneProduct Cellphone(bool touchScreen)
    {
        return new CellphoneProduct { TouchScreen = touchScreen };
    }
}</code></pre>
<p>(Как видите, я так же описал специальные статические функции - назовем их конструкторами, - которые могут использоваться для создания отдельных экземпляров классов LaptopProduct и CellphoneProduct. В этих функциях нет строгой необходимости, но, благодаря ним, работа с типами товаров станет чуть удобнее).</p>
<p>Итак, что же у нас получилось. Вряд ли такой подход к дизайну классов в C# можно назвать типичным. С другой стороны - мы всего лишь используем стандартные средства ООП и некоторые дополнительные возможности, предоставляемые C#. Мы объявили классы CellphoneProduct и LaptopProduct как вложенные в родительский тип Product, так как вложенные классы могут образащаться к private-членам своих классов-контейнеров. Благодаря этому, мы смогли описать у класса Product private конструктор, что позволяет нам убедиться в том, что никто больше не сможет отнаследоваться от этого класса. Так же мы пометили CellphoneProduct и LaptopProduct модификатором sealed, который делает классы "закрытыми", также запрещая от них наследоваться.</p>
<p>Вот как мы теперь сможем использовать эти классы:</p>
<pre class="scroll"><code class="csharp" style="font-size: 8pt; font-family: Consolas;">var l = Product.Laptop(14.2);

if (l is Product.LaptopProduct) {
    ...
}
else if (c is Product.CellphoneProduct) {
    ...
}</code></pre>
<p>Я не случайно так акцентирую внимание на нашей иерархии продуктов. Ведь фактически мы только что написали на C# самый настоящий алгебраический тип. И ведь правда - у нас есть тип Product, который может быть или ноутбуком (LaptopProduct), или мобильным телефоном (CellphoneProduct) - и ничем, кроме этих двух. Говоря другими словами, тип Product представляет собой <em>сумму продуктов</em> LaptopProduct и CellphoneProduct. При этом мы искусственно ввели ряд ограничений, благодаря которым у нас появляются существенные отличия от классической объектно-ориентированной иерархии классов:</p>
<ul>
<li>Прежде всего у нас есть лишь одноуровневая цепочка наследований, и пользователь нашего кода при всем желании не сможет ее "разветвить".</li>
<li>Далее, наша иерархия наследования закрытая. Фактически, только глядя на код, мы можем сказать, что никто, кроме LaptopProduct и CellphoneProduct, не может наследоваться от класса Product.</li>
</ul>
<p>Хорошо, но у вас наверняка возникает вопрос - а чего мы, собственно, добились, введя эти странные ограничения? Но об этом я расскажу в следующий раз.</p>]]></content:encoded>
    </item>
    <item>
      <title>Чистые функции</title>
      <link>https://docs.microsoft.com/archive/blogs/vorov/53</link>
      <pubDate>Tue, 26 Apr 2011 06:10:00 GMT</pubDate>
      <dc:creator><![CDATA[Василий Воронков]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/vorov/2011/04/26/53/</guid>
      <description><![CDATA[На первый взгляд с чистыми функциями все просто. Функция называется чистой, если она удовлетворяет...]]></description>
      <content:encoded><![CDATA[<p>На первый взгляд с чистыми функциями все просто. Функция называется чистой, если она удовлетворяет двум требованиям:</p>
<ul>
<li>Функция всегда возвращает одно и то же значение для одних и тех аргументов. Т.е. если мы один раз вызвали чистую функцию с аргументами, к примеру, 12 и 42, то когда нам потребуется вызвать ее еще раз, с точно такими же аргументами, мы можем попросту использовать предыдущее значение, которое вернула эта функция.</li>
<li>Функция не изменяет состояние, внешнее по отношению к этой функции, и не зависит от этого состояния. Функция, которая удаляет файл из файловой системы, очевидно не является чистой (и неудивительно, ведь она удаляет целый файл!). Точно так же, как не является чистой функция, которая читает данные из файла. Функция так же не должна изменять переданные в нее аргументы - ведь эти аргументы были переданы извне и, соответственно, не принадлежат функции.</li>
</ul>
<p>Также надо понимать, что если одна функция, которая не делает ничего предосудительного, вызывает другую функцию, не соответствующую высоким стандартам чистоты, то она автоматически тоже становится нечистой. Нечистота - как заразная инфекция, переходит от одной функции к другой.</p>
<p>Ну и приведем для убедительности яркий пример чистой функции на Хаскелл:</p>
<pre class="scroll"><code style="font-family:Consolas,Courier New;font-size:8pt" class="csharp">add x y = x + y</code></pre>
<p>Казалось бы, все просто. Но я не раз замечал, что существует некоторая путаница с тем, какие именно функции следует считать чистыми. </p>
<p>Прежде всего следует понимать, что чистые функции никакого отношения к программированию в&nbsp;функциональном стиле не имеют. К примеру, является ли такая функция чистой:</p>
<pre class="scroll"><code style="font-family:Consolas,Courier New;font-size:8pt" class="csharp">int sum(int[] arr)
{
  int res = 0;

  for (int i = 0; i &lt; arr.Length; i++)
    res += arr[i];

  return res;
}</code></pre>
<p>Правильный ответ - да, она такая же чистая, как и ее аналог на Хаскелле:</p>
<pre class="scroll"><code style="font-family:Consolas,Courier New;font-size:8pt" class="csharp">sum [] = 0
sum (x:xs) = x + sum xs</code></pre>
<p>Да, наша реализация на C# написана в императивном стиле, более того, она всерьез "налегает" на изменяемые переменные - но все эти переменные локальные для функции, мы не изменяем никакого внешнего состояния и не зависим от него, мы всегда возвращаем один и тот же результат для тех же аргументов.</p>
<p>И тут возникает второй нюанс. Давайте вспомним функцию filter, которую мы уже рассматривали ранее:</p>
<pre class="scroll"><code style="font-family:Consolas,Courier New;font-size:8pt" class="csharp">filter _ [] = []
filter p (x:xs) | p x       = x : filter p xs
                | otherwise = filter p xs</code></pre>
<p>Чем в сущности занимается эта функция? Она получает функцию-предикат, список и возвращает новый список, который содержит элементы из первоначального списка, удовлетворяющие заданному условию. Ключевой момент здесь - возвращает новый список. Всякий раз, когда вы будете вызывать эту функцию, она сконструирует для вас новый список. Если рассуждать формально, то, скажем, два вызова этой функции с одинаковыми аргументами вернут вам два <em>разных</em> списка, каждый из которых занимает свое место в памяти.</p>
<p>Так почему же мы считаем функцию filter чистой? Да и вообще - чистая ли она?</p>
<p>Ответ прост. Мы считаем ее чистой, потому что списки в функциональных языках сравниваются не ссылочно (как, к примеру, массивы в C#), а структурно. Поэтому результаты двух вызовов этой функции эквивалентны, что несложно проверить в GHCi:</p>
<pre class="scroll"><code style="font-family:Consolas,Courier New;font-size:8pt" class="csharp">Prelude&gt;filter (&gt;5) [1..10] == filter (&gt;5) [1..10]
True</code></pre>
<p>Скажете, мы пошли на хитрость? И да, и нет. Так как список является неизменяемой структурой данных, то мы имеем полное право применять к нему те же правила при сравнении, что и, к примеру, для целых чисел. И с чистой душой игнорировать такие низкоуровневые детали, как размещение списка в памяти. Ведь с точки зрения пользователя два списка с одинаковыми элементами - это по сути один и тот же список.</p>
<p>А как быть с изменяемыми структурами данных? Попробуем написать отдаленный аналог функции filter на С#:</p>
<pre class="scroll"><code style="font-family:Consolas,Courier New;font-size:8pt" class="csharp">List&lt;int&gt; filter(Func&lt;int,bool&gt; p, List&lt;int&gt; list)
{
  var newList = new List&lt;int&gt;();

  foreach (var e in list)
    if (p(e))
      newList.Add(e);

  return newList;
}</code></pre>
<p>Является ли эта функция чистой? Боюсь, что на сей раз придется ответить "нет". List&lt;int&gt;, вопреки своему названию, представляет собой изменяемый массив, поэтому считать два экземпляра List&lt;int&gt; с одинаковым набором элементов одним и тем же "списком" мы уже не можем. Почему? Только ли потому что в C# для List&lt;int&gt; или обычных массивом структурное сравнение не используется по умолчанию, а сравниваются ссылки?</p>
<p>Вовсе нет. Давайте представим, что массивы в C# сравниваются именно структурно. Таким образом, два массива с одинаковым набором элементов будут считаться равными. И тогда мы сможем написать такой код:</p>
<pre class="scroll"><code style="font-family:Consolas,Courier New;font-size:8pt" class="csharp">var arr1 = new int[] { 1,2,3 };
var arr2 = new int[] { 1,2,3 };
<br />
if (arr1 == arr2)
  Console.WriteLine("arr1 и arr2 это один и тот же массив.");

arr1[0] = 10;

if (arr1 != arr2)
  Console.WriteLine("arr1 и arr2 это разные массивы.");</code></pre>
<p>В результате выведется:</p>
<pre class="scroll"><code style="font-family:Consolas,Courier New;font-size:8pt" class="csharp">arr1 и arr2 это один и тот же массив.
arr1 и arr2 это разные массивы.</code></pre>
<p>Как же так? Мгновение назад массивы были одинаковыми, а теперь они разные? </p>
<p>Как вы понимаете, во всем виновата "изменчивая природа" массивов. Так как вы можете поменять значения "по месту" (в отличие от связных списков в Хаскелле, любая операция над которыми приводит к созданию нового списка), то структурная эквивалетность уже не годится. </p>
<p>Другое дело - строки в C#. Ведь строки по сути это те же индексированные массивы, только на сей раз неизменяемые. Но говоря, что две строки равны, мы прежде всего подразумеваем, что эти строки состоят из одного и того же набора символов, при этом нас совершенно не волнует, занимают ли они одни и те же байты в оперативной памяти.</p>]]></content:encoded>
    </item>
    <item>
      <title>Эти удивительные каррированные функции</title>
      <link>https://docs.microsoft.com/archive/blogs/vorov/63</link>
      <pubDate>Wed, 20 Apr 2011 04:26:00 GMT</pubDate>
      <dc:creator><![CDATA[Василий Воронков]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/vorov/2011/04/20/63/</guid>
      <description><![CDATA[Имея в языке только каррированные функции и левоассоциативный оператор вызова, вы можете с легкостью...]]></description>
      <content:encoded><![CDATA[<p>Имея в языке только каррированные функции и левоассоциативный оператор вызова, вы можете с легкостью имитировать такие возможности как передача неограниченного количества параметров, при этом не вводя в язык какие-либо дополнительные механизмы.</p>
<p>К примеру, сколько аргументов у такой функции:</p>
<pre class="scroll"><code class="csharp" style="font-size:8pt;font-family:Consolas;Courier New">out "Hello"

//Вывод:
//Hello

out "First" "Second" "Third"

//Вывод:
//First
//Second
//Third</code></pre>
<p>Если вы читали мою предыдущую заметку, то уже знаете ответ - аргумент всего-лишь один. А вот как функция out может быть определена, к примеру, на Ela:</p>
<pre class="scroll"><code class="csharp" style="font-size:8pt;font-family:Consolas;Courier New">open Con
let out x = writen x $ out</code></pre>
<p>writen - это обычная функция вывода в консоль, такая же, как Console.WriteLine. Оператор ($) представляет собой так называемый sequencing operator. Ближайшим его аналогом в C# является точка с запятой. Оператор ($) исполняет сначала левое выражение, игнорирует вычисленное при этом значение, затем исполняет правое выражение и возвращает результат его вычисления. В итоге мы описали функцию, которая принимает один аргумент, выводит его на консоль, а потом возвращает саму себя, в результате чего работать с этой функцией можно так, как если бы она принимала бесконечное число аргументов.</p>
<p>Или возьмем для примера другой язык - F#. В стандартной библиотеке F# есть такая функция printfn. Вот как она работает:</p>
<pre class="scroll"><code class="csharp" style="font-size:8pt;font-family:Consolas;Courier New">printfn "Hello, %s!" "world"

//Вывод:
//Hello, world!

printfn "%d+%d=%d" 2 2 (2+2)

//Вывод:
//2+2=4</code></pre>
<p>Опять же, на первый взгляд кажется, что эта функция принимает неограниченное число параметров - так же, как и одна из версий Console.WriteLine, которая в качестве последнего параметра принимает массив аргументов с модификатором params. Но нет, printfn принимает всего лишь один аргумент.</p>
<p>Несмотря на то, что аргумент этот выглядит как строка, он в действительности имеет тип TextWriterFormat&lt;'T&gt;. Функция printfn типизируется в зависимости от значения этого аргумента. В первом случае в формате для вывода мы указали всего-лишь один аргумент - и в результате printfn возвращает функцию для одного аргумента, которую мы тут же и вызываем в нашем примере. Во втором случае нам требуется аж три аргумента - и printfn возвращает функцию для трех аргументов.</p>
<p>Более того, printfn контролирует типы еще на этапе компиляции. В первом случае мы указали, что нам требуется строка (%s) - и получили функцию, которая принимает строку. Во втором случае нам потребовались три числовых типа (%d) - и мы получили функцию, принимающую три целые числа. Попробуйте вызвать ее с параметрами другого типа - и получите ошибку времени компиляции. Удобно, правда?</p>
<p>Фактически у нас получается, что тип возвращаемого значения у функции зависит от типа ее аргумента. </p>
<p>В языке же с динамической типизацией, где вас не сдерживает система типов, ваша фантазия практически ничем не ограничена. Вот пример функции на Ela, которая принимает список и возвращает другую функцию, число параметров которой равно количеству элементов в списке:</p>
<pre class="scroll"><code class="csharp" style="font-size:8pt;font-family:Consolas;Courier New">let fun (x::xs) = fun' x xs
                where fun' a [] = \y -&gt; y + a
                      fun' a (x::xs) = \y -&gt; fun' (y + a + x) xs</code></pre>
<p>Полученная в результате вызова fun функция суммирует элементы списка с переданными в нее аргументами и вычисляет общую сумму:</p>
<pre class="scroll"><code class="csharp" style="font-size:8pt;font-family:Consolas;Courier New">let f = fun [1,2,3]
f 1 2 3

//Вывод:
//12</code></pre>
<p>В языке со статической типизацией данный код был бы невозможен, так как длина списка неизвестна нам на этапе компиляции, а следовательно, мы не можем определить, сколько именно аргументов будет у функции, которую возвращает fun.</p>
<p>Возьмем чуть более практичный пример. Скажем, у нас есть функция openConnection, которая принимает название используемого протокола и номер порта. При этом однако номер порта не требуется нам, если в качестве протокола используется HTTP (в таком случае всегда используется 80-й порт). Думаете, что для реализации такой функции нам пригодились бы необязательные параметры из C# 4.0? Мы можем прекрасно обойтись и без них:</p>
<pre class="scroll"><code class="csharp" style="font-size:8pt;font-family:Consolas;Courier New">let openConnection p | p == "http" = create p 80
                     | else        = \port -&gt; create p port
                     where create name port = {protocol=name,port=port}</code></pre>
<p>А теперь вызовем эту функцию:</p>
<pre class="scroll"><code class="csharp" style="font-size:8pt;font-family:Consolas;Courier New">openConnection "tcp" 244

//Вывод
//{protocol=tcp,port=244}</code></pre>
<p>И для HTTP:</p>
<pre class="scroll"><code class="csharp" style="font-size:8pt;font-family:Consolas;Courier New">openConnection "http"

//Вывод
//{protocol=http,port=80}</code></pre>
<p>Как видите, пользуясь только тем, что все наши функции каррированы, мы получаем возможность имитировать функции с неограниченным количеством параметров и даже функции с необязательными параметрами (при условии, что, конечно, сам факт "необязательности" можно вывести исходя из значений других параметров). При этом у нас не возникает необходимости усложнять язык введением каких-либо дополнительных механизмов. Все наши функции могут принимать лишь один-единственный аргумент, не больше и не меньше - и мы ни разу не отошли от этого правила.</p>]]></content:encoded>
    </item>
    <item>
      <title>Каррирование и частичное применение</title>
      <link>https://docs.microsoft.com/archive/blogs/vorov/73</link>
      <pubDate>Tue, 19 Apr 2011 02:04:00 GMT</pubDate>
      <dc:creator><![CDATA[Василий Воронков]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/vorov/2011/04/19/73/</guid>
      <description><![CDATA[Наверняка вы уже слышали такие термины как каррирование и частичное применение. Термины эти...]]></description>
      <content:encoded><![CDATA[<p>Наверняка вы уже слышали такие термины как <i>каррирование </i>и <i>частичное применение</i>. Термины эти используются достаточно широко, однако несмотря на это существует определенная путаница, из-за которой некоторые даже считают, что каррирование и частичное применение - это одно и то же. Кто в этом виноват? Наверное, авторы других блогов.</p>
<p>Но я, разумеется, не такой.</p>
<p>Давайте забудем на время все эти термины и представим, что есть некий язык программирования, в котором все функции принимают только один аргумент. Программируя на этом языке, вы никак не можете создать функцию, которая принимает, скажем, два аргумента или не принимает никаких аргументов вообще. Оставим ненадолго вопрос, зачем нам потребовалось вводить такие драконовские ограничения и подумаем, к каким последствиям может привести такое представление функций в языке.</p>
<p>Ну прежде всего самое время пересмотреть синтаксис. В Си-подобных языках список аргументов для функций помещается в скобки, но зачем нужны скобки, если никакого списка аргументов у нас больше нет, а есть лишь один-единственный аргумент? Сказано - сделано. Но, оказывается, избавившись от скобок, мы придумали себе другую проблему.</p>
<p>Как следует понимать выражение вида:</p>
<pre class="scroll"><code style="font-size: 8pt; font-family: Consolas;" class="csharp">sin x * 2</code></pre>
<p>Или, еще хуже:</p>
<pre class="scroll"><code style="font-size: 8pt; font-family: Consolas;" class="csharp">sin x + y * 2</code></pre>
<p>Что конкретно выступает в качестве аргумента для функции sin?</p>
<p>Но не все так страшно. Очевидно, что для операции вызова функции попросту требуется приоритет. Какой же приоритет выбрать? Если взять низкий приоритет, то не всегда будет понятно, что же в действительности является аргументом - по сути аргумент будет представлять собой все выражение, следующее после функции, до самого, так сказать, края листа. Это, очевидно, не очень удобно да и как-то не способствует читабельности. Фактически нам придется постоянно заключать вызов функции в скобки. Получается, в одном месте от скобок избавились, а в другом - их придется писать постоянно, т.е. вместо sin(x) будет (sin x). Невелика заслуга.</p>
<p>А что, если присвоить вызову функции самый высокий приоритет? Тогда никаких дополнительных скобок не требуется, и выражение вида sin x * 2 будет полностью эквивалетно sin(x) * 2.</p>
<p>Годится. Идем дальше.</p>
<p>Что мы еще можем сказать о нашей операции вызова функции? Функция всегда идет первой, а аргумент следует сразу ней. Логично, что оператор вызова функции должен быть левоассоциативным (как, например, большинство операторов в C#) и, соответственно, исполняться будет слево направо. А теперь внимание. Кто-то написал на нашем языке такой код:</p>
<pre class="scroll"><code style="font-size: 8pt; font-family: Consolas;" class="csharp">sum 2 2</code></pre>
<p>На первый взгляд кажется, что этот кто-то пытается вызвать функцию sum с двумя аргументами. А ведь функций с двумя аргументами в нашем языке нет. Получается, что код ошибочный?</p>
<p>Вовсе нет. Так как вызов функции левоассоциативный, то выражение вида sum 2 2 полностью эквивалентно (sum 2) 2. Т.е. это вовсе не вызов функции с двумя аргументами, как может показаться на первый взгляд. Это два последовательных вызова функции. Мы вызываем sum с аргументом 2 и считаем, что результатом данного вызова является другая функция, которую мы вызываем еще раз. Аналогичный код на C# выглядел бы так:</p>
<pre class="scroll"><code style="font-size: 8pt; font-family: Consolas;" class="csharp">sum(2)(2)</code></pre>
<p>Не слишком красиво, правда? А без скобочек все выглядит очень даже прилично.</p>
<p>Итак, начали мы с того, что стали разрабатывать синтаксис для "бесскобочного" вызова функций, а закончили тем, что придумали, как нам обходиться без функций, принимающих более одного аргумента. Все просто - мы будем создавать функции, которые возвращают другие функции! Сделать это несложно и на C#:</p>
<pre class="scroll"><code style="font-size: 8pt; font-family: Consolas;" class="csharp">Func&lt;int,int&gt; sum(int x)
{
    return y =&gt; x + y;
}</code></pre>
<p>Как вы знаете, лямбды в C# являются <i>замыканиями</i>&nbsp;- внутри тела лямбды можно обращаться к переменным, объявленным в родительской функции, что и происходит в данном случае. Благодаря этому трюку, объявление функции sum не представляет особых проблем.</p>
<p>Но, согласитесь, выглядит такая функция не очень. А что делать, если у нас будет третий параметр:</p>
<pre class="scroll"><code style="font-size: 8pt; font-family: Consolas;" class="csharp">Func&lt;int,int&gt; sum(int x)
{
    return z =&gt; y =&gt; x + y + z;
}</code></pre>
<p>Разумеется, на языке с более ловким выводом типов (или на языке с динамической типизацией) аналогичное объявление будет выглядеть несколько лаконичнее - но все равно далеко от идеала:</p>
<pre class="scroll"><code style="font-size: 8pt; font-family: Consolas;" class="csharp">sum x = \z -&gt; \y -&gt; x + y + z</code></pre>
<p>Но ведь на самом деле это не более чем проблема синтаксиса. Почему бы не добавить немного "сахара"? В сущности мы можем объявлять функции, используя тот же синтаксис, что и при вызове:</p>
<pre class="scroll"><code style="font-size: 8pt; font-family: Consolas;" class="csharp">sum x y z = x + y + z</code></pre>
<p>А компилятор сам разберется, что ему надо делать. (Фактически, оба наших определения sum полностью равносильны, просто второе избавляет нас от необходимости вручную описывать замыкания).</p>
<p>Итак, мы доказали, что писать на языке, где все функции принимают ровно один аргмент, вполне можно. Более того, никаких особенных проблем это не вызывает. Конечно, не мы с вами сейчас все это придумали. Автором описанной здесь концепции является американский математик Хаскелл Карри, в честь которого, собственно, функции, аналогичные нашему sum, и были названы <i>каррированными</i>.</p>
<p>Однако самый главный вопрос пока остался без ответа - а зачем, собственно, нам потребовались все эти ухищрения? </p>
<p>Дело в том, что каррирование открывает одну очень важную для функционального программирования возможность - а именно <i>частичное применение</i>&nbsp;функций.&nbsp;</p>
<p>Представьте, что у нас есть функция sum вида \x -&gt; y\ -&gt; x + y. Что произойдет, если при вызове этой функции мы укажем лишь один аргумент? Например, вот так: sum 5. А произойдет следующее - мы как бы создадим новую функцию "на лету", которая умеет складывать числа с 5. Конечно, на основе примера с суммированием оценить преимущества этой техники непросто, однако все становится достаточно очевидно, стоит нам взять чуть более сложную функцию - например, foldl.</p>
<p>Функция foldl - это широко распространенная функция высшего порядка, аналоги которой есть в стандартных библиотеках практически всех функциональных языков. Есть она даже в C# - под видом Aggregate. Данная функция принимает список, первоначальное значение для аккумулятора и другую функцию, которую она как бы "обворачивает" вокруг всех элементов списка (отсюда и название - fold). Функция foldl является такой важной, потому что через нее можно выразить множество других операций. Мы уже коснулись этого в предыдущий раз, когда рассматривали различные варианты использования Aggregate.</p>
<p>Попробуем теперь повторить то же самое на Haskell. Для начала - суммирование всех элементов списка, которое вы уже видели:</p>
<pre class="scroll"><code style="font-size: 8pt; font-family: Consolas;" class="csharp">sum = foldl (+) 0</code></pre>
<p>Здесь все просто - операторы в Haskell (как и во многих других функциональных языках) так же являются функциями, поэтому нам нет нужды отдельно описывать операцию сложения, ведь это уже сделали до нас. Поэтому просто передаем в качестве первого аргумента функцию (+). Второй аргумент - первоначальное значениие аккумулятора. У нас это 0. А третий аргумент, собственно, список, элементы которого нужно сложить, мы "забыли". Что получается в итоге? Новая функция, принимающая список и складывающая его элементы.</p>
<p>Попробуем теперь описать функции поиска максимального элемента. Писать ее "в лоб" не очень правильно. Ведь эта функция является частным случаем более общей функции - поиска элемента по условию. Поэтому сначала опишем ее:</p>
<pre class="scroll"><code style="font-size: 8pt; font-family: Consolas;" class="csharp">elemBy p (x:xs) = foldl (\x y -&gt; if p x y then x else y) x xs</code></pre>
<p>Здесь мы принимаем предикат и список. Странное выражение (x:xs) - это паттерн, "разбивающий" наш список на первый элемент (голову) и все оставшиеся элементы (хвост). Далее все достаточно очевидно. Теперь, с помощью elemBy мы можем реализовать функцию поиска максимального элемента так:</p>
<pre class="scroll"><code style="font-size: 8pt; font-family: Consolas;" class="csharp">maximum = elemBy (&gt;)</code></pre>
<p>А минимального - так:</p>
<pre class="scroll"><code style="font-size: 8pt; font-family: Consolas;" class="csharp">minimum = elemBy (&lt;)</code></pre>
<p>Не пугайтесь, если какие-либо примеры вам могут быть не до конца понятны - все же изучение Хаскелла в рамки данной заметки не входит. Главное, чтобы был понятен общий принцип.</p>
<p>Кстати, из-за того, как работает частичное применение, основанное на каррированных функциях, порядок аргументов у нас не совсем такой, к какому вы привыкли. Например, если бы на C# вы писали функцию фильтрации списка на основе предиката, первым бы аргументом у вас был бы сам список, а вторым - функция-предикат. Здесь же все наоборот. И благодаря этому вы сможете частично применить функцию фильтрации, например, вот так:</p>
<pre class="scroll"><code style="font-size: 8pt; font-family: Consolas;" class="csharp">nat = filter (&gt;0) --выбираем все натуральные числа</code></pre>
<p>Но тут возникает резонный вопрос - а что делать, если у нас все-таки возникает необходимость частично применить функцию, указав второй параметр, а не первый? Ответ прост, сделать это можно с помощью специальной функции-помощника, которая может выглядеть, к примеру, так:</p>
<pre class="scroll"><code style="font-size: 8pt; font-family: Consolas;" class="csharp">flip f x y = f y x</code></pre>
<p>Мы принимаем функцию и два ее аргумента и вызываем эту функцию, передавая аргументы в обратном порядке. А вот пример использования flip:</p>
<pre class="scroll"><code style="font-size: 8pt; font-family: Consolas;" class="csharp">reverse xs = foldl (flip (:)) [] xs</code></pre>
<p>Мы создали функцию, которая "переворачивает" элементы списка в обратном порядке на основе foldl. Для конструирования списка мы используем стандартную функцию-конструктор списков (:). Данная функция в качестве первого аргумента принимает элемент, а в качестве второго - список, в голову которого этот элемент добавляется. Здесь же нам нужен обратный порядок, чего мы и добиваемся с помощью flip.</p>
<p>Вот, собственно, и весь секрет.</p>
<p>Таким образом, <i>каррирование&nbsp;</i>- это преобразование функции вида (a -&gt; b) -&gt; c в функцию вида a-&gt;b-&gt;c или, говоря человеческим языком, обычной, принимающей несколько параметров функции в такую, которая принимает параметры по одному (и представляет собой цепочку вложенных функций). В большинстве функциональных языков все функции как правило уже каррированные, т.е. несмотря на видимую простоту своих объявлений, представляют собой этакие цепочки замыканий. А <i>частичное применение</i> - это вызов фукнции, в котором мы указываем лишь часть ее аргументов. И каррирование представляет собой одну из возможных технологий, с помощью которой мы можем получить механизм для частичного применения.</p>]]></content:encoded>
    </item>
    <item>
      <title>Функциональный и императивный</title>
      <link>https://docs.microsoft.com/archive/blogs/vorov/83</link>
      <pubDate>Mon, 18 Apr 2011 01:46:00 GMT</pubDate>
      <dc:creator><![CDATA[Василий Воронков]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/vorov/2011/04/18/83/</guid>
      <description><![CDATA[А какой код, собственно, является императивным? Как ни странно, это не самый тривиальный вопрос....]]></description>
      <content:encoded><![CDATA[<p>А какой код, собственно, является императивным? Как ни странно, это не самый тривиальный вопрос. "Императивный" часто противопоставляется "функциональному", когда формально эти понятия не связаны. Правда в том, что функциональный код <em>по идее</em> не является императивным, но, согласитесь, что подобная формулировка не слишком хорошо подходит для определения.</p>
<p>Термин "императивный" должен противоставляться "декларивному". И вот мы имеем еще один термин с весьма расплывчатым определением. Во-первых, "декларативный" далеко не обязательно означает функциональный. Более того, найти какой-либо критерий, по которому можно однозначно разделять код на императивный и декларативный, не так просто как кажется на первый взгляд. Казалось бы, зачем мудрить? "Императивный" предполагает, что мы описываем <em>как</em> должна быть выполнена задача, фиксируем конкретную цепочку алгоритма, тогда как "декларативный" сводится лишь к декларации наших намерений, к описанию того <em>что</em> мы хотим получить. Все хорошо в теории, но на практике, когда речь заходит о реальных языках программировния, понимаешь, насколько это разделение по принципу "что" и "как" пространное и субьективное. По большому счету любое утверждение вида "язык Foo является декларативным, а язык Bar не является декларативным" некорректно. Правильнее говорить - "язык Foo более декларативен, чем язык Bar".</p>
<p>И как только мы начинаем понимать "декларативность" языка как чисто количественную, если можно так выразиться, характеристику, все становится предельно просто. Что мы, собственно, хотим сказать, утверждая, что язык Foo декларативен? Какую такую его особенность мы пытаемся описать?</p>
<p>Возьмем для примера простейшую задачу. Нам нужно выбрать из массива все отрицательные числа. Вот как, к примеру, ее можно решить на C#:&nbsp;</p>
<pre class="scroll"><code class="csharp" style="font-size:8pt;font-family:Consolas,Courier New; ">var list = new List&lt;int&gt;();
var i = 0;

START:
    if (i == arr.Length)
        goto FINISH; 
    var e = arr[i];

    if (e &lt; 0)
        list.Add(e);

    i++;
    goto START;
FINISH:
</code></pre>
<p>Что мы можем сказать про этот код? Ну кроме того, что он вряд ли прошел бы у вас code review. Мы здесь видим достаточно низкоуровневое решение поставленной задачи (даже несмотря на то, что мы не работаем напрямую с памятью и используем динамический типизированный массив, List&lt;T&gt;, для представления всех элементов исходной последовательности, которые удовлетворяют нашему условию).</p>
<p>В целом понятно, кого мы тут можем винить в "низкоуровневости". Конечно же, это печально известный goto, который недалеко ушел от соответствующих конструкций ассемблера - Br, Brtrue и пр. В результате этот листинг кода демонстрирует нам множество деталей реализации, без которых, скажем так, мы вполне могли бы прожить. На входе у нас была простая и понятная бизнес-задача - к примеру, получить всех должников по кредиту. А что мы имеем на выходе?</p>
<p>Непорядок. Давайте попробуем улучшить нашу реализацию:</p>
<pre class="scroll"><code class="csharp" style="font-size:8pt;font-family:Consolas,Courier New; ">var list = new List&lt;int&gt;();

for (var i = 0; i &lt; arr.Length; i++) {
    var e = arr[i];

    if (e &lt; 0)
        list.Add(e);
}</code></pre>
<p>Так, это уже выглядит более привычно. Количество строчек кода заметно сократилось. А главное - читается этот пример гораздо проще, чем предыдущий, правда? Можно даже сказать - этот код более декларативен, чем предыдущий. А почему бы и нет? Вместо меток и условных переходов мы используем более, скажем так, наглядную конструкцию, которая прячет от нас все эти "детали реализации". Если вы скомпилируете этот код и посмотрите сгенерированный MSIL, то увидите, что все наши goto по-прежнему там, только теперь их созданием занимаемся не мы сами, а компилятор. И все это благодаря простой и понятной конструкции for.</p>
<p>Но знаете что, все-таки на самом деле этот код совсем не декларативный. Да, мы избавились от явного goto, но то, что мы получили, не слишком сильно от него отличается. Мы заводим переменную-счетчик для цикла, проверяем условие на выход из цикла, получаем элемент по индексу. Какая же это декларация? Это же явно заданная последовательность действий. И виноват во всем этом все тот же for.</p>
<p>Давайте сделаем наш код еще лучше:</p>
<pre class="scroll"><code class="csharp" style="font-size:8pt;font-family:Consolas,Courier New; ">var list = new List&lt;int&gt;();

foreach (var e in arr)
    if (e &lt; 0)
        list.Add(e);
</code></pre>
<p>Вот это уже другое дело! Посмотрите мне в глаза и скажите, что этот код не декларативен! Особенно, если сравнить его с самым первым примером. Да он ведь даже читается практически как предложение на чистейшем английском языке - "для каждого е в arr...". А еще этот код более универсален. Мы больше не получаем элементы по индексу и можем работать практически с любой <em>перебираемой</em> последовательностью, которая реализует специальную абстракцию - ай-перебиратель. Ну, впрочем, вы и сами все знаете.</p>
<p>Ну так что, это все? Мы уже достигли вершин декларативности? Конечно же, нет. Вообще наш пример с foreach является скорее ярким примером императивного подхода. Да, мы избавились от низкоуровневых деталей работы с массивом, но у нас по-прежнему куча последовательных инструкций - перебрать все элементы, сравнить каждый элемент с нулем и т.д. и т.п.</p>
<p>Как сделать этот код еще лучше? Легко:</p>
<pre class="scroll"><code class="csharp" style="font-size:8pt;font-family:Consolas,Courier New; ">var list = arr.Where(e =&gt; e &lt; 0);</code></pre>
<p>А вот это уже больше похоже на настоящую декларативную программу. Не нужно никаких циклов - мы всего лишь комбинируем две функции и получаем нужный результат. (Вы можете сказать, что пример этот не совсем "честный" - ведь мы используем функцию Where из Linq. Но никакой проблемы тут нет. Конструкцию foreach тоже ведь кто-то должен реализовать - причем совершенно неважно, реализована ли она на уровне компилятора или библиотеки).</p>
<p>Фактически, как вы понимаете, наши примеры отличаются друг от друга "уровнем абстракции". Сначала мы избавились от goto, от описания механизма условных переходов, затем абстрагировались от конкретной реализации последовательности, содержащей целые числа, которые нам нужно перебрать, и наконец избавились и от самого перебора. Что осталось в итоге? Остались абстракции, представленные в виде функций.</p>
<p>И вот тут мы уже вплотную подходим к тому, ради чего, собственно, и затевался весь этот разговор - к функциональному программированию.</p>
<p>Программа на функциональном языке не оперирует данными - это удел более низкоуровневых языков, таких как C# (хотя и на C#, как вы видите, многие приемы функционального программирования вполне реализуемы). Вместо данных, на функциональном языке мы совершаем операции над функциями. </p>
<p>Выборка из последовательности - это лишь комбинация функции фильтрации и предиката. Проще говоря, вместо того, чтобы рассуждать в стиле, какую последовательность действий нам нужно совершить, чтобы получить результат, мы задаем другой вопрос - комбинация каких функций будет порождать требуемую нам в данному случае функцию. </p>
<p>Возьмем другой пример. Положим, нам нужно сделать суммирование элементов. Нет нужды описывать собственную функцию с нуля, воспользуемся Aggregate:</p>
<pre class="scroll"><code class="csharp" style="font-size:8pt;font-family:Consolas,Courier New; ">var arr = new int[] { 1, 2, 3, 4, 5 };
arr.Aggregate(0, (x,y) =&gt; x + y);</code></pre>
<p>Нужно получить максимальный элемент в последовательности? Снова Aggregate:</p>
<pre class="scroll"><code class="csharp" style="font-size:8pt;font-family:Consolas,Courier New; ">var res = arr.Aggregate((x,y) =&gt; x &gt; y ? x : y);</code></pre>
<p>Мы просто думаем в категориях функций, а не данных, что возможно делать даже на C#, хотя этот язык с трудом можно назвать функциональным. Функция становится для нас той самой абстракцией, которая не просто прячет от нас детали реализации, а вообще изолирует нас от работы с данными. Иначе говоря, мы описываем данные через функции.</p>
<p>Разумеется, на настоящем функциональном языке делать это немного удобнее. Вот как, к примеру, выглядит функция суммирования на Хаскеле (foldl - аналог Aggregate):</p>
<pre class="scroll"><code class="csharp" style="font-size:8pt;font-family:Consolas,Courier New; ">xs = [1..5]
sum = foldl (+) 0
res = sum xs</code></pre>
<p>И практически так же на <a target="_blank" href="https://code.google.com/p/elalang" title="Ela">Ela</a>:</p>
<pre class="scroll"><code class="csharp" style="font-size:8pt;font-family:Consolas,Courier  New">let xs = [1..5]
let sum = foldl (+) 0
let res = sum xs</code></pre>
<p>Сейчас мне полагается убеждать вас, что этот код декларативный, наглядный и прочая и прочая. И очень часто на этом этапе знакомство с функциональными языками и заканчивается. Когда я показал этот код своему коллеге, он надолго задумался, а потом задал мне один (ну, честно говоря, далеко не один, но этот был самый цензурный) вопрос - а сколько аргументов принимает foldl? Правильный ответ - всего лишь один.</p>
<p>Но об этом - в следующий раз.</p>]]></content:encoded>
    </item>
    <item>
      <title>Функциональное программирование изменит мир</title>
      <link>https://docs.microsoft.com/archive/blogs/vorov/1-2</link>
      <pubDate>Fri, 15 Apr 2011 03:01:00 GMT</pubDate>
      <dc:creator><![CDATA[Василий Воронков]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.msdn.microsoft.com/vorov/2011/04/15/1-2/</guid>
      <description><![CDATA[Видимо, у каждого в жизни наступает такое время, когда просто не можешь не завести какой-нибудь...]]></description>
      <content:encoded><![CDATA[<p>Видимо, у каждого в жизни наступает такое время, когда просто не можешь не завести какой-нибудь технический блог. Вот и я не избежал такой участи.</p>
<p>Не могу не признать, что существенное влияние тут оказал и тот факт, что с недавнего времени я являюсь сотрудником компании Майкрософт, а следовательно, могу создать блог на такой популярной и уважаемой платформе как MSDN.</p>
<p>Впрочем, рассказывать я буду не только о продуктах Майкрософт, а о самых разных языках программирования, которые объединяет одна черта. </p>
<p>Но для начала несколько слов о себе. </p>
<p>Я не являюсь евангелистом и использую языки программирования по их прямому назначению. Более того, как многие из нас, я имею обширный опыт работы с преимущественно императивными языками программирования, такими как C#. Однако некоторое время назад со мной что-то произошло, и отношение мое к программированию кардинально изменилось. Нет, мне по-прежнему нравится писать программы, и я страшно доволен тем, что могу заниматься свои любимым делом и даже получать за это деньги. Но теперь, когда я смотрю на код, меня начинают удивлять казалось бы самые обычные вещи - передача параметров через ref, присваивание значений переменным, которые уже были вполне успешно проинициализированы, внутренние, скрытые от нашего глаза изменяемые состояния у обьектов, странные функции, которые ничего не принимают и ничего не возвращают. Что же в этом такого удивительного, спросите вы. Самые обычные вещи. Может, мне просто стоит взять отпуск и немного отдохнуть от программирования?</p>
<p>Но, увы - проверено. Отпуск не помогает. Я как будто просто потерял способность воспринимать эти самые обычные вещи так же, как и раньше. Помешательство? Да, возможно. И помешательство это называется "функциональное программирование".</p>
<p>Вы, наверное, уже догадывались, что все любители функционального программирования состоят в тайной секте, главная задача которой - мировое господство, не меньше. Поэтому начну я довольно традиционно.</p>
<p>Функциональное программирование изменит мир. </p>
<p>Да, я в курсе, что оно пытается сделать это с конца 50-х, но откуда вы знаете, что мир уже не изменился? Кто может сказать, на что была бы похожа современная индустрия, если бы в свое время, в 1958 году, Джон Маккарти не изобрел бы язык под названием Лисп. Но если даже и не мир, то наш собственный образ мышления, наше отношение к программированию, может весьма заметно измениться. Что хорошо видно на моем примере.</p>
<p>Кстати, о моем помешательстве. Оно зашло так далеко, что я начал создавать свой собственный функциональный язык, который называется Ela (не спрашивайте меня о названии, все равно я не смогу ответить). Естественно, я не могу не воспользоваться возможностью, чтобы отрекламировать его здесь. Скачать последнюю версию этого языка, а также книгу о нем можно по этой <a href="https://code.google.com/p/elalang/downloads/list?can=3&amp;q=&amp;colspec=Filename+Summary+Uploaded+ReleaseDate+Size+DownloadCount">ссылке</a>.</p>
<p>Ela относится к категории языков "решай задачи в функциональном стиле или же разбей клавиатуру о стену". Это значит, что Ela немного не похожа на C#. Например, в ней нет изменяемых переменных и конструкций для организации циклов. Нет многих "встроенных" в язык операторов, даже таких банальных как сложение или умножение (хотя вы, конечно, можете определить их сами). Нет классов, наследования, итераторов. Даже нет функций, которые принимают больше одного аргумента. Так что же в ней есть?</p>
<p>Наверняка вы сейчас думаете, что Ela похожа на какой-нибудь Brainfuck, но в действительности у нее куда более благородные намерения в отношении вашего мозга. Никакой эзотерики! Как раз напротив, Ela делает все для того, чтобы код был наиболее наглядным и читабельным. Оказывается, что для этого не нужны ни циклы, ни изменяемые переменные. Зачем нам циклы, если мы собираемся писать код в функциональном стиле?</p>
<p>Но что же все-таки означает "писать код в функциональном стиле"? Наверняка многие из вас уже пытались разобраться в том, что представляет собой функциональное программирование, и какие языки можно с уверенностью относить к функциональным, а какие - нет. И наверняка вы уже в курсе, что существует множество определений функциональной парадигмы, которые зачастую совершенно не помогают разобраться в том "что есть что". Действительно, если определений много - то какому из них верить? Один твердит, что JavaScript - это самый настоящий функциональный язык и вполне убедительно это аргументирует. Другой - что даже такие языки как Scala, Nemerle, Lisp - ни разу не функциональные. Кто прав?</p>
<p>В серии ближайших заметок я постараюсь рассказать о том, как я сам понимаю функциональное программирование. Я не буду давать очередное определение и тем более делить все языки на "настоящие функциональные" и "ненастоящие". Я попытаюсь сделать другое - показать ту тонкую грань, переходя через которую, вы перестаете писать императивный код и становитесь... да, да, членом нашей тайной секты.</p>
<p>Все дело в том, что язык программирования - это просто инструмент, а использовать любой инструмент можно по-разному. Не знаю, является ли тот же JavaScript "настоящим" функциональным языком или нет, но писать функциональный код на нем вполне можно. Как можно писать его и на C#.</p>
<p>Но обо всем этом, как говорится, в следующих сериях.</p>]]></content:encoded>
    </item>
  </channel>
</rss>