Comments 83
Во многих функциональных языках, в частности в F#, существует так называемое внедрение типов: когда вы не задаете напрямую тип ваших данных, а компилятор, в зависимости о того как вы их используете, сам определяет что подставить.
Если не ошибаюсь, это обычно называют выводом типов (type inference).
Похоже пора расчехлить свой pet project на F#. Спасибо за интервью, вдохновляет шагнуть в функциональное программирование.
Небольшой группой последние полтора года на F# с помощью Akka.Net мы пишем систему, которая имеет высокие требования к быстродействию и масштабированию. Эта система разрабатывается для норвежского государственного радио-телевидения. Она оперирует многими сотнями терабайт файлов, работая с облаком. Код получается очень компактный, несмотря на сложность системы.
из реально завершенных «фунциональных» проектов
последние полтора года на F# с помощью Akka.Net мы пишем систему
Пишут, а значит пока не написали, а значит, согласно статье, у автора нет законченных проектов на функциональных языках, кроме игры «Жизнь».
Пишут, а значит пока не написали
Выходит, у Торвальдса, например, тоже нет законченных крупных проектов на Си.
Это значит написали, работет и продолжают развивать. Вот ссылка на доклад Вагифа "Опыт внедрения актёров на F#"
https://www.youtube.com/watch?v=wRxO5ky7S8g
Вагиф на прошлом Московском DotNext рассказывал о своём проекте:
https://www.youtube.com/watch?v=wRxO5ky7S8g
Нет, вы поняли неправильно.
Вначале я пытался с этим бороться и писал также много тестов, как на C#. Потом я понял, что в этом нет необходимости, так как большинство логических ошибок отлавливается компилятором, который гораздо менее «прощающий», чем компиляторы объектно-ориентированных языков.
с чего это? компилятор знает как бизнес логика работает?
http://fsharpforfunandprofit.com/ddd/
При этом я мгного лет был в каком-то смысле TDD-junkie, старался писать тест на все что угодно.
Если вы посмотрите на объектно-ориентированный код, там будут какие-то переменные, какие-то данные, потом их куда-то посылают, и ко всему этому осуществляется доступ из многих потоков.
По моему это не про ООП, а о том, как многие его неправильно используют. :)
Но самое непонятное мне, это то, что лично я бы не стал писать 20 лет на том что мне не нравится.
Если из моих слов сложилось такое впечатление, значит я совсем туманно выражаюсь.
Если из моих слов сложилось такое впечатление, значит я совсем туманно выражаюсь.
нам смог ответить человек, открывший для себя преимущества функционального подхода после 20 лет ООП-разработки
Это разве не означает что 20лет Вы писали на убогом-неудобном и неправильном подходе?
И ещё вопрос — функциональные библиотеки которые Вы используете, так же не имеют переменных и не хранят состояние свойствах?
В ООП проблема shared-state решается просто — его отсутствием: https://en.wikipedia.org/wiki/Thread-local_storage
Если вы переживаете по поводу производительности и иммутабельности — в f# достаточно средств оптимизации и можно втыкать mutable и кусочки ООП туда, где вам нужно.
Когда я попробовал f#, посмотрев лекцию VagifAbilov об экторах, я понял, что ФП — это именно то, как я стремился писать программы. И если в ООП языках я иногда чувствовал сопротивление инструмента, то в случае с f# все "как ножом по маслу".
ФП сейчас конечно форсится жутко, но это не делает парадигму плохой.
Адепты концепций высокого уровня любят высокомерно бросать «ну а если вам нужна производительность — можно подключить си библиотеку». Можно подумать пользователям не нужна производительность. Или что отдельная библиотека — это не часть программы. Существенная часть программы, написанная в другой парадигме. И это даже не наводит их ни на какие мысли. Слишком много развелось узкоспециализированных программистов, не только не способных построить с нуля всю программную систему, но даже не представляющих как она устроена. Отсюда и утверждения, что только их болото достойно внимания.
Опять же, можно подумать, что параллельное ФП по своей сути отличается от процедурного и магически решает все проблемы. Никакой магии нет. За гарантии работы программа платит скоростью работы. Параллельное программирование — это такая сложная тема не потому, что сложно написать корректную программу (хотя это тоже удаётся не всем), а потому что невероятно написать оптимизированную, но всё ещё корректную программу. Выкинуть все проверки и остановки, без которых можно обойтись, использовать разные виды локов с пониманием ньюансов их использования. Простота ФП параллелизма лежит в том, что вместо десятков разных параллельных примитивов, точное использование которых позволяет снизить издержки, у нас остаётся два-три железобетонных, и если твоя область применения порождает издержки, то тебе придётся их терпеть.
Вот как магически можно решить проблему общего состояния в игре? Каждый актор должен знать состояния всех соседей и поля боя в целом. Можно использовать локи, а можно копить кучу копий и обеспечивать синхронизацию этих копий. И никак нельзя облегчить задачу — объединить несколько мутаторов в общий логическую систему и разбить обновление этой системы на фазы, внутри некоторых из которых можно ослабить гарантии пареллельного доступа. Что-то наподобие идемпотентного оператора, только более сложное и в масштабах системы, а не отдельного кусочка данных.
Так же есть так называемые «Небезопасные» функции для работы напрямую с памятью, думаю будет примерно то же самое.
P.s. Сам не имею опыта в таких вещах.
В более традиционных системах, однако, закон Амдала, указывающий на потери быстродействия залоченных данных, имхо, делает код с замками в многопоточной среде более уязвимым в плане быстродействия, чем функциональные алгоритмы без блокировки.
Так же не понятно, как реализовать кеширование без разделяемого состояния.
Отдельный кэш для каждого потока?
При обновлении кэша пересоздавать его заново?
— Что вы можете посоветовать тем, кто решил начать изучать функциональное программирование?
Вагиф Абилов: Посоветовал бы не относиться к этому выбору как к какому-то серьезному жизненному шагу. Я заметил, что пробовать изменить основной язык у программистов считается каким-то радикальным шагом в отличие, например, от смены базы данных или какого-то фреймворка. Например, разработчики на javascript меняют библиотеки, которыми они пользуются, как перчатки. И это не выглядит каким-то серьезным изменением. Если вы перешли с реляционной базы данных на document-базу, это во многом более серьезный шаг, чем перейти с одного .NET языка на другой.
Отлично сказано!
Вообще говоря, если ориентироваться на доминирующую архитектуру процессоров, то она императивна и мутабельна :)
А вообще делал несколько попыток освоить языки, позиционирующиеся как функциональные прежде всего, но всегда спотыкался на моменте, когда становится нужна мутабельность, ввод/вывод и прочие сайд-эффекты — красивые концепции резко становятся сложными.
Вообще говоря, если смотреть на доминирующую архитектуру процессоров, то она нынче мультиядерная. А распараллеливаются, как раз, лучше "чистые" функции, которые используют только иммутабельные данные.
Так или иначе эти чистые функции изменяют состояние системы — ОЗУ и дисков, даже если про регистры не вспоминать. И делают это в императивной мутабельной манере, просто транслятор и(или) рантайм языка это скрывает.
Третий важный аспект в реализации игры «Жизнь» на F# было то, что там я не ввел ни одного типа.
Это какое-то неправильное функциональное программирование. "Правильное" современное ФП обычно начинается с типов и управляется типами.
Конечно важно. От этого асимптотика страдает. Частенько и коэффициенты при О-большом тоже.
Если вы думаете, что нужно обязательно создать полную копию структуры (графа), чтобы внести небольшое изменение — вы ошибаетесь. Вот как-то так в двух словах. Совсем не хочу при этом сказать, что эти алгоритмы простые — скорее наоборот, они сложные (классическая книжка Окасаки — это просто идеальный способ сломать мозги). Но с точки зрения памяти и времени не все так мрачно.
Сравните хелло ворлд хотя бы. На F# это одна строчка кода, на C# тебе и классы и полиморфизм, и аргументы из командной строки, и статик и синглтон — все в каше.
> Сравните хелло ворлд хотя бы. На F# это одна строчка кода, на C# тебе и классы и полиморфизм, и аргументы из командной строки, и статик и синглтон — все в каше.
Так вы определитесь, алгоритм разный или строчки? Алгоритм-то hello world один и тот же во всех случаях.
Формально подходя, вроде к ФП понятие алгоритм в целом не применимо, алгоритм — описание шагов в определенном порядке, а ФП не задает порядок вычислений в функциях.
Вы преувеличиваете. Он не задается, если вычисление не зависит от результата другой функции. Тогда можно в любом порядке, параллельно и т.п. А иначе — извините, есть зависимость по данным, извольте соблюдать.
Порядок не задаётся программистом явно всё равно, он определяется по ходу дела в процессе определения того, что нужно сделать, чтобы получить результат по декларативным правилам заданным программистом. Программист пишет "чтобы получить результат функции A нужно применить такую-то трансформацию к результату функции B". Это правило не задаёт момент когда реально выполнится вычисление функции B, единственное что можно сказать, что вычисление функции A не закончится позже вычисления функции A, если результат необходим для этого вычисления. С натяжкой, конечно, можно назвать это порядком, но не в том смысле, в котором он используется в алгоритмах.
Функции в ФП мощнее тех, которыми пользуются в математике. Тут главное — функция выбора
IfThen( condition, trueValue, falseValue )
В математике, при комбинировании функций, значение функции не определено, пока не определены все аргументы. У функции
IfThen
в ФП фундаментальное отличие — если condition==true
, то значение falseValue
может быть не определено (а может и быть определено). На этом строится и рекурсия (условие выхода из рекурсии), отсюда и проблемы с вычислимостью (аналог проблемы остановки), это и даёт возможность записывать любые алгоритмы.10 PRINT "Hello, World"
Тут helloworld так короткий, потому что BASIC — язык для начинающих (согласно расшифровке его аббревиатуры), т.е. язык для helloworld-ов. Дизайн языка говорит новичкам: смотрите, как просто — 1 строка и программа работает!
С другой стороны, если посмотреть на алгоритмы архивации, то доказано, что нет универсального — такого, который бы обошёл все алгоритмы на любых данных. Всегда найдётся лучше архиватор под специфические данные. С языками то же самое.
Вагиф, ФП очень даже востребовано в программировании интерфейсов, форм и прочего. Если вам интересен функциональный подход к написанию клиентских приложений, то очень интересный в этом плане язык — Elm http://elm-lang.org
Это, можно сказать, наследник Haskell для написания сложных frontend web-приложений, но при этом очень простой в освоении. Чистые функции, отсутствие null reference exceptions и вообще исключений в runtime для сложных SPA, где владычествует великий и ужасный js, — согласитесь, заманчиво
2. Вместо shared state — инкапсулировать состояние
и т.д…
То какие преимущества у ФП, кроме краткости, которое часто сомнительно?
Раскройте. за счет чего выше производительность при ФП, будет, если расход памяти больше на порядок (ведь состояние неизменяемое)? Как удается быстро освобождать неиспользуемую занятую память?
https://www.slideshare.net/ScottWlaschin/c-light
Но если брать другие, более важные моменты, то основная проблема языков ООП — это то, что они навсегда останутся языками мутирующих данных. Мы можем требовать от разработчиков команды высокой дисциплины, но нельзя заменить основы языка дисциплиной. Вот пересказ отзыва создателя Clojure Рич Хики о проблемах мутаций в ООП:
«It should also be obvious that OO languages have „no proper notion“ of values in this sense. As Rich Hickey points out, you can create a class whose instances are composed of immutable components, but there is no high-level concept of immutable value implemented as a first class construct within the class.
This is one of the main causes of headaches when doing OOP. How many times have you pulled your hair out trying to figure out how an object's attribute got changed? The fact is, in OO languages there is no built-in mechanism to ensure that the object you're dealing with is stable.
This is the big reason why concurrent programming is so difficult.»
Безусловно, современные C# и Java куда более пригодны для функционального стиля программирования, чем пятнадцать лет назад. Но такой стиль всегда будет оставаться для этих языков если не инородным, то по крайней мере не самым идиоматическим.
D сиподобный и в нём есть неизменяемые структуры:
immutable struct Person {
/// Full name
string name;
/// Birth day
DateTime birthday;
}
auto person = Person( 'Alice' , DateTime( 2000 ) )
Так и компьютер — это механизм с мутирующими данными. И алгоритмы затачиваются именно под мутирующие данные. При переходе на немутирующие ленивые могут даже асимптотику потерять, доступную в императивном стиле.
Я как-то купился на всю эту чушь и попробовал пописать на хаскеле. Вроде даже получилось, хотя и геморно было. Но это я прак писал, никаких требований к производительности там не предъявлялось, лишь бы работало. Потом посмотрел, как большие мальчики пишут на хаскеле. Оказалось, что реальные либы включают монады памяти, IO, подключают сишный код. В общем, весьма далеки от идеалов чистоты. А иногда ещё и энергичные вычисления вместо ленивых используют, чтобы сборщик мусора не напрягать. Если кто не понял, то именно так и выглядит текущая абстракция. Когда сборщик мусора тормозит и нужно оптимизировать код не из соображений красоты и логичности, а чтобы мусор собирался.
> The fact is, in OO languages there is no built-in mechanism to ensure that the object you're dealing with is stable.
А как же константы? Очень удобная декларация, с самого начала программистам понравилась. Просто не надо все поля класс делать константными.
> Но такой стиль всегда будет оставаться для этих языков если не инородным, то по крайней мере не самым идиоматическим
Так он в любом случае будет императивным. Ну вот заменили все функции на структуры данных, запихали их во Free, получили ad-hoc синтаксическое дерево. Только вот оно само ничего не вычисляет. Его надо интерпретировать потом. И внезапно оказывается, что интерпретатор с большим энтузиазмом меняет состояния.
проблема не в том в какой парадигме суть языка программирования
а в том что суть языка есть Синтаксис
я до сих пор не могу понять Почему синтаксис все не вобрал в себе на Упрощения кода
пусть будет синтаксис совмещен от всех парадигм и от ООП и от Функций и от Прологов и всех остальных язычеств
программисту вообще нет дела до того как реализуются уже библиотеки и модули
ему есть дело Как решить ту или иную задачу за минимум времени чтобы получить максимум премию)
никто не хочет работать
так создайте Си шарп с синтаксисом Питона и Наскела))
столько не нужных скобок и типов которые в Айдле уже сами выставляются
упрощайте синтаксис и не хамите что это Сахарный десерт
и начинайте упрощать с ассемблера
и почему нельзя в ассемблере применять циклы)) по синтаксису — сами себе вы программисты хамите
вот что я думаю
static void Main()
{
var rnd = new Random();
var size = 10;
var grid = Enumerable.Range(0, size * size).Select(_ => rnd.Next(2)).ToArray();
Func<int, int, int> match = (v, n) => n == 3 ? 1 : n == 2 ? v : 0;
var neighbours = new[] { -1 - size, -size, 1 - size, -1, 1, -1 + size, size, 1 + size };
while (true)
{
Console.Clear();
foreach (var s in Enumerable.Range(0, grid.Length / size)
.Select(y => string.Join("", Enumerable.Range(0, size).Select(x => grid[y * size + x]))))
Console.WriteLine(s);
grid = grid.Select((v, i) => match(v, neighbours.Sum(o => grid[Math.Abs(i + o) % grid.Length]))).ToArray();
Thread.Sleep(300);
}
}
Функциональное программирование: в Java и C# слишком много церемоний