Здравствуйте, уважаемые читатели! Наши искания в области языка C# серьезно перекликаются с этой статьей, автор которой — специалист по функциональному программированию на C#. Статья — отрывок из готовящейся книги, поэтому в конце поста предлагаем за эту книгу проголосовать.



Многие программисты неявно подразумевают, что «функциональное программирование (ФП) должно реализовываться только на функциональном языке». C# — объектно-ориентированный язык, поэтому не стоит и пытаться писать на нем функциональный код.

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

Итак, напрашивается вопрос: насколько хорош нынешний язык C# для функционального программирования? Перед тем, как ответить на этот вопрос, я поясню, что понимаю под «функциональным программированием». Это парадигма, в которой:

  1. Делается акцент на работе с функциями
  2. Принято избегать изменения состояния

Чтобы язык способствовал программированию в таком стиле, он должен:

  1. Поддерживать функции как элементы 1-го класса; то есть, должна быть возможность трактовать функцию как любое другое значение, например, использовать функции как аргументы или возвращаемые значения других функций, либо хранить функции в коллекциях
  2. Пресекать всякие частичные «местные» замены (или вообще сделать их невозможными): переменные, объекты и структуры данных по умолчанию должны быть неизменяемыми, причем должно быть легко создавать модифицированные версии объекта
  3. Автоматически управлять памятью: ведь мы создаем такие модифицированные копии, а не обновляем данные на месте, и в результате у нас множатся объекты. Это непрактично в языке, где отсутствует автоматическое управление памятью

С учетом всего этого, ставим вопрос ребром:

Насколько язык C# — функциональный?

Ну… давайте посмотрим.

1) Функции в C# — действительно значения первого класса. Рассмотрим, например,
следующий код:

Func<int, int> triple = x => x * 3;
var range = Enumerable.Range(1, 3);
var triples = range.Select(triple);
triples // => [3, 6, 9]

Здесь видно, что функции – действительно значения первого класса, и можно присвоить функцию переменной triple, после чего задать ее в качестве аргумента Select.

На самом деле, поддержка функций как значений первого класса существовала в C# с первых версий языка, это делалось при помощи типа Delegate. Впоследствии были введены лямбда-выражения, и поддержка этой возможности на уровне синтаксиса только улучшилась.

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

2) В идеале, язык также должен пресекать местные замены. Здесь – самый крупный недостаток C#; все по умолчанию изменяемо, и программисту требуется немало потрудиться, чтобы обеспечить неизменяемость. (Сравните с F#, где переменные по умолчанию неизменяемы, и, чтобы переменную можно было менять, ее требуется специально пометить как mutable.)

