Что он имеет в виду, когда говорит
функциональные программы не имеют никакой стороны
эффекты? * * 1002
Большинство людей считают программирование созданием переменных, присвоением им значений, добавлением элементов в списки и т. Д. Переменные «меняются», отсюда и название.
Функциональное программирование - это стиль разработки программ для исключения переменных - все постоянно или только для чтения.
Когда Джоэл говорит, что функциональные программы не имеют побочных эффектов, приходится много размахивать руками, поскольку его очень легко писать функциональные программы, которые делают , модифицирующие переменные - но в основном, когда люди говорят о функциональных программирование, они означают программы, которые не содержат изменяемого состояния.
"Но Джульетта! Как написать полезную программу, если она не может изменить что-либо "
Хороший вопрос!
Вы «модифицируете» вещи, создавая новый экземпляр вашего объекта с измененным состоянием. Например:
class Customer
{
public string Id { get; private set; }
public string Name { get; private set; }
public Customer(string id, string name)
{
this.Id = id;
this.Name = name;
}
public Customer SetName(string name)
{
// returns a new customer with the given name
return new Customer(this.Id, name);
}
}
Таким образом, вся инициализация происходит в конструкторе, и мы больше не можем модифицировать объект - мы создаем новые экземпляры с нашими изменениями, переданными в конструктор.
Вы будете удивлены, насколько далеко вы сможете перенести этот стиль программирования.
«Но Джульетта !? Как это может быть эффективно со всем этим копированием?»
Хитрость заключается в том, что вам не нужно копировать весь объектный граф, только части, которые изменились. Если части вашего графа объектов не изменились, можете повторно использовать его в новом объекте (скопируйте указатель , не создавайте новый экземпляр каких-либо объектов в этой части графика).
Вы будете удивлены, насколько далеко вы сможете перенести этот стиль программирования. Фактически, его чрезвычайно легко написать неизменяемые версии многих распространенных структур данных - таких как неизменные деревья Avl, красно-черные деревья, много видов куч и т. Д. См. здесь для реализация неизменного трепа.
В большинстве случаев неизменяемая версия структуры данных имеет ту же вычислительную сложность для вставки / поиска / удаления, что и ее изменяемые аналоги. Единственное отличие состоит в том, что вставка возвращает новую версию вашей структуры данных без изменения исходной.
И как это делает распараллеливание
тривиальный?
Подумайте об этом: если у вас есть неизменяемое дерево или какая-либо другая структура данных, то вы можете двумя потоками вставлять, удалять и искать элементы в дереве без необходимости блокировки. Поскольку дерево является неизменяемым, один поток не может перевести объект в недопустимое состояние под нос другого потока, поэтому мы исключаем целый класс ошибок многопоточности, связанных с условиями гонки. Поскольку у нас нет условий гонки, нам не нужны блокировки, поэтому мы также исключаем целый класс ошибок, связанных с блокировкой.
Поскольку неизменяемые объекты по своей природе являются поточно-ориентированными, они, как говорят, делают параллелизм «тривиальным». Но это только половина истории. В случаях нам нужно, чтобы изменения в одном потоке были видимы для другого - так как нам это сделать с неизменяемыми объектами?
Хитрость в том, чтобы переосмыслить нашу модель параллелизма. Вместо того, чтобы два потока разделяли друг с другом состояние, мы думаем о потоках как о почтовом ящике, который может отправлять и получать сообщения.
Таким образом, если поток A имеет указатель на поток B, он может передать сообщение - обновленную структуру данных - потоку B, где поток B объединяет свою копию со структурой данных с копией в полученном сообщении. Также возможно, чтобы поток сам передавал в качестве сообщения, так что поток A отправляет себя в поток B, а затем поток B отправляет сообщение обратно в поток A через полученный указатель.
Поверьте, приведенная выше стратегия упрощает параллельное программирование в 1000 раз по сравнению с блокировками в изменяемом состоянии. Итак, важная часть комментария Джоэла: «Без понимания функционального программирования вы не сможете изобрести MapReduce, алгоритм, который делает Google настолько масштабируемым».
Традиционная блокировка плохо масштабируется, потому что для блокировки объекта необходимо иметь ссылку на его указатель - заблокированный объект должен находиться в той же памяти, что и объект, выполняющий блокировку. Вы не можете получить блокировку объекта между процессами.
Но подумайте о модели передачи сообщений выше: потоки передают сообщения два и друг от друга. Есть ли разница между передачей сообщения потоку в том же процессе и передачей сообщения потоку, прослушивающему какой-либо IP-адрес? На самом деле, нет. И это именно потому, что потоки могут отправлять и получать сообщения через границу процесса, и передача сообщений масштабируется так же, как это происходит, потому что приложение не привязано к одной машине, и ваше приложение может работать на любом количестве машин.
(Для чего это нужно, вы можете реализовать передачу сообщений с помощью изменяемых сообщений, просто никто этого не хочет, потому что поток не может ничего сделать с сообщением, не заблокировав его - что мы уже знаю, что полно проблем. Таким образом, неизменяемый способ использовать по умолчанию при использовании параллельного обмена сообщениями.)
Несмотря на то, что он очень высокий уровень и скрывает множество фактических деталей реализации, вышеприведенные принципы - это то, как Google MapReduce может масштабироваться в значительной степени до бесконечности.
Смотри также: http://www.defmacro.org/ramblings/fp.html