Блог компании Microsoft
F#
Программирование
Комментарии 5
0
Спасибо за материал!
Надеюсь, будет продолжение.
Прочитал все три части на одном дыхании, и возник вопрос, надеюсь, кто-нибудь поможет разобраться.
Вопрос про иммутабельность. Зачем оно нужно, в чем плюсы такого подхода и откуда у него ноги растут — тут всё понятно.
А теперь представим, что мы ведем речь не про F#, где есть mutable, а про какой-нибудь «чисто функциональный» язык, где мутабельных переменных нет вообще. И нам нужно каким-то образом хранить текущеее состояние.
Понятно, как с помощью иммутабельных переменных и структру и хвостовой рекурсии реализовать циклы, работу со списками, и т.д. И вот с чем-то более сложным мозг ломается.
Например, простая задача: мы реагируем на какое-нибудь событие, например, по сети получаем откуда-то пакеты двух типов, и нам нужно посчитать за какое-то время, сколько пришло пакетов первого типа и сколько пришло пакетов второго типа. Мы не можем, как в процедурном или ООП стиле менять состояние нашей структуры (записи, объекта), где мы храним посчитанное. Обычно в статьях про ФП про этот случай пишут, мол, раз мы не можем изменять значение, то мы должны использовать его как аргумент функции и породить новую сущность, несущую уже в себе новое состояние.
Приход пакета — это, допустим, колбэк от ОС или какой-то внешней библиотеки.
Логично, что на этот колбэк у нас будет назначена функция, одним из аргументом которой у нас будет полученный пакет. И так же напрашивается, что вторым аргументом у нас должно быть старое состояние state, чтобы мы, проанализировав пакет, создали state' с увеличенным на единицу значением. И тут тупой вопрос: а откуда наша функция-колбэк возьмет это «старое состояние», учитывая, что это «старое состояние» есть результат работы предыдущего колбэка, которое мы должны как-то сохранить, но не можем менять?

Или я недостаточно сильно сломал мозг, и единственный верный вариант — это сделать функцию, которая будет вызывать функции, проверяющие, не возникло ли где-нибудь снаружи какое-то событие, и если возникло, то реагировать на него, и рекурсивно вызывать саму себя с новым состоянием? Правда, непонятно, как правильно организовать подобный event loop чтобы не бешено крутиться в цикле, а просыпаться именно по любому из событий, да и, как мне кажется, возникают вопросы к читаемости и понимаемости такого кода, учитывая количество таких «событий» и их комбинаций в реальном мире.

Или я где-то ошибаюсь? :)
+2

Ваш случай с сетевыми пакетами и колбэками очень хорошо и аутентично решается в Erlang (в котором, если я правильно помню, вообще нет мутабельных переменных): этот функциональный язык программирования вообще хорошо подходит для решения такого рода задач. Здесь решение будет строиться на акторах: заведём, например, какого-нибудь глобального диспетчера, которому будут приходить сообщения из колбэков ОС, а он будет их раздавать акторам — которые, например, считают количество полученных сообщений. Код внутри акторов не мутирует никаких переменных, а просто принимает старое состояние (и пришедшее сообщение), а возвращает новое состояние (ну или рекурсивно себя вызывает с этим новым состоянием).


(При этом я полагаю, что где-то в глубине акторного фреймворка может быть мутабельный код, но в Erlang его вам никто не показывает.)


В F# может быть использовано то же самое решение — ведь у нас в .NET тоже есть акторные фреймворки — а ещё есть встроенные акторы в стандартной библиотеке F#, которые также позволят обойтись без mutable в пользовательском коде.

0
Прошу прощения за некропостинг, но возник еще вопрос :)
Как в такую модель вписывается concurrency/parallelism? Например, мы хотим разгребать полученные пакеты не на одном, а на всех процессорах машины, но при этом счетчик у нас один общий на всех, и мы в ответ на каждый пакет должны посылать в ответ текущее значение счетчика.
В классчическом процедурном или ООП-подходе все просто: есть какая-то глобальная структура или переменная, и потоки ее изменяют, или получая мьютекс, или через lock-free-операции.
С ФП и иммутабельностью непонятно. Когда у нас один поток, то всё как вы написали: У нас функция, которая проверяет, не пришло ли сообщение от диспетчера, инкрементирует счетчик, и вызывает саму себя с новым значением. Но если у нас несколько потоков или процессов (чтобы задействовать все ядра), то отсутствие какого-то глобального состояния в голову снова не укладывается.
0
Насколько я понял задачу, есть некий супервизор которому и делегируется счет, и все потоки с ним взаимодействуют, он может отправлять текущее значение счетчика всем воркерам если это надо.
Только полноправные пользователи могут оставлять комментарии. , пожалуйста.