Всем привет! С вами подкаст Подлодка и его ведущие Егор Толстой, это я, и Екатерина Петрова. Всем привет. Катя, расскажи, какие сайд-эффекты подкаста «Подлодка» ты знаешь? Ой, ну первый сайд-эффект — это то, что люди делают себе коррекцию зрения, послушав наши выпуски. Так, еще какой знаешь? Второе, то, что начинают писать на котлине все повально просто.
Но на самом деле самый важный сайд-эффект подкаста «Подлодка» — это, конечно же, растущий счётчик подписчиков официального твиттер-аккаунта с названием «Виталий Брагилевский». И именно этот человек сегодня... пришел к нам в гости, чтобы забустить свой счетчик подписчиков еще дальше, куда-то вообще в невероятные небеса, потому что тема, про которую он будет говорить, она бьет в сердечко каждого из наших слушателей.
что сегодня мы будем говорить про системы эффекта в языках программирования. Виталий, привет! Да-да-да, всем привет! Я надеюсь, что в минус не пойдет счетчик после этого подкаста. Все может быть, все может быть. Так, ну тебя наши постоянные слушатели, да и не постоянные слушатели, я думаю, прекрасно знают, но на всякий случай я повторю. Виталий Брагилевский работает девелопер-адвокатом в JetBrains.
А это значит, что он пишет разные классные блокпосты. Если хотите начать с какого-то одного, я советую офигенный блокпост про Rust, который сравнительно недавно у Виталия вышел. Во-вторых, делает доклады на конференциях, а в перерыве между этим летает в самолетах на эти самые конференции, где его можно поймать, как сделал я, где как раз про эту тему с ним и договорился. Да, ну, как я и сказал, в основном все...
это делает, про замечательную Rust-Ade от самой лучшей компании в мире JetBrains под названием Rust Rover. Вот, ну и на разные другие темы. Автор книги Haskell in Depth. Обязательно покупайте. Скидка сейчас действует на Черную Пятницу, Виталий, или нет? Ой, я не знаю. Должно быть, там регулярно какие-то скидки, не знаю. Окей, и в прошлой жизни работал университетским преподавателем. Я все правильно рассказал? Да, да, все правильно, спасибо.
Если вы, Виталия, вдруг слышите впервые, то обязательно, не знаю, мой любимый, наверное, выпуск про... Блин, мне все нравятся на самом деле. Что за неожиданность? Ой, я не знаю. Я их все люблю. Окей. Молли, ну, я просто помню, что... 400-й, конечно же. 400-й. У нас просто Виталий в том числе приходил в один из юбилейных выпусков, по-моему, даже в 100-й, про выбор... Первого языка программирования или что-то такое? Да.
А мне, мне очень нравится выпуск про, на самом деле, про теорию языков программирования. Он, мне кажется, недостаточно высокооценённый. Его не так много людей слушало, как я бы хотел. Поэтому, если вы его прослушали, обязательно, обязательно к нему вернитесь. Вот. Короче, все, Виталий, мы разрекламировали тебя, разрекламировали твой твиттер-аккаунт. На этом наша работа с Катей в целом закончена на этот выпуск.
Подождите, подождите, а как же тупые шутки походу? Здесь, тупые шутки на месте. Ладно, ладно, так и быть, так и быть, поучаствуем. Так, ну давай, наверное, начнем с того, что... Короче, давай вообще... Что это за эффекты такие? Что за система эффектов для тех, кто не очень понимает, про что тема выпуска? Ну, я так скажу коротко. Это речь про будущее программирования. Наконец-то, к счастью, в языках программирования найдено...
на серебряная пуля, которая решит абсолютно все проблемы. Вот именно про это мы поговорим. Отлично. Ну, то есть выпуск про ИА, я правильно понял, да? Я бы этого не исключал. Я бы этого не исключал. Егор, а можно я переверну доску неожиданно? Ты реально этого не ожидаешь. А почему ты решил записать выпуск на эту тему и был инициатором? Тебя, может быть, что-то сподвигло? Ты статью увидел?
что-то, какое-то событие тебя направило. Да нет, на самом деле все просто. Я очень-очень-очень люблю выпуски, которые... Которые приходят, Виталий Прогиревский, ладно. Это первое. Второе — выпуски, которые берут какую-то концепцию из программирования и рассматривают, как эта концепция реализуется в... или парадигма реализуется в различных языках. Короче, как разные языки подходят к...
решению примерно одной и той же проблемы. Мне кажется, это очень круто, потому что оно как раз хорошо бьет в цель нашего подкаста расширять кругозор людей. Вот. Мы такие выпуски пишем периодически, поэтому... Это просто одна из таких тем. А если быть вообще достаточно честным, то я, как уже сказал, мы с Виталием случайно встретились в самолете, пошли попить кофе в аэропорту. Я спросил Виталия, про что едешь делать доклад. Он сказал, про систему эффектов. А я сказал, давай ты еще и...
нам про это расскажешь. Вот так тема и родилась. Да, именно так все и было, подтверждаю. Так, еще, Виталий, ты хотел начать этот выпуск с дисклеймера. Давай, ты его расскажешь или я расскажу? Давайте я скажу. Ну, на самом деле, ты частично уже это затронул, то здесь важно вот что понимать. Мы будем...
В этом выпуске, мне кажется, мы должны быть language agnostic, что называется. То есть мы не говорим о каком-то конкретном языке программирования, потому что концепция достаточно широка и реализуемая. Практически в любом языке Поэтому мы вот о таких базовых принципах Об этих идеях поговорим Ну а если вам нужны конкретные детали то вы просто идете в эти языки программирования, где сейчас эффекты есть, смотрите на библиотеки, ну и пишите реальный код.
То есть я при этом постараюсь быть более-менее прагматичным, то есть не совсем про теорию, но в общем это все равно базовые принципы, основные идеи. Я не научу никого писать код на каком-то конкретном языке программирования сегодня Окей Смотри, раз ты пообещал, что мы не только про теорию, но и про практику, давай начнем с такого рисковатого вопроса от слушателя. Кстати, дорогие слушатели, если вы хотите задавать свои вопросы к нашим следующим выпускам, обязательно подписывайтесь на наш канал.
в телеграме, где мы периодически эти вопросы собираем. Вот, поэтому вы сможете задать когда-нибудь свой вопрос тоже Виталию Брыгревскому. Так вот, что говорит наш слушатель? Он говорит, что использует XMonat, XMonat, не знаю, как правильно произносится, в качестве оконного менеджера. Он, ему очень нравится. где настройка заняла довольно много времени. Даже пришлось пройти курс по хаскелю на степике. Я, честно, никогда не слышал про X-Monat. У меня вот...
У меня очень много вопросов уже к происходящему, но я их пока оставлю при себе. Анбординг уровня бог. Так вот, Виталий, вас просят рассмотреть влияние эффектов или... их отсутствие на примере реальных прикладных программ. Например, в контексте вышеупомянутого оконного менеджера. Вот интересно, я тут даже какую-то, если не страсть, то даже агрессию какую-то чувствую по поводу таких...
теоретиков, которые заставили курс по Хаскелю пройти, чтобы пользоваться оконным менеджером. Там на самом деле история очень простая. Я... Ну, наверное, лет 8, может быть, даже 10 был пользователем XMonod. этого конного менеджера. Это такой тайлинговый менеджер для линуксов разных. Почему нужен курс по Haskell? Очень просто. Просто там конфигурационные файлы. Вот где-то там и не файлы пишут, где-то еще что-нибудь. У XMonad конфигурационный файл это программа на Haskell.
То есть для того, чтобы получить разные фрагменты функциональности, ты должен в этот конфиг файл функции какие-то вызвать, еще там чего-то. То есть конфигурация это просто программа на Haskell. Самый настоящий Haskell. Зато, с другой стороны, хотя бы не ямал. Да. Действительно, и это на самом деле серьезное преимущество XMonod. И, соответственно, когда ты конфигурируешь свою программу на Haskell, то тебе нужно Haskell знать. То есть, конечно, это такой оконный менеджер для Haskell-истов.
И он безумно расширяемый. То есть там чего только люди не делали. Я изучал, у меня был свой конфиг, я изучал конфиг и других людей, и было все очень такое сложное периодически. То есть действительно нужно было знать Haskell, чтобы этим пользоваться. Но штука в том, что, ну вот как это связано с эффектами.
Конечно, X-Monod и вот такая программа для конфигурирования которой, ты должен знать Haskell, это воспринимается как такая башня из слоновой кости, что-то не очень практическое. Ну вот люди этим занимаются. занимаются, если им нечего делать, если они такие фанаты. Но нормальным людям это не нужно. Вот типа, что это ваша теория, применимая к реальным прикладным программам, она особо не нужна. Вот я что-то такое читаю в этом вопросе.
И я бы хотел слушателя, который это спрашивает, переубедить. Я на самом деле считаю, что... Эффекты – это очень практичная, очень прагматичная вещь. И это... Вот я помню... По-моему, в 2018 году я как-то делал доклад, свой первый доклад на индустриальной конференции. Это была конференция по мобильной разработке, куда меня Егор позвал. Я там делал доклад. Если я не ошибаюсь, не морочите мне голову со своим функциональным программированием.
У меня там был один из слайдов, где была картина, настоящая картина, не AI нарисованная, а настоящая картина, где был Майкл Фарадей, по-моему, который какие-то в своей лаборатории опыты делал. И я там продвигал такую мысль, что... Вот смотрите, функциональное программирование это такая лаборатория, в которой изобретаются новые вещи. Какие-то из этих вещей там и остаются.
то есть не выходят за пределы лабораторий, а какие-то, вот как в экспериментах Фарадея, как электричество, которое... вышла наружу из лаборатории и стала широко использоваться вообще везде, в том числе и для того, чтобы работал AI, проходящий красной линией через наш подкаст уже. Вот электричество стало такой находкой лабораторной, которая стала всем полезна. И вот мне кажется, что системы эффектов как раз дают нам еще один такой предмет.
Нечто изобретенное в лаборатории функционального программирования, вроде бы такое теоретическое, что вдруг оказывается решает проблемы. Поэтому, мне кажется, стоит это обсуждать и интересно это обсуждать, даже если вы не конфигурируете свой оконный менеджер на Haskell.
Мне кажется, очень хороший пример, когда опять всякими терминами из функционального программирования — это монады. Я помню, когда мы первый раз обсуждали, и когда ты понимаешь, что там optional, тот же самый, который во многих языках представлен, это монада, вроде бы, Виталий может поправить меня.
соответствует ли тип optional, который может быть или не быть, ну, там, в том же Java, например, что по факту это монада. И ты такой, ого, оказывается, на самом деле мы оперируем такими же инструментами. Вот просто иногда даже не знаем, что оно так называется. Тут, смотрите, это очень хороший, очень правильный пример здесь, и он очень широко обсуждается в разных комьюнити. Вообще, должны ли мы использовать сложные термины?
которые могут отпугивать людей, которые не добавляют понимания, но создают некий барьер, что ты должен что-то много изучать, чтобы вообще этим пользоваться. И чтобы показать, что это действительно реальный обсуждаемый вопрос, вот одна из книг, которую я недавно прочитал, которая называется «Effect-Oriented Programming».
Там авторы Джеймс Ворд, Брюс Экель и Билл Фрейзер. Фрейзер, как-то так. Да, вот Брюс Экель, он известный, да, он там пишет книги по языкам программирования уже десятки лет. Так вот. В этой книжке «Effect Oriented Programming», они делились в Твиттере авторы, в процессе написания использовался скрипт, который находил все вхождения случайных умных слов. Вот типа монада, типа функтор, типа вот всякого такого. И если вдруг такое слово проникало, то скрипт его заменял на что-то более понятное людям.
Контейнер, например. Менее терминологическое, менее математическое, но более понятное. То есть это прямо принцип, который они заложили в основу... в своей книге. То есть давайте не брызгаться вот так терминами сложными, давайте все объяснять на понятном языке. И мне кажется, что они смогли достичь этой цели.
То есть, чем блистать интеллектом и выдавать понятия из теории категории, всяких разных других теорий математических, лучше приблизить тему к программистам и сообщить им, что ну да, вот они на самом деле...
разговаривают прозой ну вот можно нигде и им этого не говорить просто пишите код вот вот тут новый приемчик как красиво решать разные проблемы которые мы там будем с вами обсуждать сегодня Окей, окей, я думаю, это вполне пойдет за ответ на вопрос слушателя, а конкретно про то, что мы вообще... Для чего системы эффектов нужны, что мы получаем как результат их применения, мы еще поговорим сегодня чуть попозже, когда, я думаю, погрузимся сначала все-таки в теорию.
Да, ну давайте. Я вообще хотел обсудить с вами вот такую вещь, ну как бы перейдя к этому, собственно, к теории. Вот если мы будем смотреть на код разных программ, абсолютно разных. Можно на кодные менеджеры смотреть, а можно вообще на все что угодно. Вот что в этом коде является источником проблем? Откуда вообще берутся проблемы? Не только ошибки, а вообще сложности в процессе разработки ПО, связанные с кодом.
Вот что является источником? Почему программы ломаются? Есть программист, который там плохо что-то написал, но у этого тоже есть причина, да? то есть программист пишет код в котором есть какие-то ошибки но почему они там То есть не просто потому, что он там тупой какой-нибудь, еще что-то. Задача программиста в чем? Программист хочет добиться у системы, которую он разрабатывает, какого-то поведения. В голове его это поведение описывается каким-то образом.
натуральном языке, в виде каких-то схем, замков абстракции, чего-то еще. А дальше он пытается это трансформировать в язык с более строгой грамматикой, и какие-то оттенки смыслов, которые он хотел, могут здесь потеряться, либо быть неправильно. переведены. То есть, короче, трудности перевода стандартные. А второе, то, что вот, как Егор сказал, описывает поведение, но поведение часто учитывает состояние, вот, и как будто бы много ошибок часто от того, что не все...
состояния что-либо лечить иным. Так, то есть, вот, хорошо, значит... Вот то, что Егор сказал, это то, что проблемы перевода, идея программиста может не очень хорошо ложиться на язык программирования, какие-то детали в поведении, какие-то детали в состоянии. А не то, что Катя говорит, можно не все учесть и потом добавлять. Вот смотрите, все это можно свести к одной вещи. Если мы, например, пишем... простую программу, которая чего-то вычисляет по формуле какой-то.
то понятно, что у нас есть формула, мы взяли ее, она уже на каком-то языке записана, нам нужно 2 плюс 3, а плюс b посчитать. Мы а и b подставляем и результат. Вот здесь проблем не будет. потому что с одной стороны у нас уже есть некий язык понятный И мы легко с этого языка на язык программы все переводим, и мы не забудем тут ничего, потому что плюс определен совершенно точно, вот два входных элемента определены точно, забыть нечего, состояние сложное.
нет мы взяли вычислили результат и у нас все хорошо ну там если сложная формула мы там пару тестов написали для того чтобы на паре значений там проверить и все Казалось бы, плюс определён точно, а потом оказывается, что кто-то оператор за оверлоудил в другом файле. Это правда, это правда. И это на самом деле проблема в чем? Суть этой проблемы будет в том, что мы уже не можем доверять какому-то фрагменту кода, потому что есть...
перегруженная версия, про которую мы можем не знать. То есть она где-то вне, и наш код не в курсе про нее. То есть на каком-то расстоянии от того места, где мы кодируем, кто-то что-то переопределил. И мы получили вот эту проблему. На самом деле, если мы будем вот в такого рода проблемы углубляться, то мы выясним, что проблема ровно в эффектах. которые возникают от вычисления в нашем коде. Когда мы говорим о простом вычислении, вот на входе значение, вот формула.
Вот у нее результат. Мы получаем так называемую чистую функцию. А если вам слово... чистая функция не нравится, если она слишком математическая для вас или слишком функционально-программистская, то вот авторы книжки, которую я упомянул, называют ее предсказуемой, predictable. То есть... Вот входные данные, вот расчет, вот результат. Все предсказуемо. И все хорошо. И проблем здесь возникнуть не может. Проблемы возникают там, где помимо вот такого чистого вычисления...
возникают побочные эффекты. И мы привыкли, что любое полезное программирование это все-таки в некотором смысле побочный эффект. Когда мы напечатали что-то на принтере, показали что-то на экране, мы приняли данные из внешнего файла, сделали вывод и получили что-то. Или, например, подключили модуль к программе, вот то, что Егор упоминал, там переопределена какая-то операция, и мы, как бы не меняя нашу программу,
столкнулись с тем, что она начала работать как-то иначе. Потому что что-то добавилось в контексте, ну что мы не контролируем. И получилась вот проблема. которую нам нужно теперь как-то решать. То есть наш язык позволяет таким проблемам появляться. И в этом сложность, в этом источник проблем. То есть если бы нам было достаточно писать только чистые функции... то мир программирования был бы гораздо скучнее. А вот из-за появления побочных эффектов, сайд-эффектов,
Все становится резко интереснее, но при этом резко опаснее с точки зрения появления проблем. Поэтому, когда мы говорим в шутку о серебряной пуле, которая решит все проблемы... то мы на самом деле имеем в виду следующее. Мы видим, что в наших программах есть слабо контролируемые побочные эффекты, и мы хотим поставить их под контроль.
сделать так чтобы эти проблемы возникали там с меньшей вероятностью реже то есть мы фактически хотим сделать так чтобы в наших программах было меньше источников проблем контролируя те места в которых эти проблемы часто возникают Можно наивный вопрос? Вот смотрите, позади 400 выпусков подкаста «Подлодки». Плюс, Виталий, я слышал, что ты на конференцию ездил, тоже рассказывал про систему эффектов. Но ты сейчас описал концепцию, которая вроде бы суперлогична.
и, казалось бы, как будто с испоконников должна уже быть. Это какой-то действительно новый подход? Или же... давно уже обсуждалось, опять же, может быть, в мире функционального программирования, и сейчас стал протекать в более такие прикладные языки. Да, было именно так. Второй вариант, который ты описала, именно так. Началось это очень давно ближе к функциональному программированию, и сейчас начало протекать в другие языки, как и...
Очень многое другое в мире программирования. Ну вот, скажем, 80-е, считается традиционно, что вся эта тема с системами эффектов возникла примерно в 80-е годы. То есть 40 лет назад примерно, может быть, чуть меньше. Причем даже не столько в программировании, сколько... В семантике программ. Но вот тогда была очень популярна такая область, когда хотели формальными методами, математическими методами описать поведение программ.
И люди, которые этим занимались, вот они смотрят, ага, ну вот в программах есть хорошие куски, вот те самые чистые функции, а есть всякие... побочные эффекты, с которыми непонятно, как вообще математически обходиться. И они говорят, ну хорошо, ну давайте мы вот как-то ограничим вот эту часть, которая... работает с побочными эффектами, каким-то загончик какой-то это все отправим, и там будем своими другими методами контролировать.
Все остальное в программе будет чистым, красивым, и у нас все будет хорошо. Ну, я не знаю такой доклад, но это звучит как что-то близкое. То есть, действительно, внутри у нас такая чистая, красивая, вся из себя функциональная система. Ну, а снаружи что получится? Но тут скорее Желание сделать и снаружи Тоже все очень хорошо И вот заметьте сразу Вот когда заметили, что можно вот таким образом структурировать семантику программ, разделяя на части, исследователи поняли, что...
Операционная система во многом так функционирует практически с самого начала. Вот смотрите, как работает любая программа. Любой программе не так уж много разрешено. Она работает на процессоре, но часть инструкции от нее скрыта, часть фрагментов памяти от нее скрыта, и все это контролируется операционной системой. Фактически программы могут только вычислять. А если они хотят сделать что-то с ресурсами компьютера, серьезное, вывести что-то на экран...
Прочитать вывод из файла. Все это делается с помощью системных вызовов операционной системы. Это может быть спрятано глубоко в библиотеках, которыми мы пользуемся. Но любая такая задача, время узнать у компьютера, это все системные вызовы. И в некотором смысле у нас уже есть такое разделение. Мы можем считать, что программа работает чисто, но в некоторые моменты времени она обращается к операционной системе.
через интерфейс системных вызовов для того, чтобы получить какие-то побочные эффекты. И операционная система в этом смысле работает как такой хендлер. обработчик побочных эффектов она все делает для нас то есть не наша программа это делает мы там сделали там брит какой-нибудь или print а мы на самом деле внутри вызвали системный вызов И операционная система выполнила для нас что-то, чего мы попросили. Так что...
Сама эта идея действительно существует давно. В операционных системах она фактически с самого начала. Как только появилась операционная система, это было в 50-е годы. Я вот помню, когда-то давно читал курсы операционных систем, я там с этого начинал, что в начале-то операционной системы не было. На железе запускали программу.
А потом появились программы, мониторы специальные, уже которые начали выполнять функции для других программ. Так что это действительно началось давно. Но в 80-е годы начала появляться такая теория.
которая дальше не сильно пошла, а где-то в нулевые... Уже в первую очередь в Haskell, а потом и в других функциональных языках все больше и больше начала развиваться вся эта идея, как нам писать программу уже на... реальных языках с контролем побочных эффектов ну и вот так мы пришли к сегодняшнему дню где довольно много языков уже реально этими идеями пользуются. Окей. А давай попробуем дальше понять, есть ли какие-то разновидности сайд-эффектов.
Да, смотрите, слово side effect само, побочный эффект, оно обычно в какой-то момент перестают им пользоваться, потому что и начинают пользоваться просто термином эффект. Потому что это уже будет что-то, что мы контролируем. То есть это больше, как только мы начинаем что-то контролировать, это уже не побочный эффект. Это уже просто эффект, который является результатом работы нашей программы.
То есть, побочный, само слово побочный, оно плохое в том смысле, что, то есть, само слово нормальное. Но иметь побочные эффекты плохо, потому что это что-то бесконтрольное. Как только мы на этом фокусируемся... Мы перестаем говорить побочные, мы говорим просто эффект. И действительно, оказывается, что когда мы вычисляем...
У нас есть очень много разных эффектов, которые действительно очень сильно друг от друга отличаются, но при этом остаются некой общей концепцией. И я вот могу прямо... огромное количество таких примеров дать вот давайте по порядку начнем самый первый вид вычисления такого в котором кажется, что эффектов никаких нет, это уже упомянутые чистые или предсказуемые функции. Вот это мы начинаем с базы. Вычисления вообще без эффектов.
Входные данные есть, есть некий алгоритм расчета, есть результат. Если данные не меняются, то результат всегда один и тот же. И мы даже можем, скажем, это... не перевычислять, у нас есть гарантия, что результат для одних данных будет всегда один и тот же. Это такой базовый способ вычислять. Дальше начинаются другие виды, когда мы что-то добавляем еще.
Например, а что если иногда у этого вычисления результата может не быть? Ну, самый простой вариант – деление на 0. Это неопределенная операция, делить на 0 нельзя. И мы должны каким-то образом сообщить, что у этого вычисления результата нет. В некотором смысле это похоже на тот option, который Катя упоминала в начале. То есть это вычисление, но у него есть некий эффект, возможность отсутствия результата. Или.
А что если в процессе вычисления возникнет какая-то ошибка? И один из стандартных вариантов, который есть во всех языках программирования, это исключение, exceptions. То есть в этом месте возникло исключение. У нас в этом месте нет информации, достаточной для того, чтобы обработать это исключение. Значит, мы там раскатываем стек вызовов, находим где-то обработчик, который для этого исключения установлен, возвращаемся туда. То есть, в каком-то...
В другом месте продолжаем выполнение, а вот тот фрагмент, в котором исключение возникло, мы его до конца не вычисляем. Это некий вычислительный процесс. Еще раз. Вот компоненты. Вот в одном из мест возникла ошибка. Мы игнорируем последующий код. Возвращаемся куда-то назад по стеку вызовов. И там продолжаем с хендлера в другом месте. То есть это...
сложное вычисление, ну, некая его разновидность. И получается, в этом случае исключение как раз является примером эффекта. Да, да, именно так. То есть эффект это... Понятно, мы уже не назовем его побочным эффектом. Когда у нас есть язык с поддержкой исключений, это не побочный, это один из возможных вариантов развития событий, который учтен в нашем языке программирования. Опа, а вот это, кстати, интересный вопрос.
Ну, есть определенные языки, по-моему, там тот же Swift, например, в котором функцию, которую может бросать исключение, ты должен специально пометить на самом деле ключевым словом каким-то. И ты понимаешь, что вот эта функция может выбрасывать исключение, а какая-то функция от нее...
это, например, не ожидается. Вот мы говорим, что исключений не являются сайд-эффектом вообще в любом языке, где не просто у тебя есть... Концепция исключений на уровне языка или в тех языках, где у тебя есть прям специальный способ пометить, какие исключения, какая функция может бросать. Мы же обещали с самого начала быть language agnostic. Это что означает? Если мы говорим про какой-то конкретный язык программирования, там могут поддерживаться исключения, могут не поддерживаться.
могут контролироваться разным образом. Вот, например, в C++ есть замечательные такие же пометочки froze, в которых мы пишем, какие исключения могут бросаться в этом коде. Но при этом функция может бросать все что угодно, и никто ей слова не скажет. И вообще в какой-то момент от этой идеи указывать выбрасываемые исключения в C++, ну если они отказались, то очень много кто их в принципе не использует, потому что это просто была дурацкая идея с самого начала.
В каких-то языках, в Java очень длинная история с исключениями, там были специальные исключения, которые ты обязан был обработать, checked exceptions, и тоже с ними куча проблем. И когда Kotlin делали, сказали, что нет, мы такого делать не можем. будем потому что это катастрофа просто то есть автор языка программирования он естественно думает какие исключения нужны в его языке но вот о чем я говорю я говорю о том что
Мы, в принципе, можем не реализовывать исключения в языке, если у нас есть вот этот некий общий механизм систем эффектов. Просто исключение оказывается одним... тривиальным частным случаем системы эффектов и чем она богаче тем проще там будет эти вычисления с исключениями реализовать и проще их контролировать то есть автор добавляя exception и исключение в свой язык решает какую-то частную проблему ведь мы же в процессе перечисления видов вычислений
Можно сделать некий общий механизм, а можно сделать частный механизм. И вот добавление исключения – это решение частной проблемы. Некое решение проблемы с одним только видом эффектов. Но, естественно, по-разному это может выглядеть. А вот еще вопрос. Вроде ты сказал, что... как исключение – это один из вариантов результатов наших вычислений. Но и нормальный результат – это тоже результат вычислений. А нормальный результат является эффектом в этом случае или нет?
Обычно говорят так, что есть вычисление, у него есть какой-то результат, а есть некий эффект, который этот результат либо сопровождает, либо отменяет. Это уже зависит. То есть в принципе удобно считать, что всегда есть какой-то нормальный результат потенциально. Но вот когда мы получили исключение, мы не называем его результатом все-таки. Потому что это такое... Ненормальное завершение программы.
Видел я некоторые программы, где на этом были очень интересные. Интересная логика построена на exception, но не будем об этом. Ну, кстати, я вот помню из своего образовательного опыта всякие такие штуки, когда мы с помощью исключений... ребята эмулировали всякие go-to, выходы из цикла досрочные. Ну, бывает, да. Но понятно, что этот инструмент не для этого создан.
Все-таки скорее для исключительных ситуаций. Не зря так называется. Дальше. Давайте идти по списку вычислений и смотреть. Фактически мы смотрим на то, какие бывают эффекты. Потом уже будем говорить о том, как это все обобщить. То есть, как это вот такую единую систему эффектов в языке построить. Блокирующиеся вычисления.
Вот мы делаем ввод-вывод, например, по сети или с жесткого диска. И это вычисление такое. Мы делаем операцию, и пока не будет какого-то ввода, мы не можем продолжать. Это некий эффект. Это действительно можно считать побочным эффектом, а можно и не считать. Вот смотрите, вот вызываемую функцию read. в том или ином языке. И у нее как-то в сигнатуре не сказано, что она может заблокироваться.
То есть она как бы, ну просто законная функция. От функции, которая возвращает псевдослучайное число, она никак не отличается. Но при этом у нее внутри есть зашита эта возможность заблокироваться навсегда. То есть это такой эффект, который мы в современных языках программирования по большей части игнорируем. И рядом с блокирующимся вычислением находится медленное вычисление.
Ну, вот как вот такая, как бы в кавычках медленное вычисление, которое иногда неотличимо от блокирующегося. Ну, что такое медленное? Но оно может там вычисляться 10 часов. а может трое суток, в зависимости от задачи, которые мы решаем. То есть получается, что это некий особенный вид вычисления, с которым мы боремся таймаутами какими-то.
И мы часто объединяем медленные вычисления. Мы говорим, что ну хорошо, пусть оно блокирующееся, но если оно за 10 секунд не разблокируется, то давайте его прервем, потому что невозможно уже ждать это. То есть вот такой некий вид вычисления. Или параллельные вычисления, когда мы много рядом параллельных потоков или еще чего-то запускаем. Это некая...
Некое особенное вычисление. Или вот очень модное сейчас асинхронное вычисление. Когда мы... явно не блокируемся, мы позволяем вот в тот момент, когда бы наше вычисление заблокировалось, там... Какую-то другую нить выполнить. Что-то другое. Сказать, что у нас много одновременно всего выполняется. Хорошо, это вычисление. Пусть оно заблокировалось. Мы в другом месте продолжим.
И мы пишем код так, чтобы эта блокируемость была спрятана. То есть мы пишем там suspend функцию, еще что-то вот такое асинхронное. И при этом внутри там все равно... какая-то блокировка будет, но мы ее прячем от себя. То есть, какой особенный вид вычисления. Еще пример. Вычисление с кэшированием. Вот практическая проблема. Мы не хотим перевычислять одно и то же много раз. Там запустили какой-то сервис, он нам выдал результат.
Затем запустили его еще раз, те же входные данные предоставили. И он нам вместо того, чтобы заново все вычислять, выдал из кэша значение. Это пример вычисления такого особенного, в котором есть некий эффект. То есть кто-то взял и посадил туда кэш. И... обеспечил функциональность обращения к нему, если это вычисление уже было проведено. Сейчас. Этот пример, мне кажется, немного отличается, потому что все предыдущие, про которые ты говорил, это были какие-то видимые эффекты.
которые пользователь вызываемой функции видит. Exception — ты видишь. Медленное вычисление — ты видишь. А то, что есть там какое-то кэширование, нет кэширования, это же какие-то детали реализации. которые не влияют на то, что ты в итоге получаешь результат. Для тебя эта функция сработала как для пользователя, как та же самая функция 2 плюс 2. Не важно, кэшируется в ней результат сложения или не кэшируется. Вот, короче, что про это скажешь?
Во-первых, она сработала гораздо быстрее. То есть тайминг это серьезный эффект. Одно дело ты получал результат в течение часа, другое дело за 3 секунды. Если твой код как-то учитывает тайминг выполнения запросов, то на тебя это очень большое влияние оказывает. Так что это все-таки заметная вещь. Во-вторых, как кэши реализуются? Ну это какой-то стейт, который дополнительно появляется там внутри. То есть ты начинаешь...
специальную работу со состоянием проводить. Поэтому это тоже несомненный эффект особенный. Но ты прав. Тут как раз то, о чем ты говоришь, очень правильно иметь в виду. разнообразие таково что очень много сюда можно привязать в этом же смысл абстрагирования разработки общих систем именно в том что мы очень много выглядящих сильно по разному случаях, можем подверстать под одну и ту же идею. Так что это действительно другой способ, но общая идея эффекта все равно возникает.
В распределенных системах, я начал говорить, работает очень много с отказоустойчивостью. Вот как сделать так, чтобы сервис был доступен 100% времени. Это решается как-то добавлением нодов, которые обрабатывают запросы. И это тоже некое специальное вычисление. Когда у тебя есть пул серверов, которым ты отправляешь запрос, ты не знаешь, кем конкретно он будет обработан, но кем-то будет, и у тебя всегда есть больше одного узла, который потенциально может твой запрос...
обработать. Значит, в какой-то момент времени ноды начнут крэшиться, и у тебя получится более медленное получение результата. Но у тебя оно все равно будет, потому что кто-то живой все равно остается в системе. Вычисления с некоторой особенностью. И это тоже пример такого... Эффекта, который мы можем добавить к нашему вычислению. Так же, как и в предыдущем случае. Это может у клиента выглядеть как просто чистая функция, но к ней будет добавлен эффект такой отказоустойчивости.
Совершенно другой пример – случайность. Если это настоящая случайность, а не какая-то не псевдослучайность, случайность, основанная на каких-то физических принципах, например. Тоже мы запрашиваем результат, нам выдается случайное число, и это некий эффект. Вот самая непредсказуемость результата – это тоже эффект, которым мы часто можем пользоваться. Физики очень любят вычисления, в которых заранее заложена погрешность. То есть они берут данные от приборов.
Приборы часто аналоговые, всяческие датчики. И они просто, они тебе выдают какое-то вещественное число, там 10 знаков после запятой, но ты им доверять не можешь, потому что это вот тоже какая-то... Непонятно откуда взявшиеся цифры. Аналоговая величина с погрешностью переводится в цифровую всегда. Значит у нас может быть вычисление, в котором... эта погрешность заранее заложена. Погрешность может уменьшаться, увеличиваться по ходу вычислений. Вроде как тоже мы вычислили все по формуле.
Я помню, когда я учился в университете, у меня там был гигантский курс численных методов, три семестра численных методов. И там я вот понял, что... вычисление, если это настоящее вычисление, оно не такое уж и чистое. И даже помимо всяких там оверфлоу и всего такого, даже там реализация, операция, деление... гигантские погрешности вводят всегда то есть это ну вот некий эффект погрешность вычислений любителей фреймворков всяких типа спринга
Любят такие концепции, как dependency injection, например. Что это такое, если мы на некий абстрактный уровень поднимемся? У нас... идет вычисление в некотором контексте, который для нас кто-то сформировал. В этом контексте могут быть какие-то реализации интерфейсов в виде объектов, специально кем-то созданных, сформированных. И кто-то это как бы...
подсаживает в нашу программу. То есть мы начинаем вычисления и знаем, что если мы вот дернем вот за ту ниточку, то там придет какой-то объект и чего-то нам сделает. И тот, кто запускает наше вычисление, может тут одно подсунуть, а там другое подсунуть. В зависимости от того, что там ему нужно. И это тоже пример эффекта.
Про который мы можем сколько угодно говорить. Функциональные программисты могут сказать, что это монада ридер, которая тебе дает возможность узнать, чего-то из контекста достать. Но на самом деле вот dependency injection это тоже... в некотором смысле мы вычисляем в каком-то заранее сформированном контексте кто-то этот контекст для нас заготовил
А мы просто оттуда дергаем за ниточки и достаем то, что нам нужно в каждый момент времени. В некоторых языках программирования есть генератор. Вот в питоне, например, Yield операция. Такой простой способ генерировать последовательности чисел. То есть функция выглядит примерно так. Там какой-то цикл, там делается оператор yield, а тот, кто пользуется этим генератором, он не сразу все значения получает, а как бы по чуть-чуть.
То есть запросил следующее значение, оно сгенерировалось. Запросил следующее, оно сгенерировалось. Вот в отличие от вычислений, когда мы сначала, скажем, сгенерировали список, а потом обработали все его элементы. Мы как бы дернули, получили элемент, обработали. Дернули, получили следующий, обработали. Вот так работают генераторы. И сам генератор это тоже некий эффект. Там внутри наверняка есть состояние.
Он знает, как приступить к вычислению следующего элемента. Но оно особенное. Ну вот в питоне взяли, добавили оператор yield для этого. По многих других языках такого нет. Для чего нужны все эти примеры? Для того, чтобы наши слушатели увидели паттерн. Есть много разных вычислений. И они выглядят совершенно непохожим друг на друга. Но в них есть некое общее зерно. И вот это общее зерно это некий эффект, который вычисление сопровождает. То есть это очень глубокая идея.
Много всего, что есть в языках программирования, реализуется через вот эту глубокую идею. А можно для медленных? Я не успела задать вопрос. Супер, как я люблю все выпуски. Где-то про теорию с Виталием Брегрескиным. Ты начинаешь слушать такой, и я все понимаю, я понимаю все каждую секунду времени, и вдруг в какой-то момент ты понимаешь, что абстракция уже от тебя слишком далеко. Вот, я на dependency injection где-то потерялась, где там эффект? Сам контекст есть эффект?
Или момент, когда мы как раз берем то, что нам подсовывают? Короче, можно тут еще раз. На самом деле и то, и другое. Потому что это неотъемлемые части. Для того, чтобы быть способным к чему-то... обратиться в данный момент времени в процессе выполнения нужно чтобы кто-то этот контекст сформировал да то есть обе части там важны то есть эффект проявляется в тот момент конечно когда ты дергаешь за за ручку и получаешь что-то какую-то функциональность
Но там же много всего, там же всякий hot reloading, то есть это потом кто-то возьмет, подменит его в какой-то момент. То есть это все проявление вот такого общего эффекта. То есть и формирование контекста тоже здесь важно. Это тоже часть игры. Удивительно, но еще не закончились у меня примеры вычислительных процессов. Я готов продолжать. Один из базовых примеров просто мутабельность. То есть, когда у нас есть какой-то state,
С которым мы работаем. Там база данных в процессе вычисления используемая. Или мы что-то меняем. В памяти что-то. Это тоже, некоторые языки программирования это запрещают, некоторые позволяют бесконтрольно, некоторые позволяют, добавляют фичи, которые контролируют это, например, Rust, которым я много... работаю последнее время он ставит под контроль мутабельность и во многих местах запрещает ее то есть там вот сложно взять там написать алгоритм который циклом проходится
по массиву и все его элементы на что-то меняет. Или вставляет в середину массива элемент. Такой код становится сложным, потому что мутабельность известный источник проблем, важный пример эффекта, который... Хочется поставить под контроль. Еще один пример вычислений, такой даже в разных языках абсолютно по-разному к этому подходит, есть такая общая концепция окрашенных функций, colored functions, так называемые.
Ну, когда мы делаем что-то такое, мы говорим так. Вот эта функция особенного типа, особенного цвета, ну, скажем, есть зеленые и красные. И правила такие. В зеленых функциях мы можем вызывать все что угодно, а в красных функциях мы можем вызывать только красные функции. И эта идея, она очень богатая.
Она, например, реализована, ну вот в языке Kotlin есть suspend функции, когда мы часть функции помечаем suspend, и там определенные правила вызова. То есть нельзя... вне suspend функции просто так взять и вызвать suspend функцию нужно обязательно подготовить какую-то среду выполнения в которой там где будет там какую-нибудь там не знаю там пулку рутины все так
Что-то нужно специальное сделать, чтобы получить возможность запускать вот такую специальным образом окрашенную функцию. И это тоже эффект. Вот сама возможность такого... контролируемого выполнения каких-то функций. Или в других языках async функции. Ну, оно может по-разному быть реализовано, но тоже примерно такая идея. Мы часть функций взяли и покрасили. Или там в Rust есть Unsave блоки.
То есть вот этот блок, в котором ты можешь работать бесконтрольно, с памятью, делать вызовы C функции из библиотек соответствующих. То есть ты взял, покрасил функцию или блок какой-то кода специальным образом. как бы ограничив его от других частей программы и это тоже как это неудивительно дает нам некий пример эффекта
Тут на самом деле семантически могут быть сложности, считаем мы это эффектом или нет, но это не так важно. Важно, что это вот такой заборчик, которым мы отгородили часть программы и как-то специальным образом с ним работаем. Можем ли мы сказать, что языки с какой-то продвинутой рефлексией, где ты можешь подмешать какое-то дополнительное поведение какой-то функции в рантайме, тоже можно отнести вот к таким эффектам? Да.
Да, мы можем такое. Рефлексия в данном случае становится способом реализовать такое наблюдаемое поведение. Ну да, это соответствует этой картине. Окей, рассмотри, а есть же еще... Сейчас я попробую подумать сформулировать как вопрос. Давай пример приведу. Кажется, дополнительный эффект оказывает не только сама программа, но и контекст, в котором она выполняется. Например, есть у нас мобильное приложение.
В нем есть какие-то вычисления, и программист может быть уверен, что у него там замечательная чистая функция, предсказуемая, которая прекрасно все вычисляет. Но что непредсказуемо и не поддается его контролю, это мобильный операционный... система которая может взять и принять решение погасить приложение ровно в тот самый момент когда эта функция выполняется и вот с одной стороны казалось бы эффектов никаких нет а с другой
Есть эффект, получается. Вот как это попадает в классификацию? Вообще, и среда исполнения, влияет ли она какую-то роль здесь? Является ли она компонентом системы эффектов? Да, я думаю, что можно считать ее компонентом системы эффектов. Ну вот как, возвращаясь к тому примеру, о котором мы вначале говорили, операционной системы вообще, которая контролирует программу, понятно, что это несомненно...
часть другой вопрос в том а можем ли мы там что-то сконтролировать или нет то есть как бы тут скорее когда мы говорим об эффектах мы скорее приносим некий аналог такой среды вычисления внутри нашей программы для того чтобы иметь возможность про это тоже как то рассуждать и тоже как то контролировать Грубо говоря, идея в том, чтобы добавить операционную систему внутрь нашей программы. И на самом деле много, то есть операционная система это один из примеров, но мы опять же с другими...
Примерами таких окружений сталкиваемся постоянно. Ну, например, мы там запустили в дебагере программу. И там тоже такая суперконтролируемая среда, где она останавливается после каждой строчки нашего кода. Мы там можем инспектировать что-то. То есть это некая особая среда. Всем известно, что если запускать под профилировщиком, например, программу, она работает гораздо медленнее, чем обычный релизный билд. Потому что профилировщик вставляет какие-то специальные туда штуки.
которые замедляют все. То есть поведение отличается. И это тоже влияет на то, как наш эффект работает. Или там... Всегда мы пишем программу, мы ее запускаем. На машине разработчик, она себя одним образом ведет. Потом есть какая-то тестовая среда на сервере, другим образом. Там есть какой-то уже на продакшене, что называется, запустили. по-умному называется эксплуатационное окружение то есть там где программа реально будет работать там все совершенно другое и фактически мы
Хотим следующее. Вот мы сказали, да, у нас есть много разных видов вычислений, таких разных видов эффектов. Есть много вот таких сред, которые вносят некоторые особенности в процесс вычисления. И мы хотим... Это объединить, и по сути, вот если мы и то, и другое объединим, то мы получим систему эффектов в языках программирования. Всем спасибо, с вами был подкаст «Подлодка».
Так, отлично. Прошел час выпуска, мы разобрались, кажется, с тем, что такое система эффектов через примеры того, что является эффектами. Окей, а давай попробуем это теперь немного приземлить, как оно может примерно реализоваться, через какие идеи и концепции. Да, давайте, смотрите, мы... сказали, что у нас есть разные вычисления, разные эффекты. Мы сказали, что у нас есть такая среда или окружение.
А дальше начинает работать очень простая мысль, которая в программировании существует буквально с самого начала появления языков программирования. Элементы которой мы видели уже в языке... на котором писала Ада Лавлейс в одном из наших предыдущих выпусках. Это идея декларативности. Когда мы пишем не набор инструкций про то... что нужно сделать, а мы их отделяем друг от друга. То есть мы сначала описываем, что должно происходить, а потом в каком-то другом месте.
Исполняем эти инструкции. Вот если мы на большинстве современных языков программирования пишем сейчас. Ввели вот это значение с клавиатуры. потом взяли его, распечатали, потом там с ним еще что-то сделали, то есть набор инструкций, что нужно сделать, то работая системой эффектов, мы начинаем писать немножко по-другому. Мы говорим... так вот в этом месте нужно сделать вывод из клавиатуры потом взять оттуда результат переместить его куда-то в другое место
В том месте нужно будет сделать, напечатать что-то. Потом нужно будет запустить распараллеливание и в двух потоках чего-то сделать. То есть декларативность. Вместо того, чтобы говорить, делаем это, делаем это, делаем это, мы как бы говорим, нужно сделать вот то, потом сделать вот то, потом сделать вот то. Используя при этом глаголы, знакомые нам.
То есть напечатать что-то, print, или там ошибку сгенерировать, fail или raise, или там случайное число получить, get random, что-нибудь такое. То есть это все... Мы пишем эти действия с эффектами, но это не вызов соответствующей функции операционной системы. Это некое значение. И когда мы нашу программу пишем в таком стиле, то мы получаем такое как бы дерево вычислений, которое потом...
Другая часть нашей программы, а именно обработчик эффектов, хендлер, будет обрабатывать. Вот он смотрит на это дерево всего того, что должно произойти. И, пользуясь самыми разными стратегиями, начинает это все исполнять. Так, вот тут пользователь просил чего-то напечатать. Печатаем. Смотрите, вот отделение произошло.
вот если сейчас мы пишем println и что-то печатается в этот момент то потом в системах эффекта мы говорим в этом месте должен быть println и в процессе обработки этого эффекта оно будет напечатано Но штука в том, что так как только мы отделили это, мы можем начинать... контролировать это по-другому то есть запрос выполнить некое действие становится значение в нашей программе которая мы можем анализировать то есть мы можем
Какое-то дополнительное знание, то есть глядя на вот эту декларативную часть, в которой ничего само по себе не исполняется до момента обработки, мы можем много всего про нее сказать. Мы можем... запустить алгоритм, проверяющий, верифицирующий этот фрагмент. Потому что, как только мы это дописали, ничего не произошло. Оно произойдет потом, в контролируемой среде.
В которой будет гораздо больше информации от контекста окружающего нас. И может быть мы можем там воздержаться от выполнения какой-то операции, если она будет слишком опасна. И... Вот это все будет уже происходить в момент обработки. Разделили на части. Вот глаголы тут есть, но они другой статус имеют. Они теперь просто данные. которые потом обработчиками будут реально обрабатываться. Так что системы эффектов это вот такие на низком уровне. Это...
написать дерево вычислений, чего нам там нужно делать, а потом отдельно обработать. Но важный шаг вперед здесь в том, что мы и обработчики эффектов тоже можем сами писать. а это означает что один и тот же эффект мы можем вообще говоря обрабатывать сильно по-разному зависимости от того что нам нужно
Как раз, видимо, в зависимости от окружения. Например, когда мы запускаем систему в тестах, мы явно не хотим, чтобы она ходила там в продакшн базу или работала с реальным инпутом или аутпутом. И как раз там можно подставлять какие-то другие обработчики, более простые. без реальных.
эффектов на что-то. То, что ты, по сути, с dependency injection и достигал. Поэтому мы это и упоминали в контексте эффектов. Именно так. Все правильно. Именно благодаря вот такому разделению мы все те эффекты... и получаем то есть вот все что мы там перечисляли с окружениями и с видами вычислительных процессов мы получаем благодаря вот такому разделению
То есть сама идея, она реально очень богатая, и она действительно позволяет нам очень много всего получать, плюс дополнительный контроль из-за того, что мы вот вначале декларативно описали. происходящий так мы можем всю программу описать какая бы она большая не было мы ее отдельно опишем а потом запустим на исполнение вот с помощью этих обработчиков
У меня до последнего момента просто кусочки паззлов в голове не складывались, сейчас они сложились, и, короче, прекрасно. Мне нравится модель. Пока она мне нравится, давай я тебе задам вам несколько вопросов от слушателей. Во-первых, нас спросили про некие алгебраические эффекты. Чем они отличаются от обычных и что делают их алгебраическими? Вот ты в своих примерах алгебраических эффектов не приводил, поэтому давай поймем, что это вообще такое.
Помните, да, я говорил про книжку Effect-Oriented Programming? Вот там, где они брали и все слова заменяли. Вот это на самом деле тот же самый эффект. Эффект в другом смысле. Я не использую термин алгебраический эффект, потому что мне это не нужно. Что такое алгебраический? Я помню совершенно точно, что смысл слова «алгебраический» я объяснял в выпуске про алгебраические типы данных. Это было уже. Выпусков 380 назад, наверное.
Смотрите, математики используют слово алгебраически, когда они хотят сказать о чем-то, что это что-то, с этим чем-то можно выполнять какие-то операции с определенными свойствами. Вот алгебраичность – это возможность рассуждать в терминах операции с определенными свойствами. Если у нас есть эффекты, и мы с ними работаем... то это уже операция. Одна из самых важных возможностей, которые мы с эффектами получаем, это возможность их использовать вместе, композировать. И это тоже пример операции.
То есть мы же и кэш реализовали, и случайное число получили, и напечатали при этом что-то. То есть мы воспользовались композицией эффектов. И это пример операции, которую мы выполнили. Значит, если мы дальше начинаем рассуждать об эффектах, скажем, в таком более математическом ключе, то мы используем слова операция, мы изучаем эти операции, свойства, какие у них есть, и поэтому они становятся алгебраическими.
То есть мы бы использовали это слово, если бы мы говорили про математику. А если мы говорим о программировании, то у нас просто эффекты, нам не нужно это слово. То есть вот эти самые обычные эффекты... Они и есть алгебраические, это одно и то же. Нам просто не нужен этот термин, потому что нас не интересуют свойства операции. Точнее, они нас интересуют. И все хорошее, что мы получаем от эффектов, это благодаря вот этим свойствам. Но нам математики доказали.
что свойства хорошие, все ок, можете пользоваться. И мы берем, пользуемся, и нам вообще неинтересна эта их алгебраичность и все такое. Спасибо математикам, они классные. Второй вопрос. Возможно ли эффекты в динамических языках? Конечно, возможны. Потому что этот базовый принцип разделения, декларативное определение и обработчики, он работает везде, в любом языке. Просто языки с типами, языки со статической типизацией, они добавляют больше уровня контроля, который вообще-то полезен.
Ну, мы изначально говорили, что мы хотим контролировать все эти эффекты. Но вообще говоря, не обязательно. Так что мне кажется, что в любом языке можно эту идею реализовать.
Но получать меньше контроля, но в динамических языках они к этому привыкли, поэтому все нормально. Они как бы с этим живут. Но выигрыш получать от идеи разделения... действий и их исполнения они все равно смогут получать и там Так, мы еще в самом начале выпуска обещали, что после того, как мы разберемся, что такое эффекты, что такое система эффектов, как с ними работать, мы попробуем поговорить про...
то, что вообще система эффектов нам дает, что мы получаем как результат их применения. Вот давай попробуем понять, что нам как программистам система эффектов на самом деле дает, кроме красоты концепции, с которой мы уже успеем. Давайте. Значит, смотрите. Первое, что я бы хотел тут назвать, это то, что мы получаем некое разделение состояний. То есть...
Всех тех данных, которыми работает наша программа, на внешнее и внутреннее. Что я имею в виду? Есть данные, с которыми работает наша программа внутри себя. Есть что-то, что связано с окружением. Вот всякий раз... Когда мы работаем в современных языках программирования, мы вот эту грань между внутренним и внешним состоянием постоянно преодолеваем.
То есть мы прочитали значение, вот оно в переменной, мы тут же его используем в чем-то другом. То есть вот все время туда-сюда прыгаем, и такого разделения нет. Как только мы написали декларативно, описали действия, которые будут происходить в нашей программе, вот то, что там есть, это внутреннее состояние. Все, с чем мы работаем и получили в процессе исполнения уже в обработчиках, это внешнее состояние. Как только мы их разделили друг с другом, стало проще и одно, и другое.
Вот в этом идея упрощения нашей программы. Когда все смешано и постоянно из одного в другое перетекает, нам сложнее это контролировать. Когда это разделено... Нам становится гораздо проще. Вот это первый такой результат. Упрощение. Всегда, когда мы делим что-то на части, эти части становятся проще. Принцип, восходящий к аналитическому методу Декарта еще. Поделили на части и более простые системы уже с ними работаем. Дальше. Мы получаем...
при появлении системы эффектов очень выразительные языки. Вот помните, да, вот я перечислял много разных видов вычислений. И мы говорили, что ага, вот в одном языке... Это есть там добавлена специальная конструкция для этого. Исключение вот такого вида. В другом языке добавлено что-то другое. В третьем языке добавлено там пятое, десятое. То есть получается, что...
языки. Ага, вот есть проблема, давайте допишем до нее реализацию. Ввели со spend функции, отлично. Взяли еще что-нибудь такое. Что дает нам из системы эффектов? Если все вот эти отдельные улучшения можно выразить единообразно с помощью одного механизма языка, и к этому же механизму можно кучу еще всего присобачить, то мы получаем гораздо большую выразительность без необходимости сам этот язык менять.
То есть в следующий раз, вместо того, чтобы добавлять еще одну фичу в язык, мы скажем, так это ж можно реализовать вот на этой системе эффектов. И один из самых красивых примеров этого... Это язык Acamel, который, к сожалению, не так уж прям очень популярен. Но вот что они сделали? Они там очень долго хотели добавить в язык асинхронные вычисления.
И что-то у них там не получалось. У всех есть асинхронность, а у них что-то не получалось. Что они сделали? Они вместо того, чтобы расширять язык, асинхронными вычислениями, расширили язык системы эффектов, и неожиданно оказалось, ну точнее, ожиданно совершенно, что вся асинхронность, все конкурентное программирование...
можно реализовать просто на уровне библиотеки без всякой дополнительной языковой поддержки, потому что это оказался частный случай. То есть язык стал настолько выразительным, что... конкурентность реализуется с асинхронностью распараллеливание исключения тоже оказались частным случаем который можно этим всем реализовать то есть вот простой относительный механизм, который позволяет кучу всего реализовать без необходимости добавлять это на уровне языка. Не хотела своими кринжовыми шутками...
перебивать. Очень хорошее, понятное объяснение, но языка Камал, к сожалению, пока не очень популярен, потому что мы не записали выпуск подкаста под лодки про него. И, кстати, Call for Papers открыт, да, Виталий, посматриваю под миги. его не было, да, Егор? У меня есть кандидат. Да, мы очень хотим записать, поэтому присылай нам кандидата, мы с радостью запишем.
Вот так, дорогие слушатели, вы видели воочию, как, собственно, вершится будущее подкаста «Подлодка». А второе, я просто хотела отметить, что, ну, классно, это же красиво само заложено в названии «Система эффектов». точечные какие-то случаи, а это все объединяется в систему и становится классно. Теперь прям хочется посмотреть, как это в реализации выглядит, но мне кажется, это фича, что не нужно, получается, каждый раз менять язык, ведь как...
какие-то каждый раз проблемы при миграции с одной версией на другую, все вводятся новые и новые концепции, ключевые слова. Звучит очень красиво. А можешь буквально немного, не погружаясь сильно вглубь, рассказать, а как оно в Акамле реализовано? Смотри, значит, они сделали следующее. Ну, на самом деле, мы вот про реализацию, наверное, чуть... попозже стоило бы поговорить и подробнее. Тогда можно пока тормознуть, если хочешь. На самом деле, я скажу несколько слов все-таки. Что такое синхронность?
По большому счету, это возможность прекратить выполнение на какой-то момент и потом его восстановить, когда у тебя появятся необходимые данные. Что сделали в Акамле? Они реализовали некий низкоуровневый механизм, позволяющий остановить и потом продолжить. И выяснилось, что асинкронность является его частным случаем. То есть эффект с возможностью остановиться и потом продолжить.
И вот этот механизм, который они сделали внутри языка, и с его помощью уже реализовали и исключение. Что такое исключение? Это продолжить совсем в другом месте. Вот здесь мы остановились, потом продолжили в другом месте. А синхронность мы продолжили в том же месте, но через некоторую паузу, когда там пришли какие-то данные. Если мы можем реализовать вот эту вот... нелинейное вычисление паузами, еще с чем-то, то этого уже достаточно для того, чтобы выразить асинхронность. Окей, спасибо.
Так, давай тогда дальше вернемся в основную веточку обсуждения. Что еще мы получаем от системы эффектов? Мы получаем очень сильную возможность контроля за происходящим. и если мы добавим в типы информацию об этих эффектах то мы сможем их тоже контролировать Вот если мы вернемся к исключениям в языках программирования, вот ты там упоминал фроуз и там чего-то, значит, мы как бы дополнительно к типам, которые в языке уже есть.
добавляем некий еще один механизм контроля. В какой-то момент с эффектами тоже пытались так делать. Говорим, ага, вот давайте добавим, скажем, есть система типов, а есть типы и эффекты. Но сейчас уже выяснилось, что можно прямо в сами типы запихать все, что связано с эффектами и получить хороший уровень контроля. В целом наша цель, с самого начала мы это говорим, мы хотим контролировать, что происходит в нашей программе. Это мы получаем с помощью эффектов.
И тут контроль – это хорошо, но мы можем точно так же дополнительно контролировать что-то еще. В плюс контроль, можно так сказать, контроль это корректно-некорректно, но контроль это еще и такое управляемое выполнение кода. Например. трекаем что происходит
То есть мы можем автоматически добавить логирование в тот момент, когда обработчики эффектов работают, мы можем в этот обработчик встроить логирование. И все действия, которые будут выполняться, будут куда-то в журнал записываться, который потом... то можно аудит выполнить и все узнать. То есть это свойство обработчика, запротоколировать все происходившее в программе. И это полезно, это тоже контроль.
Ну, только немножко другой. Или там, например, мы можем обеспечить задержки, мы можем обеспечить квотирование. Знаете, вот в операционных системах там есть предел... Количество открытых файлов. Или количество открытых файлов, открытых определенным пользователем. Это квота. И такой механизм квотирования тоже можно реализовать. Например, мы даем бюджет. процессу. Вот у тебя на println максимум 1000 println в программе или максимум запросов к внешнему серверу в течение секунды.
Это квота. И мы эту квоту можем встроить. И в тот момент, когда мы будем исполнять запросы, приходящие из декларативной части программы, мы просто ведем счетчик и говорим, так, вот процесс, вот твоя квота. Если стало больше, мы тебя задержим немножко. Притушим, и ты потом продолжишь, чтобы ни на кого дедоса так и не случилось. То есть механизм, который изначально позволяет вот такой контроль управления процессом вычисления обеспечить. Дальше.
Важный фактор, который всегда упоминается, когда говорят об эффектах, это модульность. То есть нам становится проще отделять части программы друг от друга. Почему? Потому что программа, как я сказал, превращается в такое дерево. Вот где разные эффекты на поветочках. А дерево это простая структура. Мы ее можем всегда тут под дерево выделить, там еще что-то. И как бы разделение на части, модульность становится...
проще, она естественная. Взяли тут веточку, подцепили и модифицировали это, просто подключив какой-то модуль. То есть мы встроили в нужное место программы нужное нам поведение. Тут на самом деле уже, конечно, стоило бы смотреть на конкретные языки программирования, потому что там это по-разному выглядит. Но в целом принцип такой, что мы можем разделять, нам проще разделять нашу...
декларативную часть программы на отдельные модули. Самое важное, это то, что мы можем эффекты, я уже поминал, это композировать друг с другом. То есть... Разные эффекты очень легко в одной программе соединять. И обрабатываются, у них обработчики разные.
Ну, там, принт и чего-нибудь там с мутабельностью какой-то. Но их очень легко в одной программе написать и потом уже по отдельности обработать. Причем с разными стратегиями. Ну, там... Мы можем взять, проанализировать, вот как мы этими эффектами оперируем в декларативной части программы и понять, что, например, две веточки вычислений можно запускать параллельно, потому что они никак не связаны между собой.
И там пром на двух процессорах загнать их и пусть они вычисляются. То есть благодаря декларативной структуре программы мы легко можем проследить, что там нет каких-то зависимостей по данным. А это прямой... путь к распараллеливанию. И всегда можно, как выясняется, добавлять примеры эффектов. То есть легко расширять наш язык и нашу программу, добавляя туда новые эффекты.
Потому что вот то, что я перечислял вначале, это далеко не полный список, и более того, как только мы начнем активно это во всех языках использовать, мы очень много других эффектов будем придумывать, и их легко в наш язык. Я сейчас еще думаю, что конечный автомат — это же тоже штука, которая, по сути, реализуется на системе эффектов, где эффект — это переход структуры из одного состояния в другое просто, по сути, правильно?
Да, я думаю, что это красиво так можно реализовать. Совершенно верно. Окей. Такой еще вопрос. Тоже, опять же, от наших слушателей. Еще раз, слушатели, дорогие, спасибо за вопросы. Они очень классные. Как тестировать эффекты? Смотрите, мы можем сказать так, что вот первая часть программы декларативная. Фактически это просто некая чистая функция и там тестировать можно как обычные чистые функции тестируют. Ну то есть там же задача...
Не в том, чтобы что-то сделать, а в том, чтобы описать, что нужно делать. То есть там тестировать легко. Мы это всегда умеем, мы там просто юнитесты написали и все. Что касается обработчиков эффектов, то тут сложнее, конечно. Но мы все равно, у нас есть преимущества. То есть мы можем, благодаря свойству композируемости, мы можем их по отдельности тестировать. что проще ну и уже вот уровень там интеграционного тестирования усложняет вещи но в целом мне кажется что
Не возникает какой-то особенной здесь проблемы с тестированием, опять же, благодаря разделению частей. То есть всегда части тестировать сложнее. Другое дело, что с тестированием тут важно упомянуть вот такой момент. Мы сами эффекты можем для тестирования использовать. Мы упоминали там тестовую среду выполнения. Подмена обработчика эффекта позволяет нам среду исполнения поменять.
То есть вот обращаться, ну то, что мы упоминали, не в продакшн базу, а в какую-то тестовую, там моки такие. Вот всякие моки становятся частным случаем таких эффектов. То есть мы просто подменили некий уровень обработки эффектов, и теперь мы будем идти не в базу данных, а куда-то в заготовленные значения. То есть это еще инструмент для тестирования программ очень удобный.
Ну а практика тестирования, я не уверен, что очень много про нее можно сказать. То есть это нужно писать код и изучать, как это делать. Ведь мы сейчас говорим о проникновении. идеи в мейнстрим. А дальше уже надо смотреть, какие проблемы будут возникать и как-то с ними бороться. То есть в этом смысле еще индустриальное применение новое. Не все можно сказать о том, какие сложности здесь могут быть.
Я предлагаю тогда к следующей части двинуться. Я помню, что мы пообещали в начале выпуска быть language-агностик, но все-таки давайте немножечко на прикладной уровень языков опустимся, но, опять же, постараемся... посмотреть, как бы всё множество охватить, и хочется поговорить о том, собственно, а как...
реализуется система типов, да, мы немножечко говорили уже, когда о Камо упоминали, и что вообще должен язык уметь, чтобы такую систему типов реализовать? Или вообще, может быть, в любом языке это можно сделать? Какие есть перереквизиты? первых хотел бы начать отвечает на этот вопрос со слов что а мы пока еще не знаем то есть вот всех всех деталей этого вопроса мы не знаем но вот у нас есть относительно много всяких исследовательских языков.
которые эту идею изучают. Вот есть Акамл, есть Скала, где системы эффектов уже популярны, и они есть. А вот где еще, кстати, я поняла, что не хватает еще чуть-чуть, да, давай перечислим, в каких языках так или иначе она реализована. в TypeScript библиотека соответствующая, которая прямо очень модная в последнее время. Там ребята сделали шикарный сайт. Например, это в JavaScript самое главное, в общем, в TypeScript тоже. То есть вот эти, вот Scala, TypeScript, Acamel, это...
приближенный к индустриальным языкам, это есть. А для других языков есть, ну, Хаскель, там чуть подальше, там это, в Хаскеле это началось с начала нулевых годов. В других языках делаются попытки. Например, пару лет назад на РастКонфе был доклад про то, как систему эффектов в Расте реализовать. То есть можно... это сделать и как бы есть примеры но не то что там этим кто то готов сейчас пользоваться потому что есть сложности даже не очень понятно природа этих сложностей
То есть действительно ли чего-то языку не хватает? Или мы просто пока еще не придумали, как в имеющий аппарат языка это вписать? То есть, it depends, что называется. Пока не очень понятно. Там в C++ можно вписать все, что угодно. В Java нельзя вписать ничего. что она такая вот специальная, чтобы она как раз сделана так, чтобы ничего туда не вписывалось. И это не бага фича. То есть, вот, скажем, для Котлина мы смотрим...
Какие-то элементы систем эффектов там есть и реализуются в библиотеке Arrow КТ. Это не не фуллблоун система, где вот прямо система эффектов как в скале, но это какое-то приближение к этому. то есть авторы языков программирования дизайнеры думают над этим то есть они все в курсе этой тематики и вот для кого-то это опция для кого-то нет но Про любой язык программирования мы можем делать. И что важно, мы же можем не прямо все сделать, мы можем чуть-чуть добавить, и уже получится плюс.
То есть уже какое-то расширение наших возможностей. Так что я дальше, отвечая на этот вопрос, могу просто по разным аспектам языков программирования пройтись и посмотреть, как, чего... где нужно быть, где помогает, и как бы, а где может быть не очень, но это все равно такой сейчас work in progress, поэтому...
Вполне возможно, если я тут что-то скажу, через какое-то время окажется, что кто-то придумал, как это сделать, и там ничего этого не нужно, например, специального чего-то. Да-да, мне кажется, по аспектам отличное. Первое, что приходит на ум, когда мы говорим про язык программирования,
Это синтаксис, собственно, как синтаксис участвует в системе типов. Помогает, не помогает? Вот похоже, что для того, чтобы реализовать систему эффектов, никакой специальный синтаксис не нужен. Тут нам... Очень хороший пример дает язык Scala, где некоторое время назад начали реализовывать так называемые Direct Style эффекты. Сам термин директ стайл, он такой очень смешной. А история там примерно такая. Ну вот в языке скала давно, практически с самого начала есть монады.
которые так любят функциональные программисты. И монады добавляют специальный синтаксис, так называемую do-нотацию. Ну или там не do-нотация, а там for. Так называемый, но неважно. То есть некий синтаксис, в котором вот эти монадические действия выполняются. Можно без него, но очень некрасиво выглядит. Вот прямо ужасно выглядит, программы становятся чем-то неудобоваримым. Синтаксис нам же для чего нужен? Чтобы выглядело хорошо.
дальше в скале добавляют direct style эффекты и директ означает что мы пишем просто как есть без дополнительного синтаксиса без вот этих for нотации И дальше мы начинаем смотреть на примеры, и выясняется, что это вот ровно как мы всегда делали, так всегда и делается. Вот просто написать код, вызвать функцию, это и есть Direct Style.
То есть раньше, чтобы получить эту функциональность, мы использовали специальный синтаксис, а сейчас оказывается, вот можно так что-то подкрутить в языке, что можно просто вызывать. А на самом деле оно не вызывается, а на самом деле оно вот декларативно что-то объявляется, обрабатываться будет потом в хендлере этого эффекта. Даже понадобился специальный термин, директ стайл, для того, чтобы описать, что мы на самом деле так всю жизнь писали. Там это, например, сделали с помощью так называемых...
неявных определений, контекстные функции, вот что-то такое, не углубляя в скалу. То есть они это сделали так, что это выглядит как обычный синтаксис, знакомый всем с рождения. А не вносит ли это путаницу, когда непонятно, в директ-стайле написано в итоге обычная программа или программа с использованием системы эффектов? Как понять? А в этом на самом деле и главный плюс такого директ-стайла и системы эффектов.
На самом деле, если ты не видишь, что ты используешь систему эффектов, и у тебя все нормально, то и прекрасно. Все хорошо. Если она становится прозрачной для программиста, это просто супер. Тебе можно хендлеры все дефолтные по умолчанию установить. И ты про это даже не знаешь, оно все работает само и все прекрасно.
А если ты начинаешь уже разбираться, хочешь заменить хендлер с дефолтного на какой-то другой, специальный для какого-то тестового окружения, ну ты там где-то в другом месте программы это сделал. Так что это плюс. это не путаница это плюс Просто снижаем когнитивную нагрузку на программиста. Следующий аспект, вот как раз слушатель тоже нам задал вопрос, есть ли какие-то способы реализовать систему эффектов чисто на типах?
Да, ну, то есть, сейчас, это не реализация на типах на самом деле, но типы, конечно, начинают играть очень важную роль вот в той части, в которой мы контролируем это все. То есть, если мы тип функции... каким-то образом закладываем исполнение некоторого эффекта. Например, эта функция печатает в консоль. И в типе функции как-то это зашиваем. То мы уже получаем некий уровень контроля.
То есть мы видим, что в сигнатуре этой функции появляется дополнительная информация. И дальше мы должны, если мы захотим вдруг исполнить эту функцию, хендлера обработчика для печати в консоль не будет установлена то у компилятора возникнут вопросы а чё это ты тут значит вызвал функцию обработчика у нее нет
куда я тебе буду печатать а где вот этот контекст сформированный то есть мы действительно добавляем в тип информацию о тех эффектах которые там используются и компилятор потенциально начинает контролировать а есть ли обработчик для этого эффекта или это где-то в воздухе повисло
И вот здесь, например, в самые первые способы, в способах реализации систем эффектов, в Haskell это было в начале нулевых годов, они там стали вводить так называемые множество типов. То есть... В тип функции встраивается некое множество, как перечень эффектов, которые в этой функции имеют место. эта функция мутабельная, она запрашивает время операционной системы, у нее там может быть кэширование, еще что-то, такой функция, множество этих эффектов.
И мы уже глядя на нее смотрим, что это за функция, что от нее вообще можно ожидать. А если множество эффектов пустое, то это чистая функция. тривиальный случай, мы уже ее не боимся, мы понимаем, что там все под контролем. То есть, опять же, вот эти вот froze, suspend и прочее, это все частные случаи, только здесь это на уровне как бы предусмотрено, что...
Это может быть и расширяемо, да, как раз то, про что я заговорил. Да, именно так. То есть, как бы, froze что-то, exception, это как бы некое дополнение к сигнатуре функции. Она не входит в ее тип. большинстве существующих языков а если мы реализуем эксепшены на уровне системы эффектов то она начинает входить в тип и и тут же начинают всякие вещи там полиморфизм начинает работать
То есть, когда один и тот же разные подтипы могут соответствовать одному общему типу. Вот для обычного фроуз... языки очень плохо с этим справляются а здесь это как бы если это в типе то сразу начинается много таких такой синергии богатая система димов начинает тут работать
В какой-то момент выяснилось, что вот такие множество типов, может быть, не очень хорошо работают. Появилась идея использовать abilities, так называемые, как бы в тип... встраивается дополнительный не параметр как бы вот как бы это сказать то есть мы к типу добавляем некую сущность которая начинает контролировать, что происходит с этим значением, как оно получено, что там с ним происходит. И, соответственно, мы эти дополнительные возможности добавляем или убираем.
Мы добавили, значит компилятор может потребовать наличия хендлера у этой возможности. И это начинает работать. В скале можно посмотреть или в языке юнизон таком экспериментальном. Там есть конкретные примеры, как отказались от идеи множества типов.
пришли к идее другого способа реализации с другого способа расширения системы типов тут вот я уже упоминал курс операционных систем свой там Всегда в курсе операционных систем изучаются системы контроля доступа ко всяким защищенным, ну там безопасность, в теме безопасность, и системы контроля доступа, и там есть разные варианты того, как это обеспечить. Это теория известная уже лет 50, наверное. Есть два базовых способа. Списки контроля доступа и мандаты.
И в некотором смысле вот этот ресерч в плане системы типов и систем эффектов, он повторяет шаги, которые были в теории систем контроля доступа. Ну вот что такое... Список контроля доступа. Это, скажем, у каждого файла написано, так, вот этот файл может быть, как в Unix, например, владелец этого файла может его читать и писать. Группа владельца может делать, читать, только читать, писать не может. Все остальные не могут ничего. Вот это простейший пример списока контроля доступа.
в некоторых системах можно описать там вот пользователь вася имеет право делать то то пользователь пользователь Маша Тота. И такой гигантский список получается. И он на каждый ресурс системы тоже составляется. А есть противоположный вариант. Мандаты. Когда каждый актор, такой процесс, несет с собой какое-то специальное значение, которое уже заключает в себя все права этого процесса.
И вот два вот этих список контроля доступа и вот такой мандатный способ контроля. И он как бы говорит, так, я такой-то, вот мой мандат, проверь, можно мне эту операцию выполнить или нет. Ну и кто-то там проверяет специальный контроль мандатов, а как-то криптографически зашито, что именно этот мандат в себе несет. Там подписи цифровые, там что-то такое. И выясняется, что возможности, вот эти abilities, это некая реминесценция мандатного контроля доступа, когда мы...
процесс вычисления добавляем некий токен, который каким-то образом потом контролируется. То есть мы используем идеи, которые в Computer Science много лет существуют, и вот на уровень... новых систем типов приносим. Но, что здесь важно сказать? Да, вот Scala пытается все жестко проконтролировать. А, например, в Акамле ребята поступили проще. Они сказали... А ну и что, что у нас система типов статическая? А мы не будем ничего контролировать, а нам пофиг. Пишите, что хотите.
Мы вот выразительность вам добавили, асинхронность через эффекты получили, а типы мы будем по минимуму контролировать, а дальше все остальное на усмотрение программиста. И это нормально. Не хотите контролировать? Не надо. Ну будет у тебя там исключение в рантайме. Ну, как бы... по логам прочтем, разберемся. Не впервой, скажем так. Да, именно. Это подчеркивает такую прагматическую направленность Акамла. Ну и ладно. Не важно. Потом разберемся.
Еще вопрос от слушателей в тему типов. Как соотносится система эффектов и линейные типы? Это хороший вопрос. Давайте такой рекап небольшой. Что понимается обычно под линейными типами? типами там идея такая тип значение этого типа может использоваться только один раз То есть линейность в этом смысле имеется в виду вот что. Вот у нас есть значение. Вот мы им один раз воспользовались для чего-то. Дальше...
Вот это чего-то один раз использовали для чего-то другого. Потом еще раз такое же сделали. И вот получается линейное существование некого значения. То есть мы его преобразовали. И больше у нас его нет. Вот это линейность. Это не нужно для простых вычислений, но это нужно для ресурсов. То есть ресурсы часто бывают такими одноразовыми.
Вот один раз воспользовались, и больше его уже нет. И компилятор может, если линейный тип реализован в языке, он может это контролировать. То есть он говорит, ага, вот этим значением вы уже пользовались, больше нельзя. Значит, память можно освобождать. Всякие вот такие вещи возникают. Вот это линейные типы. Интересно, что... Прямо применение теории линейных типов систем эффектов я не видел, но...
Я что видел? Внутри в реализации идея линейности очень широко там используется, потому что она все сильно упрощает. То есть мы, скажем так... В реализации самих эффектов, вот мы упоминали перед этим про синхронность, что там в реализации, что это возможность остановиться на какой-то момент, потом продолжить в каком-то другом месте.
Вот линейность, например, означает, что мы можем продолжить один раз. Вот выполнили этот код, все, мы уже к нему вернуться не можем, потом продолжим только один раз и все. А нелинейность означала бы, что мы можем этим кодом воспользоваться несколько раз. И вот это тоже возможно. Но это уже сложнее с точки зрения реализации, и поэтому, например, в Акамле от этой идеи отказались. То есть линейность в этом смысле разработчиков внутренней реализации сильно упростила жизнь.
Хотя в некоторых других языках этим не воспользовались и появилась возможность какие-то более богатые эффекты описывать, которые даже сложно у себя в голове описать. Так что... Прямо про линейные типы и особая связь я могу не знать чего-то, потому что там ресерч очень активный, много всего, но сама идея линейности, безусловно, используется.
Вот мы всё ещё о Camel, о Camel упоминаем. Есть ещё вопросик от слушателей в эту тоже тему. Почему эффекты в Unison или Unison? Признаюсь честно, первый раз сегодня на выпуске узнала про существование этого языка, и о Camel
требует специального рантайма. Ну, может быть, в ответе на этот вопрос стоит вообще порассуждать про то, как именно оно реализовано внутри, потому что именно специальный рантайм... позволяет эти вещи делать значит вот смотрите как вообще устроены компиляторы ну до сих пор вот мы написали код который что-то делает Этот код с высокоуровневого языка программирования транслирован на уровень ассемблерных инструкций, потом в машинные коды, и программа превратилась в последовательность машинных кодов.
Там есть стек, например, как дополнительная такая штука. Есть специальные операции типа прыжков, джампов в разные места. Но все равно это такая последовательность инструкции. С циклами, с функциями, вызов функций возврат обратно, раскрутка эстека, всего такого. То есть это некий способ исполнять последовательность инструкции. реализовали в кодленные suspend функции. И вот как только появились suspend функции, то вот так напрямую прямо компилировать в обычный код
нативный, с последовательностью инструкции, гоутупрыжками, функциями и возвратами из них уже не получается. То есть компилятор начинает код как-то трансформировать. В случае Котлина этот код трансформируется в стейт-машину специальную и компилируется уже немножко другой код. То есть, если мы непосредственно возьмем код...
ассемблерный, и попытаемся его восстановить, то там будет уже не совсем тот код, который написал разработчик. Там будет вот эта state-машина. Конечный автомат, в который превратилась... превратился код программы на suspend функциях код со синхронностью. То есть мы как бы увеличили расстояние. Но...
Сама последовательность инструкции, она такая же, как и была для всех языков. Хоть бы на фортран бы компилировали, хоть что. Просто компилятор такую дополнительную работу проделал, прежде чем генерировать код. В языках программирования, в низкоуровневой реализации очень давно существует идея так называемых продолжений или Continuation Passing Style. Идея которого в том, что мы можем нашу программу рассматривать немножко не в виде последовательности набора инструкций, а в виде функций.
которым добавлен специальный параметр, так называемое продолжение, смысл которого в том, что как только функция доходит до конца, Вместо того, чтобы делать возврат в точку вызова, она на самом деле вызывает вот этот свой параметр продолжения и управление передается вот в эту функцию. То есть мы, вызывая функцию, подаем ей некий специальный параметр, в котором программа должна продолжить свое выполнение после завершения этой функции.
Это так называемый continuation passing style. То есть мы передаем continuation в качестве специального параметра. И это немножко другой стиль выполнения. Но мы программу, любую программу вообще-то...
можем в такой стиль преобразовать. То есть вместо возврата в точку вызова, вызывать функцию, которая указана в качестве параметра. Есть... Некое развитие идеи продолжений, так называемые ограниченные продолжения или delimited continuation, очень популярный в языках программирования термин, у которого идея... В самом деле довольно простая. Делимитед, ограниченная. Вот мы берем какой-то кусок программы. Какими-то специальными конструкциями его ограничиваем. Начинаем исполнять.
И там дальше в какой-то момент исполнения возникает некая точка, в которой мы начинаем этим исполнением как-то хитро управлять. И вот в этой точке... мы получаем в качестве параметра снова весь этот фрагмент, внутри которого мы находимся. Вот это на самом деле ключевая вещь здесь, которая довольно сложная. И для программиста, привыкшего писать код в последовательности инструкции, это сложно представить. Но еще раз. Вот у нас есть фрагмент программы. Специальная точка внутри.
в которой мы получаем в качестве входа весь этот фрагмент программы. Какой у нас выбор? Мы можем как бы спрятать вот эту возможность и просто продолжить с того места, где мы остановились. Можем вернуться в начало и начать выполнение с нуля. перевыполнив тот же самый кусок кода. Эта концепция может показаться знакомым тем, кто с базах данных знаком с транзакциями. Транзакция откатилась назад и выполнение началось в начале. Кстати, это тоже...
Пример вычислений, который мы забыли упомянуть в начале. Это тоже эффект некий. Откат и исполнение сначала. Когда у тебя в руках, внутри фрагмента... код этого фрагмента, ты можешь делать с ним все что угодно. Исключение тоже так реализуется. Вот представьте себе, что вот в этом фрагменте Вот есть хендлер обработки исключений.
Вместо того, чтобы идти на начало, ты вот в этом фрагменте продолжаешь, у тебя случилось исключение в некоторой точке, и ты продолжаешь код в том месте, где у тебя обработчик исключения, catch-block. То есть ты не просто продолжаешь с того места, где возникла проблема, а где-то с другого места начинаешь работать. И вот это сложно представить, но и асинхронность... Остановиться и продолжить чуть позже. И исключение. Остановиться и продолжить в другом месте. И распараллеливание.
исполнить тот же фрагмент в двух потоках. Это все разные варианты, которые у нас здесь возникают, когда мы получаем такую возможность управлять фрагментом программы внутри этого фрагмента. То есть это продолжение, которым мы управляем. И языки программирования, конечно, там может совершеннейшее безумие происходить. И языки программирования могут это прятать, и они не дают интерфейс ко всем возможностям управления вот этим ограниченным продолжением.
Но они могут его реализовать. Вот в Акамле поступили именно так. Они реализовали. Вот такую возможность. Для этого технически там они прямо в хелпе Акамла написали, как они это сделали. Там просто, ну не просто, это, конечно, сложно. Они добавили некий специальный стек. некая специальная структура данных, которую они потом в код уже генерируют.
Они должны запоминать вот эти точки, в которых это ограничение существует, куда продолжает. То есть они там все технически это реализовали на дополнительных стеках, все это повесили в программу, сгенерировали код. И дальше выдали разработчикам библиотек эффектов очень ограниченный интерфейс к управлению выполнением кода. И этого хватило для реализации асинхронности.
То есть, реализовав поддержку ограниченных продолжений в рантайме, разработчики компилятора смогли дать очень много возможностей разработчикам. пользователям этого механизма. Но не все. Тут важно. То есть они, реализовав это, не дали полный интерфейс к этому. В Скале поступили похожим образом, но там они вообще все сильно спрятали. И сейчас в Скале 3 это work in progress, все, что с этим связано.
То есть они какое-то подобие вычислений, похожих на exception, дали в качестве интерфейса, но пока работают над continuation API. Как позволить вот этими фрагментами работать. В Хаскеле начали работать с эффектами без всякой поддержки в рантайме. И это означает, что это можно делать и без рантайма совсем.
Правда, в последнее время, последние годы добавили реализацию, потому что выяснилось, что если есть поддержка в рантайме, то можно какие-то вещи более эффективно с точки зрения перформанса делать. Но это именно разработка последних годов. То есть можно было бы обойтись и без такой низкоуровневой реализации, но решили это сделать.
Соответственно, возвращаясь к вопросу от слушателя, из которого мы это начали обсуждать. Нельзя говорить, что эти эффекты требуют специального рантайма. Возможно, это можно было бы реализовать и без него. И в Юнизоне, и в Акамле. Просто разработчики языка решили, что их язык сильно выиграет, если они эту поддержку реализуют. станет более эффективно, появится больше возможностей, что-то будет проще. Поэтому там этот специальный рантайм появился. Но может быть...
Это и не нужно, может быть, это и не требуется от языков. Но я все время хочу напомнить здесь, это все research, поэтому мы пока, может быть, чего-то и не знаем, это нормально. Учитывая, что мы поговорили довольно много про рентайм, следующий вопрос от слушателей прям как раз, мне кажется, сюда отлично вложится. А что происходит с дебагом всего этого добра? Потому что звучит довольно страшно.
Я надеюсь, что нам слушатели поверят, что это реально вопросы не от слушателей, а не какие-нибудь подсаженные казачки, которые в нужные места нам эти вопросы задали. Хороший вопрос. Очень хороший вопрос. Но я бы хотел обратить ваше внимание, даже вот отвечая на него, такой кусочек истории сделать. Много лет в языке Хаскель. Не было никаких стэк трейсов вообще.
И это имело, я сам помню, как я студентам объяснял, почему стектрейсы не нужны и почему, значит, их невозможно реализовать и все такое. То есть я говорил о том, что вот программа компилируется, хаскельная... в LambdaTerm, и все эти stack trace теряются, еще и ленивость там возникает, то есть как бы, ну нету, не может быть stack trace, и поэтому вот мы по-другому программируем, вот мы там тесты пишем,
все такое, нам не нужны стек трейсы, не нужно их анализировать. Но в какой-то момент, и это уже было на нашей памяти где-то в десятые годы, взяли и реализовали стек трейсы в Haskell. Потому что пришли люди там, кто там это был, не знаю, то ли стандарт чартер, какие-то такие промышленные пользователи Хаскеля сказали, что ну ребята, ну невозможно, ну вот у нас баги, которые мы не можем.
Там найти, пофиксить, если мы стэк трейсы не видим. И неожиданно выяснилось, что можно реализовать в Хаскеле стэк трейсы. Хотя все были уверены, что нельзя и не нужно. Нужно. И можно. И как дебажить все вот эти вот сложные делимитед континуэйшн и все такое. Вот если не реализовать тулинг. для отладки, то будет сложно. То есть это вопрос тулинга и вопрос того, как это все обрабатывается.
Если у нас сложная цепочка исполнений, давайте это хорошо визуализируем. Добавим какие-то информационные точки, которые будут все объяснять. Давайте дополнительную информацию отладочную добавим. И все будет нормально. Да, жизнь сложно устроена. Да, чтобы получать новые возможности, нужно что-то усложнять. возникают проблемы, давайте использовать соответствующий тулинг. Давайте его сначала напишем и потом им воспользуемся. А может быть там...
И не будет никаких проблем со стек трейсами, потому что эти сложные стек трейсы для их анализа AI будет очень хорошо приспособлен и все нам сразу будет выдавать. Нам вообще никакой тулинг кроме AI не понадобится. И это я серьезно говорю, это не то, что я там шучу. То есть вполне возможно, что соответствующие сложно устроенные стэк трейсы... Будут очень легко анализироваться AI. Он будет там баги находить и...
фиксить, что очень важно. У меня есть еще один вопрос. Ты несколько раз говорил о том, что вот, не знаю, система эффектов, это сейчас прям ресерч, ресерч, прям такой самый-самый bleeding edge. А есть ли уже какое-то понимание того, а насколько этот ресерч действительно плодотворный, успешный, насколько все-таки этой концепции нужно?
Проникать в мейнстримовые языки. Или она так и останется в языках, названия которых Катя даже не слышала. Котлин Катя слышала, да, я надеюсь? Да. ну вот смотрите можно без эффектов прекрасно обойтись Но мне кажется, что я постарался в этом выпуске все-таки описать, почему хорошо их иметь. То есть мы чего-то выигрываем. И мне кажется, что... стоит все-таки реализовать и хотя бы изучить это, воспользоваться этим.
Вот книжка, на которую я с самого начала ссылаюсь, про эффекты ориентир программинг, ставит именно такую цель. Она пытается легко объяснить, почему это все выигрышно. Почему работать с dependency injection проще, когда у тебя есть система эффектов? Мы никуда не денемся. В современных фреймворках от dependency injection мы никуда не денемся. Но работать с этим проще.
Поэтому давайте добавим эту фичу в язык. И я уверен, что это будет приходить дальше. Может быть, я ошибаюсь. Но мне кажется всегда, что программисты в этом смысле очень прагматичны. Если что-то облегчает их работу, то они это добавляют в языки. Они пытаются там. И дальше есть кактусы колодца. Окей. А про... Эффект ориентир программинг книгу мы поняли, но что еще почитать тем, кому очень-очень концепция понравилась? Я предлагаю идти просто в соответствующие доки.
библиотек эффектов если вы в TypeScript у вас есть эффект В скале есть масса библиотек с эффектами. Ну вот книжка, упомянутая 500 раз, там она описывает скала ZIO библиотеку, но там есть и еще. То есть вы идете в соответствующие библиотеки, В Акамле, например, прямо в справке по языку есть глава, посвященная этому, там все на хороших примерах описано. Если у вас Kotlin, вы идете в библиотеку Arrow, смотрите, как они там это делают.
Смотрите на их родмапы, как они пытаются что-то еще добавить в области поддержки систем эффектов. Ну где-то нет библиотек, но вот есть доклады, как я для Раста упоминал. Напишите в гугле Effect Systems Library C++ или там Fortran или Cabol или ваш другой любимый язык программирования и с большой вероятностью вы что-то найдете.
Я рекомендую такой эксперимент проделать, чтобы увидеть, что это реально есть. Я бы еще советовал посмотреть на исследовательские языки не в плане того, чтобы их как-то использовать. Они не для этого существуют. В том плане, что если язык создан специально для поддержки и для изучения темы эффектов, то там...
в наиболее сконцентрированном виде и наиболее ясно соответствующие концепции проявляются. То есть, если вы хотите понять, о чем речь, стоит пойти в один из исследовательских языков, созданных специально для этого. и посмотреть, как там это делается, как там в туториалах это описывается, как об этом пишут документации. Это такие языки, как F, эффект, кока.
Вот Юнизон уже упоминавшийся нашим слушателям. Ну вот, может быть, мы приложим к выпуску список, чтобы там со ссылками. Потому что слова F и эффект. Эффект там с буквой K. То есть вообще сложно найти. Посмотрите на исследовательские языки. Они лучше, поскольку они фокусируются на эффектах, в них лучше это описывается, это проще там понять. И я бы рекомендовал это проделать, даже если вы не планируете программировать на коке или на эффекте.
Кок, я имею в виду, там именно язык Кока называется. Не Кок, про который некоторые могли слышать. В каком-то из выпусков подкаста он наверняка был. Это другой язык. Стоит посмотреть. Окей. На этом, я думаю, все. Да нет, я сейчас думаю просто, как суммировать. Мне на самом деле очень понравилась концепция. Во-первых, Виталий, спасибо тебе большое за то, что ты смог очень... классные подробно все описать мне понравилось в целом как мы
До середины выпуска избегали закапывания в определении и скорее просто пытались наоборот от частного перейти к общему. Очень классно, поэтому в голове картинка у меня, я надеюсь, сложилась довольно неплохая. Поэтому спасибо тебе большое, было, короче говоря, суперинтересно. Да, спасибо, спасибо, что позвали, я рад, что мы за один час уложились. Это правда, это правда. И...
Настало время подвести черту выпуску. Сегодня мы, как вы уже догадались к этому моменту, обсуждали эффекты, системы эффектов и эффект-рендед программинг. Мы поговорили про то, что... Что такое эффекты? Что определяет система эффектов? Какие вычислительные процессы бывают? Какое влияние оказывает среда вычислений? Разобрали кучу разных вопросов наших слушателей. Поняли, что...
что мы можем получить от системы эффектов как программисты, и какого счастья эффекты нам приносят. Поговорили про то, что вообще должны уметь языки, чтобы систему эффектов поддерживать, и надо ли на самом деле. деле, чтобы они это умели. И напоследок обсудили, что можно еще почитать и в какие языки посмотреть, чтобы вдохновиться еще сильнее. Егор.
Мне очень нравится всегда смотреть, как основные ответственные ведущие мучаются в конце, пытаясь подводить черту. Это правда. Ты отлично справился. У меня есть еще к тебе такой вопросик, чтобы тебя до конца добить. Что тебе нравится больше, чем когда у тебя возникает желание или, возможно, даже какая-то рабочая необходимость, не знаю, разобраться в какой-то новой, довольно сложной концепции, вместо того, чтобы читать книжки, идти разбираться?
заказывать консультацию приватную вместо этого у Виталия Брагилевского на тему и разбираться в чем-то с помощью Виталия.
Больше этого, дорогие друзья, мне нравится только пользоваться корпоративной подпиской на чат GPT и задавать ему эти вопросы. Но еще большее общение с чат GPT мне нравится, когда вы... уже говорил, подписывайтесь на наш канал в Телеграме и задаете там разные вопросы, потому что вы не представляете, как Виталий радуется, когда мы на выпусках с ним не только свои вопросы, а ваши задаем. Вот, что нам еще нравится, когда вы не слушаете нас, а смотрите на ютубе, потому что мы...
Стараемся снимать себя на камеры и это монтировать. Вы там ставите лайки, пишите комментарии. Комментарии, на самом деле, лучше не писать, потому что они очень часто бывают странные. Вот молча нас вы смотрите. Вот это лучше всего, короче. Что еще нам нравится? Когда вы ставите лайки, рассказываете о нас, своим друзьям, коллегам, темледам и всем остальным. А самое главное, что мне нравится, это когда вы просто слушаете наш подкаст и радуетесь ему. На этом, наверное, все.
Всем спасибо и всем пока. Пока-пока. Пока.