![](https://habrastorage.org/files/e1b/694/f55/e1b694f554424d9c8bbbde5be94ddd91.jpeg)
Многие программисты неявно подразумевают, что «функциональное программирование (ФП) должно реализовываться только на функциональном языке». C# — объектно-ориентированный язык, поэтому не стоит и пытаться писать на нем функциональный код.
Разумеется, это поверхностная трактовка. Если вы обладаете чуть более глубокими знаниями C# и представляете себе его эволюцию, то, вероятно, в курсе, что язык C# мультипарадигмальный (точно как и F#) и что, пусть он изначально и был в основном императивным и объектно-ориентированным, в каждой последующей версии добавлялись и продолжают добавляться многочисленные функциональные возможности.
Итак, напрашивается вопрос: насколько хорош нынешний язык C# для функционального программирования? Перед тем, как ответить на этот вопрос, я поясню, что понимаю под «функциональным программированием». Это парадигма, в которой:
- Делается акцент на работе с функциями
- Принято избегать изменения состояния
Чтобы язык способствовал программированию в таком стиле, он должен:
- Поддерживать функции как элементы 1-го класса; то есть, должна быть возможность трактовать функцию как любое другое значение, например, использовать функции как аргументы или возвращаемые значения других функций, либо хранить функции в коллекциях
- Пресекать всякие частичные «местные» замены (или вообще сделать их невозможными): переменные, объекты и структуры данных по умолчанию должны быть неизменяемыми, причем должно быть легко создавать модифицированные версии объекта
- Автоматически управлять памятью: ведь мы создаем такие модифицированные копии, а не обновляем данные на месте, и в результате у нас множатся объекты. Это непрактично в языке, где отсутствует автоматическое управление памятью
С учетом всего этого, ставим вопрос ребром:
Насколько язык 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);
}
using static
обеспечивает неквалифицированный доступ к статическим членамSystem.Math
, например,PI
иPow
ниже- Авто-свойство
getter-only
можно установить только в конструкторе - Свойство в теле выражения
- Локальная функция – это метод, объявленный внутри другого метода
- Синтаксис кортежей 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# и далее будет развиваться как мультипарадигмальный язык со все более выраженным функциональным компонентом.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Комментарии (54)
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());
Понажимайте «вверх», кого заинтересует раскрытие данной темы.
У меня уже два рабочих проекта в таком стиле сделано. Но есть два минуса такого подхода, могу позже объяснить что к чему…xGromMx
10.02.2017 14:35Extend это из комонады?
Magic_B
10.02.2017 14:43Extend — это функция создающая Tuple из входящего Tuple или одного типа, добавляя к нему еще один элемент. Например, есть Tuple<int, double>, тогда Extend(Tuple<int, double>, string) вернет Tuple<int, double, string>. В данном случае, моделью для вьюшки будет являться Tuple<Cart, Product>.
xGromMx
10.02.2017 14:48оно делает тройку?
Magic_B
10.02.2017 14:49Вы имеете ввиду, Tuple<T1,T2,T3>? Хоть пятерку… Но это не очень хорошо, использовать такие длинные записи для логики…
xGromMx
10.02.2017 14:54и кто сказал что это фп? то что вы сделали флюент это еще не значит, где правильный карринг и все остальное? Ваши идеи можно сделать с помощью монадических цепочек + Maybe/Either. Также Rx достаточно самодостаточен и очень приближен к идеям фп. Есть еще вариант с Validation монадой, если надо накапливать причины ошибок
Magic_B
10.02.2017 15:09Я и не говорю что это ФП, даже намека не делаю, но такой стиль написания на C# имеет место быть и может называться функциональным стилем.
Deosis
13.02.2017 08:29Когда завезут Variadic Templates?
У вас ведь написаны 8 вариантов функции Extend?
У вас учитывается, что в Tuple<T1,T2,T3,T4,T5,T6,T7,TRest> последний параметр должен быть тоже Tuple?Magic_B
13.02.2017 08:49В C# 7.0 есть записи. Нет, не учитывается, если вы имеете ввиду «склейку» Tuple. Вообще, я выше говорил, что не стоит использовать такие громоздкие записи, где будет Item7 например, это понижает читаемость, хотя дает типизацию.
Magic_B
10.02.2017 15:42ссылка на репозиторий
Осталось сделать рабочий компилируемый пример и описать плюсы и минусы…
Кому интересна данная тема, милости просим!
Vadem
10.02.2017 14:06+1И всё? Есть ли в C# возможность каррирования, частичного применения, ленивости, контроля за сайд-эффектами?
Например, можете ли вы привести универсальный способ каррировать произвольную функцию?
Или как-то ограничить функцию чтобы она не могла иметь побочных эффектов?
Я не спорю, что на C# можно писать в функциональном стиле, но называть C# функциональным языком я бы не стал.Varim
10.02.2017 15:46контроля за сайд-эффектами
А это что, можете пример привести? Вы про что то вроде указать методу ключевое слово «pure» и что бы компилятор запрещал изменять состояния?Vadem
10.02.2017 20:38-2Да что-то вроде этого.
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, но пока, увы, только на рассмотрении.0xd34df00d
14.02.2017 18:23Например в Haskell все функции по умолчанию чистые.
Они там не по умолчанию чистые, они просто чистые. Есть некоторая тонкость с определением функций в ST-монаде, но она скорее техническая, ИМХО.
А IO — это такое ST с особым тегом, если хотите.
ad1Dima
10.02.2017 15:57Если под ленивостью понимается ленивая инициализация и исполнения, то на этом весь Linq построен.
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.
Впрочем, ленивость — это, наверное, самая незначительная вещь для ФП.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 произойдет тут.
Впрочем, и то и то не возможности языка, но возможности библиотеки.
ad1Dima
10.02.2017 16:06А насчет каррирования, вы хотите что-то вроде https://m.habrahabr.ru/post/76545/ ?
Vadem
10.02.2017 20:52Да, я хочу что-то вроде этого, но только чтобы не надо было добавлять новый метод-расширение, когда у меня появляется метод с другим числом параметров.
В принципе, конечно можно один раз сделать 100500 вариантов функции Curry один раз на все случаи жизни.
Более того, можно взять language-ext в котором это уже сделано(правда только для Func, хорошо бы ещё и для Action).
Но в общем, согласен, что это решает проблему с каррированием.
sshikov
20.02.2017 14:56На самом деле, многое из того что вы написали, совершенно необязательно.
Скажем, Haskell язык ленивый, а Scala нет. Это делает скалу другим языком, нежели Haskell, но это никаким образом не делает скалу «не ФП» языком. Тоже самое и с побочными эффектами. Чистота функций — это очень полезное свойство, но ее отсутствие тоже не приговор.
Это ортогональные вещи.
Впрочем, если вы хотели сказать, что таких как C# «почти функциональных» языков сегодня пруд пруди — так это чистая правда.ad1Dima
20.02.2017 17:04Впрочем, если вы хотели сказать, что таких как C# «почти функциональных» языков сегодня пруд пруди — так это чистая правда.
И хорошо, что есть языки, позволяющие придерживаться наиболее подходящей парадигмы для конкретных задач.
Varim
10.02.2017 15:41Регистрируемые типы (неизменяемые типы без трафаретного кода)
Что такое изменяемые типы? В c# в частности.
Алгебраические типы данных (мощное дополнение к системе типов)
Что это вообще такое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 тут проверка будет не на этапе компиляции, а в рантайме.Sirikid
11.02.2017 00:19-1Достаточно было привести ссылку на Википедию.
В C# близко к этому null, но в отличие от ADT тут проверка будет не на этапе компиляции, а в рантайме.
В Haskell проверка тоже будет в рантайме.
Любопытный факт, Rust представляетOption<&T>
(Option
это аналогMaybe
,&T
— ссылка наT
) в виде указателя наT
,None
в таком случае соответсвуетnull
, аSome
любому другому значению.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
Sirikid
11.02.2017 03:23А, понял, вы имели ввиду что
Maybe
в отличии отnull
нельзя проигнорировать? (Можно:fromJust
:^)) Ну да, это проблема дефолтов старых языков, более новые, например Kotlin, не разрешают таких вольностей. Хотя, насколько мне известно это единственный язык с такой системой типов — честная nullability, не скрытая под абстракцией ADT, и при этом по дефолту типы nonnullable.
Vadem
10.02.2017 21:28А какой популярный язык не является «почти функциональным»?
Автор выделил 3 критерия, которым должен соответствовать язык, чтобы называться функциональным(хотя я с ними не согласен, но допустим):
- Иметь функции высшего порядка
- Неизменяемость(иммутабельность) по умолчанию
- Автоматическое управление памятью
C# не соответствует пункту №2, т.е. у него есть только функции высшего порядка и сборка мусора.
Навскидку функции высшего порядка и сборка мусора есть в D, Python, JavaScript, Java, PHP, Ruby, Swift и Go.
Сюда ещё можно отнести C++, так как хоть у него и нет сборки мусора, но есть умные указатели и функции высшего порядка.
P.S.: А почему убрали ссылку на Functional Programming in C#? В оригинальной статье она есть.
NIKOSV
11.02.2017 02:26С функциями высшего порядка нужно быть осторожнее, а то некоторые почитают о модном функциональном подходе, и начинают возводить это в абсолют в ущерб читаемости, поддержки и тестируемости кода, в стиле «смотри как я могу!». Там где можно было обойтись логической композицией имутабельных классов (старый добрый IoC/DI) и дергать их в императивном стиле, они фигачат функции с километровыми сигнатурами которые на 27'' экран не помещаются, где функция принимает функции с кучей параметров и возвращает другую функцию с кучей параметров, некоторые из которых кортежи. Потом час сидишь и втыкаешь в эту сигнатуру и пытаешься понять что вообще происходит. Не говоря уже что дебажить это все счастье практически невозможно. Зато от поганых классов и объектов избавился, это да.
IndigoMan
11.02.2017 09:40+1а что не так с F#?
Или я что не понимаю…aikixd
11.02.2017 12:06Люди передали функцию в функцию и думают что это ФП. Поэтому думают что на С# можно написать функциональный код, так что f# не нужен.
Varim
11.02.2017 12:40А что такое ФП? Скажите вашими словами.
aikixd
11.02.2017 19:35В ФП есть только типы и функции. Нет переменных, нет сравнений, нет действий.
Например
let a = 5 // остальной код
На самом деле
(fun a -> //остальной код)(5)
Все функции имеют один параметр и всегда возвращают одно значение. Функции порождают функции, их можно частично применять и получать другие функции.
В ФП нет циклов, нет тупиков, вы не можете заранее выйти из функции (
if (??) return;
).
На пальцах примерно так. Это совсем другой мир.
ad1Dima
12.02.2017 08:13Т.Е. Lisp не является функциональным языком? http://www.tutorialspoint.com/lisp/lisp_variables.htm
Sirikid
12.02.2017 09:18Лисп просто не чистый.
ad1Dima
12.02.2017 09:19Ну тогда Lisp->Ocaml->F# все они не чистые.
aikixd
12.02.2017 12:24Вас не заставляют использовать не идиоматические элементы. В конце концов, в идеальном функциональном языке нет вычислений, но мы живем в реальном мире и иногда нужно писать императивно, потому что код в конце концов обрабатывается процессором.
Sirikid
12.02.2017 19:25Да, многие лиспы и эмэли не чистые ФЯ. Чистые это Haskell, Miranda, Clean.
ad1Dima
12.02.2017 19:31Языковой фашизм какой-то http://stackoverflow.com/questions/993124/does-haskell-have-variables
Sirikid
12.02.2017 21:31Это не фашизм, просто термин. Лично я считаю что (хаскелевские) константы (переменные) это функции без аргументов, примерно так оно и есть.
vba
15.02.2017 19:01На мой взгляд C# до функциональности далеко еще из за нехватки read-only по умолчанию и везде, судя по тек тенденции команда поддержи языка идет по противоположному пути, ака readwrite по умолчанию.
pygubanov
Можете привести реального примера с применением такого функционального подхода?