Что насчет типов? Во фреймворке есть несколько неизменяемых типов, например, string и DateTime, но определяемые пользователем изменяемые типы в языке поддерживаются плохо (хотя, как будет показано ниже, ситуация немного исправилась в C#, и в последующих версиях также должна улучшаться). Наконец, коллекции во фреймворке являются изменяемыми, но уже имеется солидная библиотека неизменяемых коллекций.

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

Итак, в C# очень хорошо поддерживаются некоторые (но не все) приемы функционального программирования. Язык эволюционирует, и поддержка функциональных приемов в нем улучшается.

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

Функциональная сущность LINQ

Когда вышел язык C# 3 одновременно с фреймворком .NET 3.5, там оказалась масса возможностей, по сути заимствованных из функциональных языков. Часть из них вошла в библиотеку (System.Linq), а некоторые другие возможности обеспечивали или оптимизировали те или иные черты LINQ – например, методы расширения и деревья выражений.

В LINQ предлагаются реализации многих распространенных операций над списками (или, в более общем виде, над «последовательностями», именно так с технической точки зрения нужно называть IEnumerable); наиболее распространенные из подобных операций – отображение, сортировка и фильтрация. Вот пример, в котором представлены все три:

Enumerable.Range(1, 100).
   Where(i => i % 20 == 0).
   OrderBy(i => -i).
   Select(i => $”{i}%”)
// => [“100%”, “80%”, “60%”, “40%”, “20%”]

Обратите внимание, как Where, OrderBy и Select принимают другие функции в качестве аргументов и не изменяют полученный IEnumerable, а возвращают новый IEnumerable, иллюстрируя оба принципа ФП, упомянутые мною выше.

LINQ позволяет запрашивать не только объекты, находящиеся в памяти (LINQ to Objects), но и различные иные источники данных, например, SQL-таблицы и данные в формате XML. Программисты, работающие с C#, признали LINQ в качестве стандартного инструментария для работы со списками и реляционными данными (а на такую информацию приходится существенная часть любой базы кода). С одной стороны это означает, что вы уже немного представляете, каков из себя API функциональной библиотеки.

С другой стороны, при работе с иными типами специалисты по C# обычно придерживаются императивного стиля, выражая задуманное поведение программы в виде последовательных инструкций управления потоком. Поэтому большинство баз кода на C#, которые мне доводилось видеть – это чересполосица функционального (работа с IEnumerable и IQueryable) и императивного стиля (все остальное).

Таким образом, хотя C#-программисты и в курсе, каковы достоинства работы с функциональной библиотекой, например, с LINQ, они недостаточно плотно знакомы с принципами устройства LINQ, что мешает им самостоятельно использовать такие приемы при проектировании.
Это – одна из проблем, решить которые призвана моя книга.

Функциональные возможности в C#6 и C#7

Пусть C#6 и C#7 и не столь революционны, как C#3, эти версии привносят в язык множество мелких изменений, которые в совокупности значительно повышают удобство работы и идиоматичность синтаксиса при написании кода.

ПРИМЕЧАНИЕ: Большинство нововведений в C#6 и C#7 оптимизируют синтаксис, а не дополняют функционал. Поэтому, если вы работаете со старой версией C#, то все равно сможете пользоваться всеми приемами, описанными в этой книге (разве что ручной работы будет чуть больше). Однако, новые возможности значительно повышают удобочитаемость кода, и программировать в функциональном стиле становится приятнее.

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

Листинг 1. Возможности C#6 и C#7, важные в контексте функционального программирования

using static System.Math;                          <1>
public class Circle
{
   public Circle(double radius) 
      => Radius = radius;                          <2>
   public double Radius { get; }                   <2>
   public double Circumference                     <3>
      => PI * 2 * Radius;                          <3>
   
   public double Area
   {
      get
      {
         double Square(double d) => Pow(d, 2);     <4>
         return PI * Square(Radius);
      }
   }
   public (double Circumference, double Area) Stats   <5>
      => (Circumference, Area);
}

  1. using static обеспечивает неквалифицированный доступ к статическим членам System.Math, например, PI и Pow ниже
  2. Авто-свойство getter-only можно установить только в конструкторе
  3. Свойство в теле выражения
  4. Локальная функция – это метод, объявленный внутри другого метода
  5. Синтаксис кортежей C#7 допускает имена членов

Импорт статических членов при помощи «using static»

Инструкция using static в C#6 позволяет “импортировать” статические члены класса (в данном случае речь идет о классе System.Math). Таким образом, в нашем случае можно вызывать члены PI и Pow из Math без дополнительной квалификации.

using static System.Math;
//...
public double Circumference
   => PI * 2 * Radius;

Почему это важно? В ФП приоритет отдается таким функциям, поведение которых зависит лишь от их входных аргументов, поскольку можно отдельно протестировать каждую такую функцию и рассуждать о ней вне контекста (сравните с методами экземпляров, реализация каждого из них зависит от членов экземпляра). Эти функции в C# реализуются как статические методы, поэтому функциональная библиотека в C# будет состоять в основном из статических методов.

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

Более простые неизменяемые типы с getter-only авто-свойствами

При объявлении getter-only авто-свойства, например, Radius, компилятор неявно объявляет readonly резервное поле. В результате значение этим свойствам может быть присвоено лишь в конструкторе или внутристрочно.

public Circle(double radius) 
   => Radius = radius;
public double Radius { get; }

Getter-only автосвойства в C#6 облегчают определение неизменяемых типов. Это видно на примере класса Circle: в нем есть всего одно поле (резервное поле Radius), предназначенное только для чтения; итак, создав Circle, мы уже не можем его изменить.

Более лаконичные функции с членами в теле выражения

Свойство Circumference объявляется вместе с «телом выражения», которое начинается с =>, а не с обычным «телом инструкции», заключаемым в { }. Обратите внимание, насколько лаконичнее этот код по сравнению со свойством Area!

public double Circumference
   => PI * 2 * Radius;

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

Синтаксис с объявлением в теле выражения появился в C#6 для методов и свойств, а в C#7 стал более универсальным и применяется также с конструкторами, деструкторами, геттерами и сеттерами.

Локальные функции

Если приходится писать множество простых функций, это означает, что зачастую функция вызывается всего из одного места. В C#7 это можно запрограммировать явно, объявляя методы в области видимости метода; например, метод Square объявляется в области видимости геттера Area.

get
{
   double Square(double d) => Pow(d, 2);
   return PI * Square(Radius);
}

Оптимизированный синтаксис кортежей

Пожалуй, в этом заключается важнейшее свойство C#7. Поэтому можно с легкостью создавать и потреблять кортежи и, что самое важное, присваивать их элементам значимые имена. Например, свойство Stats возвращает кортеж типа (double, double), но дополнительно задает значимые имена для элементов кортежа, и по ним можно обращаться к этим элементам.

public (double Circumference, double Area) Stats
   => (Circumference, Area);

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

В будущем язык C# станет функциональнее?

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

  • Регистрируемые типы (неизменяемые типы без трафаретного кода)
  • Алгебраические типы данных (мощное дополнение к системе типов)
  • Сопоставление с шаблоном (напоминает оператор `switch` переключающий “форму” данных, например, их тип, а не только значение)
  • Оптимизированный синтаксис кортежей

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

С другой стороны, такие возможности планируются в следующих версиях, и уже идет проработка соответствующих предложений. Таким образом, в будущем мы, вероятно, увидим в C# регистрируемые типы и сопоставление по шаблону.

Итак, C# и далее будет развиваться как мультипарадигмальный язык со все более выраженным функциональным компонентом.
Актуальность темы

Проголосовало 173 человека. Воздержалось 87 человек.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Поделиться с друзьями
-->

Комментарии (54)


  1. pygubanov
    10.02.2017 12:26

    Можете привести реального примера с применением такого функционального подхода?


  1. Magic_B
    10.02.2017 13:12
    +1

    Могу в течении дня выложить на гит-хаб свою библиотеку и примеры использования. Выглядит код как-то так в итоге:
    Action в MVC-контроллере добавления в корзину:

    public IActionResult Add(string a, string b, string c, string d, int count = 1, int mod=0)
      => _dal.GetUrl(a, b, c, d).Convert(_dal.GetProduct)
         .IfNotNull(x => 
            _dal.AddProductToCart(_dal.GetCartBySession(HttpContext.Session.Id), x, count, mod)
         .Extend(x).Convert(PartialView), () => (IActionResult) NotFound());
    

    Понажимайте «вверх», кого заинтересует раскрытие данной темы.
    У меня уже два рабочих проекта в таком стиле сделано. Но есть два минуса такого подхода, могу позже объяснить что к чему…


    1. xGromMx
      10.02.2017 14:35

      Extend это из комонады?


      1. Magic_B
        10.02.2017 14:43

        Extend — это функция создающая Tuple из входящего Tuple или одного типа, добавляя к нему еще один элемент. Например, есть Tuple<int, double>, тогда Extend(Tuple<int, double>, string) вернет Tuple<int, double, string>. В данном случае, моделью для вьюшки будет являться Tuple<Cart, Product>.


        1. xGromMx
          10.02.2017 14:48

          оно делает тройку?


          1. Magic_B
            10.02.2017 14:49

            Вы имеете ввиду, Tuple<T1,T2,T3>? Хоть пятерку… Но это не очень хорошо, использовать такие длинные записи для логики…


            1. xGromMx
              10.02.2017 14:54

              и кто сказал что это фп? то что вы сделали флюент это еще не значит, где правильный карринг и все остальное? Ваши идеи можно сделать с помощью монадических цепочек + Maybe/Either. Также Rx достаточно самодостаточен и очень приближен к идеям фп. Есть еще вариант с Validation монадой, если надо накапливать причины ошибок


              1. Magic_B
                10.02.2017 15:09

                Я и не говорю что это ФП, даже намека не делаю, но такой стиль написания на C# имеет место быть и может называться функциональным стилем.


                1. xGromMx
                  10.02.2017 15:09

                  так это же просто флюент =)


                  1. Magic_B
                    10.02.2017 15:30
                    +2

                    Да, это просто Fluent! И он прекрасен!!!
                    Я не совсем понимаю, что Вы хотели увидеть… Разговор ведется в рамках императивного языка программирования C#, я использую лишь другую запись, и не собираюсь делать из C# F#…


                    1. xGromMx
                      10.02.2017 15:32

                      Было бы не плохо написать интерфейс для LINQ


        1. Deosis
          13.02.2017 08:29

          Когда завезут Variadic Templates?
          У вас ведь написаны 8 вариантов функции Extend?
          У вас учитывается, что в Tuple<T1,T2,T3,T4,T5,T6,T7,TRest> последний параметр должен быть тоже Tuple?


          1. Magic_B
            13.02.2017 08:49

            В C# 7.0 есть записи. Нет, не учитывается, если вы имеете ввиду «склейку» Tuple. Вообще, я выше говорил, что не стоит использовать такие громоздкие записи, где будет Item7 например, это понижает читаемость, хотя дает типизацию.


    1. xFFFF
      10.02.2017 14:52

      Было бы интересно почитать плюсы и минусы!


      1. Magic_B
        13.02.2017 10:20

        Попытался описать минусы, плюсы и кое-какие советы по использование. См. readme в репозитории.


    1. Magic_B
      10.02.2017 15:42

      ссылка на репозиторий
      Осталось сделать рабочий компилируемый пример и описать плюсы и минусы…
      Кому интересна данная тема, милости просим!


      1. Magic_B
        13.02.2017 15:25

        Залил примерчик


  1. Vadem
    10.02.2017 14:06
    +1

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


    1. xGromMx
      10.02.2017 14:35

      помимо F# есть еще такой интересный проект http://elalang.net/


    1. Varim
      10.02.2017 15:46

      контроля за сайд-эффектами
      А это что, можете пример привести? Вы про что то вроде указать методу ключевое слово «pure» и что бы компилятор запрещал изменять состояния?


      1. Vadem
        10.02.2017 20:38
        -2

        Да что-то вроде этого.


        1. Vadem
          14.02.2017 10:44

          Разверну мысль.
          Для функционального программирования очень важно чтобы функции не имели видимых побочных эффектов. Например в Haskell все функции по умолчанию чистые. Очевидно, что в C# это не так.
          И мне интересно можно ли как-то пойти обратным путём и как-то контролировать побочные эффекты функций.
          Например, есть System.Diagnostics.Contracts.PureAttribute и JetBrains.Annotations.PureAttribute, но, насколько я понимаю, они не контролируют побочные эффекты метода.
          Есть даже пропозал C# Pure Function Keyword to Mark No Side Effects Or External Dependencies, но пока, увы, только на рассмотрении.


          1. 0xd34df00d
            14.02.2017 18:23

            Например в Haskell все функции по умолчанию чистые.

            Они там не по умолчанию чистые, они просто чистые. Есть некоторая тонкость с определением функций в ST-монаде, но она скорее техническая, ИМХО.


            А IO — это такое ST с особым тегом, если хотите.


    1. ad1Dima
      10.02.2017 15:57

      Если под ленивостью понимается ленивая инициализация и исполнения, то на этом весь Linq построен.


      1. Vadem
        14.02.2017 10:59

        Насколько я понимаю, LINQ состоит из нескольких частей:
        1. SQL-подобный синтаксис, который просто транслируется в вызовы определённых методов и ничего про их ленивость не знает.
        2. Возможность в C#, которая позволяет транслировать лямбда выражения в деревья выражений. Тоже никакого отношения к ленивости не имеет.
        3. Набор методов расширений из пространств имён System.Linq, System.Xml.Linq и System.Data.Linq. Часть из которых ленивые, а часть из которых нет(Sum, All, Min, Max, и т.д.)
        Так что я не могу согласиться с тем, что «на этом(ленивость) весь Linq построен».
        Но даже если бы это было так, LINQ это только часть C#. И как раз LINQ — это кусочек функционального программирования в C#. Я слышал, что его даже хотели назвать LINM(Language Integrated Monads), но решили, что будет понятнее Queries.
        Впрочем, ленивость — это, наверное, самая незначительная вещь для ФП.


        1. ad1Dima
          14.02.2017 11:10

          Ленивое исполнение:

          var subCollection = collection.Where(o => o.someFlag = true).Select(o => o.EmbedObject).OrderBy(o => o.Title);
          // some other code
          var first = subCollection.FirstOrDefault() // <= вычисление Linq запроса произойдёт только здесь.
          


          Ленивая инициализация:
          var lazyLargeObject = new Lazy<LargeObject>(() => 
          {
              LargeObject large = new LargeObject(Thread.CurrentThread.ManagedThreadId);
              // Perform additional initialization here.
              return large;
          });
          
          LargeObject large = lazyLargeObject.Value // иничиализация инстанса LargeObject произойдет тут. 
          


          Впрочем, и то и то не возможности языка, но возможности библиотеки.


    1. ad1Dima
      10.02.2017 16:06

      А насчет каррирования, вы хотите что-то вроде https://m.habrahabr.ru/post/76545/ ?


      1. Vadem
        10.02.2017 20:52

        Да, я хочу что-то вроде этого, но только чтобы не надо было добавлять новый метод-расширение, когда у меня появляется метод с другим числом параметров.
        В принципе, конечно можно один раз сделать 100500 вариантов функции Curry один раз на все случаи жизни.
        Более того, можно взять language-ext в котором это уже сделано(правда только для Func, хорошо бы ещё и для Action).
        Но в общем, согласен, что это решает проблему с каррированием.


    1. sshikov
      20.02.2017 14:56

      На самом деле, многое из того что вы написали, совершенно необязательно.

      Скажем, Haskell язык ленивый, а Scala нет. Это делает скалу другим языком, нежели Haskell, но это никаким образом не делает скалу «не ФП» языком. Тоже самое и с побочными эффектами. Чистота функций — это очень полезное свойство, но ее отсутствие тоже не приговор.

      Это ортогональные вещи.

      Впрочем, если вы хотели сказать, что таких как C# «почти функциональных» языков сегодня пруд пруди — так это чистая правда.


      1. ad1Dima
        20.02.2017 17:04

        Впрочем, если вы хотели сказать, что таких как C# «почти функциональных» языков сегодня пруд пруди — так это чистая правда.

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


  1. Varim
    10.02.2017 15:41

    Регистрируемые типы (неизменяемые типы без трафаретного кода)
    Что такое изменяемые типы? В c# в частности.
    Алгебраические типы данных (мощное дополнение к системе типов)
    Что это вообще такое


    1. wheercool
      10.02.2017 20:30
      +2

      Алгебраические типы данных — это альтернатива классам. Позволяют создавать новые типы путем композиции с использованием операции логического сложения (или) и умножения (и)

      Примеры:

      data Bool = True | False-- (логическое или). Работает примерно как enum
      data User = Name Age -- (логическое и). Объединяет 2 типа Name и Age в 1. 
      

      Аналоги в C#

      enum Bool = True, False
      class User {
          Name Name;
          Age Age;
      }
      


      Но вот чего нельзя сделать в C# — это комбинация этих операций:
      data Maybe a = Nothing | Just a -- тип значение которого может отсутствовать.
      


      В C# близко к этому null, но в отличие от ADT тут проверка будет не на этапе компиляции, а в рантайме.


      1. Sirikid
        11.02.2017 00:19
        -1

        Достаточно было привести ссылку на Википедию.


        В C# близко к этому null, но в отличие от ADT тут проверка будет не на этапе компиляции, а в рантайме.

        В Haskell проверка тоже будет в рантайме.
        Любопытный факт, Rust представляет Option<&T> (Option это аналог Maybe, &T — ссылка на T) в виде указателя на T, None в таком случае соответсвует null, а Some любому другому значению.


        1. wheercool
          11.02.2017 02:17
          +1

          В Haskell проверка тоже будет в рантайме

          Да нет же. Не знаю как в Rust, но если вы используете Maybe значит вы явно обрабатываете случай отсутствия значения.
          Возможно вы имели ввиду вот такой вариант:

          myFunction:: Maybe Int -> Int
          myFunction (Just a) = a
          

          Здесь мы явно отказываемся от кейса Nothing. Но при компиляции с -W компилятор нам об этом подскажет.
          Вообще как мне кажется это очень плохая практика использовать Maybe в качестве входного параметра функции, она ведь является монадой и Haskell сам сможет построить в случае необходимости композицию из нескольких Maybe.
          Т.е. в предыд. примере функция должна быть вроде этой:

          myFunction::Int -> Int
          myFunction a = a
          -- ну или просто
          myFunction = id
          


          1. Sirikid
            11.02.2017 03:23

            А, понял, вы имели ввиду что Maybe в отличии от null нельзя проигнорировать? (Можно: fromJust :^)) Ну да, это проблема дефолтов старых языков, более новые, например Kotlin, не разрешают таких вольностей. Хотя, насколько мне известно это единственный язык с такой системой типов — честная nullability, не скрытая под абстракцией ADT, и при этом по дефолту типы nonnullable.


  1. Vadem
    10.02.2017 21:28

    А какой популярный язык не является «почти функциональным»?
    Автор выделил 3 критерия, которым должен соответствовать язык, чтобы называться функциональным(хотя я с ними не согласен, но допустим):

    1. Иметь функции высшего порядка
    2. Неизменяемость(иммутабельность) по умолчанию
    3. Автоматическое управление памятью

    C# не соответствует пункту №2, т.е. у него есть только функции высшего порядка и сборка мусора.
    Навскидку функции высшего порядка и сборка мусора есть в D, Python, JavaScript, Java, PHP, Ruby, Swift и Go.
    Сюда ещё можно отнести C++, так как хоть у него и нет сборки мусора, но есть умные указатели и функции высшего порядка.

    P.S.: А почему убрали ссылку на Functional Programming in C#? В оригинальной статье она есть.


  1. NIKOSV
    11.02.2017 02:26

    С функциями высшего порядка нужно быть осторожнее, а то некоторые почитают о модном функциональном подходе, и начинают возводить это в абсолют в ущерб читаемости, поддержки и тестируемости кода, в стиле «смотри как я могу!». Там где можно было обойтись логической композицией имутабельных классов (старый добрый IoC/DI) и дергать их в императивном стиле, они фигачат функции с километровыми сигнатурами которые на 27'' экран не помещаются, где функция принимает функции с кучей параметров и возвращает другую функцию с кучей параметров, некоторые из которых кортежи. Потом час сидишь и втыкаешь в эту сигнатуру и пытаешься понять что вообще происходит. Не говоря уже что дебажить это все счастье практически невозможно. Зато от поганых классов и объектов избавился, это да.


  1. IndigoMan
    11.02.2017 09:40
    +1

    а что не так с F#?
    Или я что не понимаю…


    1. aikixd
      11.02.2017 12:06

      Люди передали функцию в функцию и думают что это ФП. Поэтому думают что на С# можно написать функциональный код, так что f# не нужен.


      1. Varim
        11.02.2017 12:40

        А что такое ФП? Скажите вашими словами.


        1. aikixd
          11.02.2017 19:35

          В ФП есть только типы и функции. Нет переменных, нет сравнений, нет действий.


          Например


              let a = 5
              // остальной код

          На самом деле


              (fun a -> //остальной код)(5)

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


          В ФП нет циклов, нет тупиков, вы не можете заранее выйти из функции (if (??) return;).


          На пальцах примерно так. Это совсем другой мир.


          1. ad1Dima
            12.02.2017 08:13

            Т.Е. Lisp не является функциональным языком? http://www.tutorialspoint.com/lisp/lisp_variables.htm


            1. Sirikid
              12.02.2017 09:18

              Лисп просто не чистый.


              1. ad1Dima
                12.02.2017 09:19

                Ну тогда Lisp->Ocaml->F# все они не чистые.


                1. aikixd
                  12.02.2017 12:24

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


                  1. ad1Dima
                    12.02.2017 15:00

                    а в F# вас заставляют?


                    1. aikixd
                      12.02.2017 15:18

                      Не понял вопроса. У меня проблем с F# нет.


                1. Sirikid
                  12.02.2017 19:25

                  Да, многие лиспы и эмэли не чистые ФЯ. Чистые это Haskell, Miranda, Clean.


                  1. ad1Dima
                    12.02.2017 19:31

                    Языковой фашизм какой-то http://stackoverflow.com/questions/993124/does-haskell-have-variables


                    1. Sirikid
                      12.02.2017 21:31

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


                      1. ad1Dima
                        13.02.2017 05:11

                        туда пишут, от туда читают, но это не переменные…


                        1. Sirikid
                          13.02.2017 18:12
                          -1

                          Когда я писал предыдущий комментарий я ещё не прочитал ответы на SO. Это эмуляция переменных, так что нет, в Haskell нет переменных.


                    1. 0xd34df00d
                      12.02.2017 22:40

                      IORef и STRef некорректно называть переменными.


  1. vba
    15.02.2017 19:01

    На мой взгляд C# до функциональности далеко еще из за нехватки read-only по умолчанию и везде, судя по тек тенденции команда поддержи языка идет по противоположному пути, ака readwrite по умолчанию.