Однажды я столкнулся с мистической проблемой — серия последовательных операций спонтанно начинала выполняться в случайном порядке, вызывая race condition и абсурдные сообщения об ошибках. Первая операция однозначно запускалась раньше второй, но сервер почему-то периодически сообщал об обратном в своей излюбленной манере: молча закрывал соединение. Раз за разом я вчитывался в одни и те же строчки, всё больше впадая в отчаяние; в коде не было ни намёка на источник хаоса. В течении двух недель в отладчике я перебрал все возможные варианты решения и приступил к невозможным, пока наконец не решил проблему, стерев один console.log.
Так я познакомился с сайд-эффектами.
Сайд-эффекты в коде — это места, в которых чистый абстрактный код сталкивается с реальным миром; понятие, которому уделено особое внимание в функциональном программировании. Когда функция пишет в файл или запускает ядерные боеголовки — это сайд-эффект. В моём случае вывод в консоль тормозил выполнение функции на период от 20 до 200 мс, а часть кода была рассчитана на то, что результаты будут доступны в течении текущего тика эвент лупа, и в сочетании с многопоточным libuv (io ядром nodejs) превращалась в бомбу замедленного действия.
Большинство ООП языков абсолютно беззащитны перед сайд-эффектами. И действительно, функция будет иметь один и тот же тип вне зависимости от того, есть ли там console.log или нет. Программисты вынуждены раз за разом сочинять самые разнообразные абстракции для одного и того же явления — функции, влияющей на окружающий мир. В фп же сайд эффекты явно выделяются в типе функции, тем самым явно определяя код, требующий аккуратного обращения.
Вот как это происходит в purescript:
Допустим, что у нас есть какой-то js код с сайд-эффектами, который мы хотим вызывать из нормального ™ языка
// Main.js
exports.pushTheButton = function() { console.log('nuclear missile launched') }
Чтобы различать разные сайд-эффекты, каждый из них можно задать как отдельный тип. Присутствует тип в сигнатуре функции — значит эта функция повлечёт за собой данный побочный эффект
module Main where
import Control.Monad.Eff(Eff, kind Effect)
import Control.Monad.Eff.Console (logShow, CONSOLE)
import Prelude (Unit, discard)
foreign import data NUCLEAR_MISSILE :: Effect
foreign import pushTheButton :: forall e. Eff (
launch :: NUCLEAR_MISSILE | e
) Unit
main :: Eff (
console :: CONSOLE,
launch :: NUCLEAR_MISSILE
) Unit
main = do
pushTheButton
logShow "its fine"
-- Results:
-- nuclear missile launched
-- its fine
В результате имеем чётко описанные типы и предсказуемый чистый код
ООП перечисляет использованные предметы, ФП — описывает суть происходящего.