Пишем код, за который не стыдно. Разбираем базу, даем рекомендации и встречаемся с умными людьми
Друзья, привет. Это подкаст Организованное программирование. Я его ведущий Кирилл Макевнин. У меня в гостях Александр Вершилов. Саш, привет. Привет. Уже, если я не ошибаюсь, третий раз или даже третий, по-моему, да, раз, Саша. А, третий. Мы сегодня продолжаем тему, которую начали буквально недавно. Это Хаскель. То есть, э, в том видео, если кто увидел, если кто не помнит, я напомню, а мы разбирали основы Хаски какие-то базовые вещи и в конечном итоге прямо в середине стрима поняли, что лучше это показывать на примерах, и, в общем-то, сделали лайфкодинг, честно говоря, не очень запланированный, но мы остановились на чистом хаскеле, то есть мы не перешли эту границу, которую на самом деле те люди, которые с этим знакомы ждали, это то, что связано с монадами, побочными эффектами, вызовами какими-то внешними и всякими другими страшными словами, вещами, которые происхо ходит. Соответственно, мы говорили, что обязательно запишем вторую часть. А сегодня мы готовились уже сразу к тому, чтобы вам показывать всё, поэтому перешли с какого-то редактора на VS-код. Я не знаю, в каком редакторе ты это раньше показывал, да? Ну и в общем, да, если короче редактор, но чуть-чуть под более настроенный. Понятно. Короче, мы сегодня чуть подготовились в этом плане и будем сегодня вам показывать, начиная с того, начиная, наверное, с проблематики Ао и постепенно погружаясь в саму концепцию монат. Ну и в конечном итоге от Аё, наверное, отвязываясь и в целом рассказываю про эту историю.
Постараемся сделать так, чтобы вы не просто такие: "О, как Хаскель классно, сложно устроен", узнал что-то новое, но в целом пошёл дальше. Мы хотим, чтобы в конце этого видео вы концептуально осознали, что это такое, увидели у себя это в коде, поняли, что это не какая-то хаскелевская штука, которая присуща только ему. И, возможно, это как-то повлияло на то, как вы думаете о программировании, как вы видите свой код и как вообще можно с ним более эффективно работать. Привет, ерс в GO. Всегда люблю, так сказать, классные примеры приводить. Ну и я бы хотел начать сегодняшний выпуск с того, что монада - это моноид в категории дофункторов. Всё правильно, я прошёл собес.
Саш, значит, вопрос к тебе такой. Давай мы сейчас будем переключаться на другой режим, где у нас показывается экран. Значит, давай начнём с проблематики. То есть вот когда мы работали в Хаскиле в обычном режиме, как в любых других языках, именно в чистом коде у тебя какие-то вычисления, вызовы функций, в целом концептуального отличия нет, кроме каких-то нюансиков, связанных с ленивостью, с корированием, может быть, да, ещё какие-то вещи, но в целом похожий код, похожие концепции, похожие идеи, и никаких особых знаний тут не нужно, чтобы начать что-то делать. Но вот есть некая граница, после которой вот эта сложность, она вот так вот вверх растёт. И если ты тут что-то заранее не почитал, не подготовился, шансов вообще никаких понять, что это за конструкция и почему именно так надо делать. И вот этот барьер мы сейчас попытаемся убрать. Соответственно, м проблематика. Вот вдруг нам понадобилось сначала прочитать файлик, что-то сделать, а потом его, допустим, записать. И мы поняли, что теперь мы не можем работать, как в обычном языке. Так, поехали тут. Давай переключимся сюда. А на самом деле моя задача здесь будет рассказать, что, в общем-то, никакой границы на самом деле нету. Она только кажется, и мы её себе создаём. Посмотрим, насколько у меня это получится, насколько не получится. Естественно, у нас, э, возникает проблема, а что у нас чистый язык и нам нужно придумать какое-то решение, как это сделать. Ну, тут, э, то есть у нас появляются побочные эффекты. Во-первых, давайте поговорим о том, что такое побочные эффект, что это за зверь, почему важный, почему с ним надо работать. И чуть-чуть введём определение. Грубо говоря, у нас есть какая-нибудь функция, пусть будет какая-нибудь функция. Что такое побочный эффект? У нас у функции есть некоторый тип, те аргументы, которые она принимает, то, что она возвращает. Всё, что не входит в это описание - это побочный эффект. Грубо говоря, если происходит в программе, что или в окружении программы, что угодно, что не описывается здесь, а, например, меняется состояние, меняется значение какой-то переменной, которая здесь не описана, меняется что-то в файле, то это побочный эффект. А почему это важно? В чистых языках есть такая штука, которая называется ссылочная прозрачность. А, referential transparency. Это такое свойство, что мы можем в любом месте её, а, функцию заменить. вызов функции, заменить на её результат. А это, естественно, перестаёт работать в аэ местах с побочным эффектом функциях, потому что мы не можем заменить чтение из файла на результат этого чтения из файла. Потому что, если мы его читаем сейчас и потом через 10 секунд, совершенно вот не факт, что мы получим ровно тот же результат. Это нужно уметь как-то описывать. И халь, он достаточно интересный язык. И он говорит, что вы такое никогда не получите. И вот нельзя так писать. Вы можете только описывать вычисление. Соответственно, а исторически здесь был очень простой подход. Мы берём и делаем нашу программу. Что наша программа напишу здесь не совсем честно будет. Она просто берёт на вход строку и возвращает строку. Соответственно, вполне себе решение. Тогда вокруг программы у нас есть некоторый интерпретатор, который может подавать значение, возвращать, и мы не делаем никаких побочных эффектов. Вот это работает. Естественно, это всё прекрасно до тех пор, пока нам не нужно хранить какое-то состояние, жить внутри. Э, и, соответственно, для этого мы должны, э, ну, можем пойти тем же путём и иметь какой-то интерпретатор. Нам нужно вернуть что-то, что описывает о том, что вот у нас есть вычисление, и здесь есть два подхода. Это описать это типом. То есть у нас есть какая-нибудь функция, и мы говорим:
haveect,
то есть такая как некоторая структура данных, которая описывает, что у нас здесь есть эффект. И дальше мы её можем интерпретировать. Таким образом, мы разделим часть, которая описывает, и часть, которая работает. Второй способ - это использовать так называемые теги. То есть, например, в каких-нибудь более популярных языках программирования мы можем видеть такую историю, как эффект. Эфкт функция и там неё какой-нибудь тип и так далее. Соответственно, это два подхода и они достаточно интересные. А в Хаскеле выбран первый из-за ленивости, но в целом тут есть о чём поговорить. А вот и более популярная история может приводить к некоторым сложностям. Вот, Кирилл, ты ведь сталкивался со всякими такими асинхронными функциями ещё там в Пайтоне, тейпскрипте и прочих языках. А насколько удобно? Хорошо. Вообще Осинка - это крутая, конечно, штука. А так сходу это у нас что? Python, JS, C#P и котlн. Это вот прямо совсем сходу, наверное, где-то ещё. Да, всё прекрасно, кроме одной вот вещи, которая связана с нашей текущей историей. Вообще мы, кстати, про это в начале не совсем сказали о проблематике. Вообще почему об этом надо думать, типа почему вдруг, э, это появилась проблема? Наверное, потому что Хаскель, в отличие от других языков мейнстримовых, где она это просто, ну, эффект, эффект, это твоя проблема, а он пытается всё-таки гарантии какие-то давать, да, больше, чем даёт другие языки. Из-за этого, собственно, проблема-то из-за этого появилась, потому что в том же Джесе, если я, например, забываю написать Aй, то у меня просто функция выполнится где-то там. Я ничего об этом не узнаю. И, в общем-то, в рантайме только узнаю о том, что у меня ой, а я забыл поставить авей. И, кстати, это очень часто встречается. Да, согласен. И как раз вот вот это вот этот вот подход, он это решает. Как он это решает? Если мы сделаю какое-нибудь более короткое описание. Если у нас есть какая-то структура данных, ну, например, в том же Хаске M, то есть некоторая обёртка, то а мы просто так с ней сделать ничего не можем. Мы должны её каким-то образом интерпретировать. То есть у нас должна быть какая-нибудь функция, которая с ней что-то делает. Прости, а что ты имеешь в виду, когда ты говоришь: "Мы ничего с ней не можем сделать?" А что мы хотим с ней сделать? Это структура данных, и внутри неё есть некоторое значение. Мы не можем получить значение из этого контекста, не интерпретировав его каким-нибудь образом. А, например, то есть у нас должна быть какая-нибудь функция, если она возможна, например, из
делаем так, run из ma в а в том случае, если если эта функция, ну, если это возможно, а, например, для каких-нибудь А если возьмём какие-нибудь существующие типы данных, например, у нас есть тип данных Maybe, который, как подсказывает Иишка, выглядит вот так. И совершенно прав. То есть у нас есть два варианта, и нам нужно каким-то образом это интерпретировать. Это то, что называется, называют деконструктор. То есть мы можем написать какую-нибудь функцию, которая работает либо с этим значением и вызначает значение результата. Плохая идея, мне это не нравится. Не так делаем, плохая ишка. Вот у нас есть два варианта. А так лучше, а либо у нас есть значение, тогда мы можем применить какую-нибудь функцию, получить результат, либо, если у нас ничего нету, мы можем просто вернуть. Таким образом мы можем это интерпретировать. И таким образом мы можем заглянуть вовнутрь этого контекста. И других способов у нас нету. Соответственно, если в каким-то в каком-то месте мы использовали контекст, то нам нужно либо вынести его наверх, либо его явно интерпретировать таким, если мы можем сделать это. А таким образом это позволяет нам следить за эффектами, за тем, что происходит. Единственный вопрос у нас: как объединять, как работать с этой структурой, если мы не хотим её каждый раз интерпретировать? А можно пару вопросиков, да? Потому что я буду вот играть роль э наблюдателя, которого возникают вопросы по пути, потому что понятно, ты как специалист, там у тебя какие-то моменты в голове проскакивают, да, и ты такой дальше идёшь. Смотри, тут, наверное, первое. Я правильно понимаю, когда ты вот эту концепцию, что у тебя есть структура и ты не можешь напрямую, то мы, наверное, можем по-простому как сказать, что у тебя по сути нету литерального синтаксиса там извлечения элементов, у тебя всё упрощённо является приватным. Единственный способ извлекать данные - это писать специализированные аксессоры, можно так я это назову, да, которые через типы пролазят внутрь и извлекают оттуда данные. Да, ес при этом, если говорить в терминах паттернов или чего-то такого, то можно посмотреть на паттернпретатор. И это вот будет как раз ровно то, о чём мы говорим. То есть у нас есть некоторая, э, некоторый объект, ну, в данном случае структура данных, который содержит информацию о том, что происходит внут и описывает контекст вот этого вычисления, которое есть. И у нас отдельно может быть один или несколько интерпретаторов а этого значения. В данном случае мы можем говорить как в терминах чистого кода, так в терминах эффектов, чтения файлов и так далее. То есть, например, если мы хотим описать вот эту вот ту проблему, с которой мы начали, то мы можем сделать что-нибудь, некоторый контекст, который у нас есть. И у нас есть эффекты readтфайл, который будет принимать файл пас ишка забегает вперёд. Поэтому пока с простого начнём. И там в write файл pass, который получает файл и пишет строку. Соответственно, имея такую историю, мы можем описать наш эффект. Естественно, здесь пока проблема, мы не можем описать программу. То есть, на самом деле, нам это нужно соединять. То есть, грубо говоря, что мы говорим, что мы читаем файл и возвращаем какой-то следующий, то есть и имеем продолжение. То есть мы прочитали файл, у нас на руках есть некоторое значение, мы его передаём в следующую функцию, которая возвращает нам этот контекст. А у этого паттерна в функциональных языках есть а имя. Э, это если его чуть обобщить, но в целом это, то есть стандартная история, да, здесь тоже нам нужно продолжение, мы ничего не возвращаем. Вот такая штука, наверное, сейчас получилось сложно, непонятно, и мне нужно проговорить будет подробнее, да, вот эта часть в отсутствии большой практики я сам теряюсь. Давай я тебя сейчас попробую спросить. То есть мы определяем структуру данных через Это же не вычисление прямое, да, что мы сейчас прочитаем файл, запишем и так далее, да? Это вот в данном случае это просто информация о том, что здесь должен произойти эффект чтения файла. И всё. Наша программа, которая будет нам нужен интерпретатор для того, чтобы это выполнить. То есть когда оно есть, мы просто говорим, что у нас есть вычисление, которое прочитает файл. Что именно значит прочитать файл? Определяет интерпретатор. Это может быть чтение файла. Это может быть штука, которая Курл позовёт и сходит в интернет на какой-нибудь сайт. Я понял. Ты на ты начал скорее сразу с такого с обобщения этой истории. А мне кажется, будет, знаешь, проще и понятнее, если мы начнём сначала всё-таки с кейса. Мы просто вообще это сделаем и начнём разбирать уже кейс. То есть обобщать что-то приземлённое, потому что оно получается сложновато для понимания, да? Сейчас прямо просто прочитаем файл, не знаю, там какой-нибудь простой и выведем его на экран, допустим, да? Ну, то есть мы здесь, э, то есть мы можем написать интерпретатор этой истории. Я здесь использую страшную штуку, как i. Я пробовал, когда готовился к этой встрече, показать, как это можно не вводя её делать, но это будет слишком сложно, если честно. Вот Ишка всё за нас записала, можно посмотреть сейчас проверить, правильно ли оно всё сделало. Так. Вот, соответственно, пока что делает здесь мы сделаем немножечко не так. Мы сделаем немножечко в другом синтаксисе мы про него поговорим. Тактактак тактак. Здесь всё правильно. Здесь тоже сделаем вот так.
Так. Так. Сейчас здесь я тоже сразу перезапущу для того, чтобы мы могли смотреть. Отлично. То есть мы сейчас можем поделать некоторые примеры, зайти в JCI.
А
вот надо прямо не туда,
да? Ё-моё. Так, надо что позакрывать. Там у тебя ещё функцию брал. Да, я сейчас всё всё уберу и закомментирую.
Так, отлично.
X Вот, собственно, что здесь происходит. Грубо говоря, мы написали интерпретатор, который может сходить в прочитать файл. То есть, что он делае что он делает? А вот прямо рассмотрим пример. Вот у нас есть наш контекст. Мы говорим, что мы читаем файл. И вот у нас некоторое продолжение, другая функция, которая Так. А я думаю, нам, наверное, нужно программу в этих терминах написать. Example ноль.
Вот сейчас мы напишем прямо программу. То есть мы пишем наш контекст, что мы читаем файл input текст. У нас мы получаем значение его. Пусть мы напишем нужка хорошо лучше так. read contents int, то есть мы посчитаем какой-то integer, потом мы прочитаем второй файл
2, прочитаем его. Вот. И вернём значение норм программы. Сейчас нам нужно сделать
input. TP
2 вот и попробовать в её запустить. Так, понятно ли, что до этого произошло или лучше проговорить сейчас сразу? А ты запустил? Да вот, ну сейчас можно будет запустить вот здесь. Давай
X. Так, я что не то запустил? Damage example 0.
Так, сохранить файл тоже надо. Мне казалось, скод автоматом сохраняет. Нет. М, идея точно сохраняет. Вот отлично. Он прочитал два файлика, вывел три. Вот если не копать даже в подробности, когда ты говоришь: "Да, это то же самое". Нет ощущения, что это то же самое, да? То есть у тебя чтение файла превратилось просто в целую историю, а у тебя нету такого, что ты просто давай просто вот по чуть-чуть идти. У тебя нету такого, что ты просто вызываешь функцию read и, соответственно, что-то получаешь. У меня нету, да. Ну вот здесь, если честно, есть, но мы это обойдём и объясним, что это такое. На самом деле это то же самое, что мы написали. Вот. Да, мы не вызываем ни в каком моменте языке мы не выполняем эффект и не делаем ничего с внешним миром. Мы говорим, что мы сделаем вычисление, которое говорит: "В этом месте надо прочитать файл". Вот смотри, давай начнём прямо с базы, которую нужно понять. Угу. Вот я даже с ходу не очень понимаю, что такое example ноль. Это функция это что? Example 0 - это функция. Это функция, у которой тип вот наш вычислительный контекст от интеджера. То есть, грубо говоря, это штука, которая описывает граф вычислений, который, будучи интерпретированным, вернёт что-то связанное с интом. Вот. А так мы определили контекст, да. Вот я пытаюсь из этого понять. Вот реально для того, чтобы написать файл, нам вот вот это минимально всё нужно. То есть мы не можем написать это. То есть какая микро самая микроскопическая конструкция, если мне просто нужно прочитать файл и получить переменную, грубо говоря, с данными этого файла, если потому что будет проще от этого начинать отталкиваться, если оно есть. Если мы воспользуемся тем, что готово и всё есть, то тогда у нас есть функция а readфайлile,
которая читае, которая типа файл iOS string, она просто вернёт, она просто прочитает файл. Но вот здесь вот нужно разобраться, что такое i это работает. А если ну давай сначала пример посмотрим, да. прочитаем файл, вот который у нас был, и прямо распечатаем на экран значение. А, ну, распечатаем на экран, что там было, да? Если вот, собственно, если мы посмотрим, если я вызову read file файл inputt, оно вернёт значение. А как это будет в файле, если мы в коде это напишем? Если мы пишем в коде, то а вот здесь как раз вот это вот обращаясь сюда, грубо говоря, у нас уже есть готовый контекст. А это никакая не магическая штука, не встроенная в язык. Это просто такая же обычная структура данных, которую мы написали здесь, а которая позволяет вычислять это. Соответственно, мы можем написать вот здесь read file input. А, ну вот сейчас мы к этому придём, да, просто надо вот начать с конца в данном случае получается, да? Вот, соответственно, если мы это скомпилируем, а демо
не скомпилируем. Нам надо это ещё распечатать. Придётся мне сейчас написать страшные символы, которые я не объяснял. А что это такое? Нормально. Нормально. При этом, кстати, а это нормально, да, что он не ругается у тебя? Он, если не скомпилировал, он же, по идее, мог поругаться или не настроено? Не, не, вот здесь он наругался, не скомпилировал, а здесь я имею в виду подчёркивание подчёркивание. Ну ты заставляешь меня настраивать мой редактор больше, чем я хочу. М, интересно, мне казалось, что Ну ладно, да, да. У меня не получается нормально настраивать language сервер, поэтому я уже привык просто включить у себя интерпретатор, который мне показывает ошибки всё сразу. А это всегда работает, не жрёт оперативку, работает абсолютно в любом окружении, работает, если ты поуключаешься куда-нибудь. Поэтому я, к сожалению, так привык. Но всем советую ставить langage сервера. Они уже более-менее работают. Да, мне казалось, что просто вскоде обычно, знаешь, когда, ну, новый язык открываешь и у тебя там, в принципе, ничего нету, кроме файлов, да, то он обычно подхватывает нормально. Просто интересно, почему он не подхватил. Ну, он такой говорит, типа installш начинает работать. Так, ну, о'кей. В итоге получается вот мы прочитали файл и дальше хитрым образом, хитрым символом перенаправили его в функцию команду. Да, тут тут это на самом деле не перенаправление, не что-нибудь. Ну вот сейчас, наверное, мы если вот здесь вот порисуем, поработаем с вот этой штукой, мы до этого дойдём, до этой проблематики и до того, что мы тут написали. А вот Но в целом ты прав. То есть, грубо говоря, а как это работает? У нас уже написан за нас определённый контекст, вот мне нечто похожее на такое, которое позволяет нам использовать различные функции с эффектом, но у нас на руках нету этого интерпретатора. То есть у нас нету какой-то команды, а, которая рано или придётся тебя остановить. Мне тебя придётся остановить, потому что я как раз хотел ещё позадавать вопросов для того, чтобы перейти. Ты, да, ты для меня вот прямо переход происходит. А я ещё, кстати, объясню такую историю, то, что вот лично для меня, когда я изучал Хаскель, как раз, то есть с монадами в том или ином смысле я встречался, конечно, и в других языках. Хаскиль, там Maybe, I и другие какие-то штуки, да, которые мы там пытались имплементировать и использовать, но именно изучение Хаскеля с этой точки зрения для меня остановилось вот примерно на этой точке, потому что как раз вот здесь вот я этот барьер не преодолел. У меня не хватило желания, ресурсов и сил, чтобы сесть такой: "Так, всё, я должен через эти пробраться, через это айё". И вот сейчас я хочу как бы это победить с твоей помощью. Давай. А, смотри, вот вот вот тут понятно, да? Вот readфайл мы её там, ну, выглядит как перенаправили, скажем так, да, но вот по её всё-таки хочется именно на не копая внутрь, а всё-таки на более чуть-чуть высоком уровне с этим разобраться. Смотри, давай попробую говорить, а ты скажешь, насколько я прав или не прав. Угу. Всё-таки, когда мы описываем тип функции, у тебя там значение, стрелка, значение, стрелка, значение и последняя стрелка - это возврат. Угу. Вот отсюда не очевидно, что это такое, потому что у тебя у мей нету входа. И получается, что если ты описываешь одно значение, то это как будто получается возврат. Правильно или нет? Или это вообще ровно так? Ровно так и происходит. Всё правильно говоришь. То есть, грубо говоря, то есть это описание возврата. Мы возвращаем её, да? Это описание возврата. функция main возвращает вот этот вот контекст вычисления её от юнита. А как мы понимаем, что это контекст, а не конкретный тип? Потому что оно с большой буквы. То есть как это определяется? Контекст, наверное, больше для понимания и ещё, потому что контекст и тип в это в данном случае э нету разницы. То есть контекст - это, грубо говоря, смысл этого, а тип - это, ну, структура, это реализация. То есть, ну, по большому счёту, это вот как раз то, о чём ты говоришь, да, что разницы нет. Мы просто возвращаем специализированный тип, а который, да, я бы даже не сказал специализированный, это, в общем-то, обычный тип. Он он ничем не отлича то есть интерес - это основной смысл вот этого в Хаскеля тем, что это не является чем-то специальным. То есть любой человек, в принципе, может написать ровно такую же историю, как, которая будет обладать теми же свойствами в используя обычную библиотеку, просто не экспортируя приватный метод запуска этого запуска как бы интерпретации этого, я хочу сказать, контекста, этой структуры. Вот если говорить совершенно честно. Угу. О. Во. Вот теперь начинает проясняться. Теперь правильно я понимаю, что слово контекст - это такое слово, как бы существующее не в Хаскиле, на техническом уровне, а мы просто это так называем для того, чтобы как раз оперировать тем, что там происходит интерпретация в дальнейшем, да? То есть сказать, что тут сейчас я попробую описать это. Я не факт, что у меня получится это хорошо выразить, но за но если получится, оно будет хорошо связываться с тем, о чём мы будем говорить потом. То есть, грубо говоря, если мы посмотрим на, наверное, даже вот я какое-нибудь вычисление, у нас, не знаю, есть in, а in, и оно вернёт там. То есть мы, например, у нас есть нечто, что прочитает число, что вернёт число, нечто, что вернёт число, и мы это запишем файл функция. Угу. В конце запишем файл. Это просто, потому что вот возврата нет. Ага. Да. То есть мы, например, напишем в right. Так, мы пока не умеем дунотацию, которая, возможно, упростит, но содержит себе магию. То есть мы написали вот этот файл, мы, то есть мы вызвали первое действие, вернули результат, вызвали второе действие, вернули результат и потом записали. Ну, не файлы здесь, просто на экран вывели. Будем верить иишки. А вот, соответственно, и здесь происходит, как будто мы оперируем с обычными чиселками. Вот эта вот штука у нас, она у нас типа int. И вот эта вот штука, она у нас тоже типа. И как будто бы мы оперируем с обычными чистыми интовыми значениями внутри контекста. То есть слово контекст, оно возникает примерно отсюда. То есть, что у нас есть обычные чистые, приятные вычисления, которые мы поднимаем в некоторый, а, контекст, описывающий побочный эффект. Угу. Тогда вопрос. Тогда вопрос. Вот для того, чтобы теперь вот, мне кажется, мы уже сейчас прямо ближе стали, окончательно понять, грубо говоря, чем это отличается. У меня уже есть представление, но всё-таки чем это отличается, если бы мы просто читали из файла, давай как вот в обычном языке и фактически просто сразу типизировали значение, говорили, что вот это n int равно readфайл такой-то. Почему бы не так было сделать? В чём отличие? В чём проблема? Ну так, если сделать сейчас, если мы пишем, ну как бы, ну представь, да, вот просто я пишу вот что-то такое readфайл. Угу. Так. И тогда какого оно у меня типа? Не, мы вот прямо принудительно, допустим, они бы вместо всего этого сделали прямо принудительный синтаксис. То есть это мы гипотетический вариант рассматриваем, что мы же там тоже принудительно говорим, что там инт читаем, и там точно так же может оказаться не инт и точно так же грохнется с ошибкой. Да. А вот вот ну не грохнется, может быть, да. Ну, во-первых, у нас, то есть, в чём у нас особенность i, ну, и вот вот этой вот истории в том, что помимо, а, того, что оно может грохнуться, у него есть ещё такая важная штука, как порядок вычислений. Соответственно, нам нужно это помнить. А, и если мы, не знаю, пишем какой-нибудь, а, writeфайл, writeфайл, потом readфайл, а,
readфа, то нам здесь очень важен порядок вычисления, потому что если, так как у нас язык ленивый, вот это во-первых, а для того, если у нас важен порядок вычислений, то нам нужно хранить об этом информацию. То есть мы не можем просто так взять и куда-нибудь в программе переставить. Если у нас это обычное чистое чистое значение, если мы могли написать L n равнофайл, это значит, что компилятор волен переставить запомнить это n, подставить туда вызов файл или подставить результат дфайла. И это может распространяться по программе как угодно. То есть, например, е, да, ленивый, пока ты не начнёшь использовать, хрен его знает, когда, в что, какой момент произойдёт. Вот. И если мы, например, напишем вот такую вот штуку LED N = и M = readфайл, а-э, то тогда, в принципе, ээ если я напишу readфайл штрих, Слушай, я правильно понимаю, что даже технически сработает вот эта штука? Да, на самом деле, я когда готовился, я подготавливал пример, где, э, ну, то есть без ввода i АО, как это могло бы быть работать просто с вызовом фонфорлов. И там как раз вот был вот этот пример. И, грубо говоря, что вот здесь произойдёт? Поскольку у нас, если бы эта штука была чистой, просто возвращают что-то типа int, а то у нас компилятор имеет право вынести это в топ-леevel, имеет право вынести это в топ-леevel. Понять, что это одна штука, и здесь произойдёт, ну, асинг говорить, конечно, неверно, но здесь вполне компилятор вполне мог сделать вот такую штуку. Ну, мимоизацию, если упрощать, наверное, можно так сказать, да? Мимоизация, да, будет правильно. Да. Да. Ну, в принципе, это можно и А если сделать, не знаю, там, ну, более сложный пример, придётся много нового выводить. Ну, грубо говоря, если бы мы записывали в переменную в байту в байтстроку результат, то он мог переиспользовать одну байтстроку под всё. Кстати, под это был баг очень хороший. Теперь есть классная функция в одной библиотеке unsafe accursed performo, которая как раз вот произошла вот в это вот и в память о том, что как писать не надо. А в общем, могут происходить страшные вещи. Поэтому нам важно помнить о том, что, во-первых, это штука, которую с которую мы не можем просто взять и вытащить в другое место. Соответственно, поэтому у нас есть некоторый контекст в данном случае а, который, а, протаскивает у себя некоторое состояние, что позволяет следить за тем, какой порядок и не позволяет сделать какие-нибудь странные операции, типа мимоизировать, вынести наверх и делать прочие преобразования над нашим графом вычислений, которые, в общем-то, запрещены. И самое главное, мы нигде не можем это потерять. То есть мы не можем написать AC и забыть написать. А, да. То есть получается, если у нас где-то внутри, то из-за того, что это не просто чтение, которое там потерялась, ты просто обязан теперь эту цепочку соблюдать наверху до самого верха. И таким образом происходит, собственно, контроль всего этого добра. Да, причём нужно понимать, с теми же самыми Mayби, айзерами и прочим происходит более-менее то же самое. Единственное отличие, что у нас есть интерпретаторы, и мы можем, в общем-то, в любой момент посмотреть, а что там внутри сейчас, потому что это безопасно, что неправда в случае Ао мы там так делать не можем. А так всё, кстати, это интересно. Я просто такой думаю, вот если упростить и попытаться наложить это на обычный язык, допустим, JavaScript, да? То есть, предположим, была бы такая штука: как только ты внутри своей функции вызываешь любую асинхронную функцию, он заставляет тебя писать a, да? Мы могли бы сказать, что это похожая история. Да, это это Ну а заставляет писать. Ан заставляет это была бы похожая история. То есть, если, э, ну, например, в условном тайпскрипте мы бы могли вынести это на уровень выше. Аэ, наша, ну, в данном случае тапчекер там работает не так, но это бы позволило и, грубо говоря, возврат нашей функции бы был бы я всегда обёрну, да, в промис. Был бы какой-нибудь, да, был бы какой-нибудь промис или или acing, а это в C#P, если я не ошибаюсь, именно так что-то пишется. Но действительно, это был бы промис и соответственно за значением. Это именно так и происходит, да. Вот, кстати, вот это уже классно, потому что тут уже видна вот эта связь и понимание, что у тебя действительно происходит этот а контейнер, из которого ты достаёшь авейтом. Авейт не делаешь, ты получаешь этот контейнер. Ну, и дальше у тебя вот эта цепочка, она раскручивается. И на, ну, здесь нужно подготовиться было, но на самом деле здесь бы вполне можно было вот аналог вот какой-то такой истории написать. А вот вот вот то, что мы написали здесь. а, переводят то, что написано здесь на уровень выше. То есть мы, поскольку мы, а, у нас есть вот функция readфайлile, которая у нас просто есть в библиотеке, которая является высокоуровневой отбёрткой над функцией, над сишными функциями и обычными вызовами чтения файл из, э, lipc. А вот, но, грубо говоря, вот это мы подняли на уровень выше. То есть мы абстрагировались, мы сказали, что у нас есть любой дфайл. И вот такую историю, на самом деле уже легко можно переносить не только в хаске, а в любой другой язык и использовать там. Теперь следующий вопрос. Давай. Вот вот этот переход, мне кажется, сложный и хочется его сделать. Да, мы сейчас с тобой теперь вот теперь, когда концептуально поняли, почему это нужно, это управление порядком вычислений, при этом контролем до самого верха, чтобы это не потерялось, сравнили это с джавоскриптом и поняли какую-то аналогию, которую 100%, если посмотрят специжт: "Ребята, вы вообще не правы". не выполняются какие-нибудь законы, которые должны выполняться. Но мне кажется, допустимая аналогия, учитывая, что это позволяет хотя бы примерно понять вот эту вот контейнерную сущность. Как ты считаешь? На самом деле, если мы аккуратно проговорим, то все законы выполняются. Ну, то есть, если мы хотим это повторить, то нам нужно сделать структуру объект в джаваскрипте, соответственно, записать пару правил, пару функций и всё буде. И, ну, вплоть до того, что он, конечно, не проверит, если сайд-эффекты, оно будет выполняться. Вот если мы считаем, что мы пишем, если мы пишем у атрибут и он чистый и компилятор этому верит, то мы можем так делать в джесе, всях и так далее. Ну мы, правда, сейчас тоже надо, наверное, сказать, что ограничили типа промезами, потому что понятно, что жизнь-то сложнее. То есть здесь идёт речь вообще про любые побочные эффекты в целом. А у нас, например, допустим, мы, о'кей, мы использовали asinwayate, ну, а если мы внутри функции просто readфайл сделаем синхронный, ну, тут уже тебе ничего JavaScript не поможет. Поэтому это получается либо у тебя получится такая частичное решение проблемы, которое в итоге суммарно ничего не решает, либо тебе придётся делать общую концепцию над чтением, чтобы readфайл ни в коем случае не строчку тебе возвращал в дже, да, а возвращал что-то такое, с чем приходится вот так по цепочке работать. А вот здесь пришлось бы, короче, переделать всё. Да, да, ну не факт. Но надо смотреть. Наверняка уже существует 10 реализаций фанклибы, в которой всё это есть уже реализовано. и посмотреть. А, да, ну, я скорее про стандартную либу, то есть если ты стандартную Либу не переписываешь, у тебя всегда есть возможность сделать всё, что угодно. Этим, кстати, вот просто такая ремарка маленькая отличается, когда сравнивают асинхронное программирование на Джесси и на других языках. Я, например, лично предпочитаю, вот если чистый язык брать, мне гораздо больше нравится это на Джесе по одной простой причине. У тебя, ну, нету библиотек синхронных. У тебя все библиотеки асинхроны. У тебя, в принципе, ты работаешь в ивентлупе. Если ты работаешь в Питоне, Джаве, котле, где угодно, просто в любом месте херанул синхронный вызов и всем привет, как бы поплыли. При этом каждый раз, когда ты ищешь библиотеку, ты должен понимать, а эта библиотека асинхронная или асинхронная. И получается, что у тебя в каждой экосистеме существует два параллельных как бы мира, да. Одна типа это синхронные библиотеки, вторая - это асинхронные библиотеки. Мы вот как-то разговаривали с разработчиком Асинко в У меня вот был в гостях разработчик Асинко в Пайthне, и он говорил, что как раз эту проблему, его будущее восприятие, что типа Python, например, станет весь абсолютно асинхронным, а синхронные библиотечки останутся просто как, знаешь, такой типа как LEAC скорее. Вот. И тогда, да, тогда мир поменяется. Ну ладно, было небольшое отступление. Короче, теперь, когда вот это более-менее становится понятно, а мы пытаемся понять, а что, собственно, А такое внутри. Хорошее отступление, кстати, потому что вот как раз вот вот вот эта вот штука и контекста - это как раз решение, которое позволяет вот разделить миры. Если мы хотим показать, что вот вот вот это вот что-то особенное, то оно позволяет его явно промаркировать и поднять с самого и и заставить поднять с самого нижнего уровня на самый верхний, тем самым разделив вот эти вот контексты, миры и всё остальное. Вопрос. Проверяем исключения. имеют связь с тем, что мы сейчас обсуждаем, или нет? А попытка эксепшены, а описывать в Джаве, чтобы если ты их описал, соответственно, выше, выше, выше, выше, он тебя заставляет по цепочке это делать.
Ну, понятно, что это сильно по-другому реализовано, но похожая история здесь есть. Похо есть похожая история, да, мы можем говорить, мы говорим, а, ну, это не прямо то же самое, но связь есть. А, идейна, наверное, да. Вот так идейная, да. Вот. Угу. О'кей. Теперь, если мы берём всё-таки это ай и пытаемся понять, что внутри, потому что вот честно, когда ты написал эту структуру, мне стыдно признаться, я с трудом понимаю, что она из себя представляет, потому что вот давай наверх перемотаем. То есть вот давай я тебе дам выбор дорог. У нас есть дорога понять, что внутри Ао и понять, что вот это. Давай определимся с чем-то одним и пойдём туда, потому что они чуть-чуть разные. Это как мы пошли, это обобщение. Правильно я понимаю, что вот это контекст A - это скорее как бы аё могло быть реализовано внутри, да? Ты это имеешь в виду? Или это более конкретная версия? Да, давай сразу просто чувствую себя дебилом немножко. Не, не, не нормально. Просто с монадами всегда есть очень неудобная штука, что здесь смешивается много вещей. Здесь смешивается сама концепция структуры данных и как мы с ней работаем. А, то есть тип интерфейс, который позволяет нам получить кучу абстракций. Собственно, хаскилисты говорят о монадах ровно поэтому благодаря тому, что у нас есть очень простая абстракция, которая может позволить написать кучу библиотек, и мы начинаем использовать для одного, другого, третьего типа, говорить вот эти вот страшные слова. А и всё. Дальше у нас есть а, собственно, которое мы используем для того, чтобы в Хаскеле писать программы, которые что-то делают. И у нас есть вот этот вот синтаксис, как мы пишем. И эти три истории, они вечно перемешиваются. Обычно, когда разговариваем, всегда байс в какую-то одну из сторон. Поэтому здесь всегда нужно быть осторожным, что мы проговариваем, что мы хотим добиться. Так, сейчас сделаем. Ну да, ну то, что с Mayби таких проблем нет. Вот если ты просто рассуждаешь о нём, пишешь, используя Mayби, как-то у тебя вот оно просто заходит. А Сайо что-то вот для меня всегда было. Да не знаю. А да, ребят, напишите, это я один такой или нет? Мне кажется, это сложновато. Так вот, грубо говоря, Аё внутри - это такая штука, которая просто начиная со сказок, как что нам сделать для того, чтобы описать эффект того, что может произойти что угодно. Потому что понятно, а - это никакого контроля и это взаимодействие с внешним миром. Вот. То есть в ха, ну, сетевой запрос, да, у нас там что файлов, у нас может быть что угодно. Оно у нас может меняться в любой момент. У нас один вызов одного и того же.
Пожалуйста, вот и этот. Так вот, собственно, и что он говорит? Предположим, что у нас есть тип, который может содержать в себе и описывать всё, что есть в реальном мире. Вот. Вот такой вот, как я здесь нарисовал. Соответственно, что такое i? Аё - это функция, которая принимает наше текущее состояние реального мира и возвращает некоторое новое состояние реального мира вместе со значением, которое мы хотим. То есть, например, если мы читаем файл, оно возвращает реальный мир новый и содержимое файла. Если мы записываем файл, оно возвращает реальный мир с записанным файлом и ничего. Ну такая нормакция достаточно простая. Вопросики есть. Синтаксис. Вот. А, а, её, господи, как это символ называется? Это не апостроф, а это апостроф, наверное. Правильно. Да, это про просто символ. Мы можем, это просто символ, да, но ты дальше просто фигурные скобки показал, а мы их не обсуждали. Я проще, мы это не вводили, не говорили ни в тот раз, ни сейчас. Вот. Вот просто так. А, грубо говоря, это способ введения конструктора, который вернёт то, что внутри. То есть я бы мог бы написать run my. My Да, это у нас конструктор. Конструктор типа, правильно? Вот my конструктор типа, конструктор значения, а здесь был деконструктор, если уж говорить в терминах языка честных. Вот. Угу. Да. Конструктор типа на вход тип, а деконструк то, как мы получаем в итоге значение. А при этом вот эта абстракция Real World, знаешь, она немножко так ускальзывает. То есть вроде как бы есть понимание, а с другой стороны типа что значит весь мир, как у нас работают функции, как нам описать. То есть, а, ну, мы сейчас думаю закопались такие немножко, да? Не, ну оно оно, когда поймёшь, слишком просто, чтобы это его описывать. То есть, грубо говоря, для того, чтобы это была сущность наша была функцией, нам нужно, чтобы у неё всё, с чем она работает, было в аргументах, которые она принимает. А всё, что оно меняет, было в в значение результата. Соответственно, Real Word, я не знаю, как бы это писать, это ссылка на реальный мир, ну, на всё, что есть. То есть как бы понятно, что это некоторая несуществующая вещь. А вот, но она позволяет описать математическое и описать наш мир и описать вообще, что наше вычисление делает. Ну, как бы построить модель. Знаешь, мне это вот так вот что напоминает, если об этом подумать. Это как раз такое некое на тегирование похоже. То есть мы помечаем по сути тем, что эта штука взаимодействует с реальным миром. И как раз любая штука, которая взаимодействует с реальным миром, она должна, собственно, этот мир откуда-то получить. В свою очередь он требует его прихода сверху. То есть такой ты его протаскиваешь как бы через всю цепочку до того места, где он реально используется. Ровно так уже. И фактически у тебя просто получается некий такой стержень, на который всё это навешивается. То есть оно хоть где-то есть, не знаю, как бисер вот представить. Оно вот на это всё насаживается, да? Это так и работает. Благодаря этому оно автоматически решает проблему того, что вот как мы рисовали ээ N = M =НDО и оно бы вместе всё за, как мы назвали, мемоизировалось. Соответственно, здесь это позволяет протащить вот эту вот зависимость между значениями. То есть мы видим, что если мы сначала читаем из файла, а потом пишем файл, то у них есть зависимость по данным, по вот этому протащенному нашему реальному миру. И компилятор, интерпретатор никто не имеет права как-нибудь их переставить по-другому, потому что у них есть вот в этом графе вычисления есть зависимость между данными. Вот родилась аналогия. Знаешь, я понял ещё в чём проблематика, когда ты говоришь реальный мир. А важно понимать как бы антагониста, грубо говоря. А что такое нереальный мир? Так вот, у тебя нереальный мир - это чистый мир хаскеля, в которых чистые вычисления, а реальный мир - это побочные эффекты зависимости, последовательность и всякие разные там ограничения и так далее. И поэтому как только ты с этим взаимодействуешь, то у тебя, грубо говоря, меняются немножко правила. Я бы по-другому сказал исполнения, не по-другому. Да. Да, давай чуть-чуть по-другому. Реальный мир, то есть у нас есть чистые вычисления, с которыми работает язык, с которыми всё обычно, хорошо, как мы разбирали. И у нас есть вычисления с побочными эффектами, ну, даже с аоэффектами. И реальный мир - это, грубо говоря, билетик, который позволяет затащить вот эти вот функции, меняющие что угодно, работающие как угодно, затащить в наш хороший чистый мир. То есть, грубо говоря, это можно рассматривать как какой-то тикет, билет, токен вычисление. Грубо говоря, для того, чтобы это вычисление вычислить, ему нужно получить этот самый токен разрешения на выполнение. А когда он всё выполнил, он должен отдать, сказать, что вот я молодец, я всё вычислил и передать этот токен дальше. Вернул обратно. Так, да. При этом самое главное, тут важно понимать, что с ним-то ничего не происходит. То есть нет такого, что мы как-то взаимодействуем на него. Он просто протаскивается, да, сквозь всю эту историю. На самом деле у вот этой штуки, если заметить, если посмотреть в языке, как это реализовано, у неё нету конструктора данных. У неё есть только тип, а это как бы данные, которых не существует. То есть с ним даже если постараться ничего не сделать, не сделать, да, просто протаскивается. Вот. Да. И соответственно, грубо говоря, как это работает? У нас есть вот наш функция main, которая i ну могла бы быain. А чём не my тогда, раз написали my main myo. Вот. И грубо говоря у нас есть наша программа, наш интерпретатор Runтайм система, которая, грубо говоря, отдаёт в неё вот этот вот, а, может сконструировать такую штуку и передать туда. и наши вычисления все вот в цепочку встанут и начнут вычисляться, передавая вот этот токен от одного к другому. Аналогия, но достаточно корректная. Всё понятно. О'кей. Теперь мы как будто вот знаешь, наверное, не идти внутрь, потому что я чувствую, что вот как будто нет сейчас потребности вот копать, пытаться глубже, как оно там внутри устроено, а именно вот непосредственно работа с этим. То есть как мы понимаем, что вот сейчас я должен именно такой синтаксис написать вот как бы для чтения файла, например, там или для записи файла? Да. Теперь давай синтаксис. И а то есть у нас вот возьмём вот примерно то, что мы внизу вот здесь писали. То есть возьмём аа да напишем собственную функцию. Я бы, знаешь, как её назвал? Прямо, чтобы было ближе к пониманию. Read report. То есть вот прямо конкретный репорт она умеет читать какого-то специального формата. Давай сделаем так. Read in from file. Read report гораздо лучше. Никогда не пишите так функция. А вот, но вот соответственно оно, а, да, то есть я пока undefined напишу, потому что на самом деле то, про что мы поговорим, нужно внутри. Вот мы и мы хотим, например, так сделать. Прочитать два энта и после этого записать их сумму. Подойдёт? или не подойдёт как-то. Ну, ты имеешь, что теперь мы эту функцию два раза используем уже в нашей как бы программе, да. А единственное, почему файл pass Мне просто интересно. Я так понимаю, этот тип, чем он от стринга отличается? Это исключительно для лучшего обозначения или он реально отличается в не самых последних версиях языка? Это просто это просто стренга, ничем не отличается. Но в последних версиях, и если взять последней библиотеки File Pass, оно наконец-то отличается, потому что оно не должно быть стрингой, а это гораздо более сложный тип, который отличается в разных ось, а потому что они немножко парситс, что ли, прямо внутри. А, ну да, да, он он внутри смотрит. Ну, например, у нас просто разный набор символов может быть в файловой строке на линуксах, в NT ядрах, на Макаси. А они просто разные. Вот поэтому в некоторых местах это используется текст, в некоторых местах это используется байтстрока. Соответственно, ну, например, у нас может или не может быть символ нулевой в файл пассе. Слушай, ну просто я такой думаю с точки зрения реализации, получается, ты туда передаёшь, он реально что, регспами по строке проходится или, ну, с нересми, а циклом проходится, проверяя каждый символ. Ну, в зависимости от того, как мы создаём, но он перекладывает в, грубо говоря, что в хаскеле хорошее. Это такая, как правило, мы сделать некорректные состояния невыразимыми. То есть, грубо говоря, что у тебя типы такие, что ты не можешь описать некорректное состояние. Поэтому, когда ты создаёшь файл pass, если его не преобразовать в тот базовый тип, с которым ты можешь работать в этой операционной системе, он выбросит исключение, ну или там ошибку покажет, в зависимости от того, какая пишка используется. Вот. Но пока, в общем-то, основная, и все библиотеки работают со стандартной строкой, страдают, мучаются и решают одни и те же проблемы по 10. Да, эти переходы, это всегда, конечно, кошмар. Так, ну хорошо. Вот теперь вот, знаешь, тип логично выглядит. Понятно. Вот мы возвращаем мир с интом, а всё. Значит, мы не можем просто так его прочитать. Нам надо уже читать его по-особенному. Да, давай мы сейчас попробуем эту функцию написать, и это, на самом деле, будет интересно, если мы возьмём что-то кроме андефайна. То есть у нас есть readфайл, который вот такого типа, напомню. То есть он читает файл и возвращает строку. У нас есть, например, функция read, которая упрощённо, давай я сделаю вот так. Re in, а, которая из строки получает in. Пока даже ошибки рисовать не будем. И нам нужно их как-то объединить. У нас вот эта вот штукан. Подожди, read ты имел в виду по сути pars in, потому что readт звучит как чтение из файла. А всё-таки всё-таки есть, да? Я я просто у нас есть функция readт, которая читает. Вот давай pars in сделаем. Pars, да? Да. Я напишу здесь. Нам сейчас, на самом деле, это сложнее, но нам сейчас в разговоре будет чуть проще. Мы говорим только о монадах. Мы не вводим, не говорим про функры, аппликативные функторы и прочее. И предположим, что мы напишем так вот. И нам нужно сделать какую-то новую функцию, которая на входе принимает файл Pass, а на выходе делает IO in. То есть нам их нужно как-то объединить, да? Да. Просто последовательный вызов. О'кей. Да. И у нас нету никакой функции, которая из i из i string сделала быстринг, чтобы передать её сюда. Потому что если у нас такая функция будет, то мы потеряем наш вот этот вот real word, потому что он здесь внутри i хранится. И дальше у нас будут все варианты проблем. Нам нужно что-то придумать. Ага. Я единственное только вот ещё раз подчеркну, то есть типа м как бы вот это вот мышление в терминах хаскеля. Мы не можем просто написать чтение одного, а потом типа передать в другое просто потому что мы работаем с айзически
не даст так сделать. Ну в данном случае неважно, что, потому что у нас типы не сходятся. То есть, грубо говоря, чтобы написать, а, pars in от что угодно readфайл,
то есть, например, вот вот я хочу написать что-то такое, а у нас там функция, он скажет: "У тебя ожидался i string, а ты мне ой, в смысле, ожидался стринг, а ты мне iOS string бросил." Вот сейчас мы воспользуемся компилятором и красивыми подсказочками. То есть напишем вот такую вот штуку. А в Хаскеле есть такая классная штука. Так люблю делать. А когда placeхоolder и когда мы пишем такую функцию, он попытается подставить, какой тип подходит туда и подсказать. То есть, э, если я сделаю, попытаюсь это собрать, он скажет, что вот мы нашли, ну, это называется хол дырку, и мы говорим, что, а, тип, который там должен быть сейчас аа так вот этот файл F. Вот. А, о'кей. А мы функцию не определили, да? Да. Слишком слишком просто мы
так мы сделаем. А, да, кстати, кто не знает, return в Хаскиле это не return как в обычных языках, просто вдруг на всякий. Давайте вот так вот напишу, чтобы не было шаных этих. Вот. И он написал, что типа вот я нашёл дырку, которая функцию help me, которая должна сделать из iOS string string. Дальше мы можем вернуться к вот этому интерпретатору. Если у нас наш тип, у нас для него есть интерпретатор, мы бы это могли сделать. То есть, грубо говоря, если у нас есть дата Mayby, A, у нас есть функция Maybe,
которая позволяет её интерпретировать и сделать. Но для IO у нас такой штуки нету в языке. Она не экспортирована. Единственное, кто может интерпретировать это - это сама программа, которая запущена. Поэтому у нас теряется эта возможность. И вместо того, чтобы вызвать деструктор, вместо того, чтобы интерпретировать это iё здесь, мы должны наоборот как-то поднять наше вычисление в этот контекст. Собственно, вот я говорил раньше, что вот это вот возможно что что-нибудь кликнет и как раз где слово контекст важно, это как раз здесь. То есть, грубо говоря, нам нужно придумать, как мы наше вычисление объединим, засунем вот в этот контекст. Дальше предположим, что у нас есть некоторая функция, назовём её pipe или так, как делается в некоторых языках, которая позволяет написать что-то такое. У нас есть значение, у нас есть обычная функция, вот какая-нибудь такая история. Предположим, оно у нас есть. Тогда, если оно у нас есть, не будем написать для, потому что для i её написать нельзя. Для my i, которые я, к сожалению, удалил, написать бы можно было, и мы бы прямо написали этот пример. Если хочешь, можем вернуться. То есть вот вот вот вот эта вот штука, функция с таким типом, позволяет нам построить цепочку вычислений, которые в каком-то контексте. Ну, в данном случае в её контексте. Так, давай применим, посмотрим, да? То есть, например, здесь, если мы напишем read file
pars ent, так и попробуем зайти, посмотреть, что он нам скажет. Вот скомпилировался, всё отлично. Можем вызвать её read int from file. Что там было? Input txt. Отлично. Получили единичку, двоечку. Вот это уже выглядит легко. Да. А в чём, а, собственно, вот то, что мы сейчас вот увидели - это монады. А, соответственно, откуда это взялось? Чисто из математики. Там, наверное, не буду углубляться, но, грубо говоря, а вот мы написали вот эту вот штуку. Сейчас для iписа
всё-таки я верну пай. Я напишу Pippe, потому что сейчас мы их pipe pipe i pipe i здесь мы, кажется, говорили про этот синтаксис. Вон Ишка его знает, да? Всё работает. Мы можем написать pipe maybe. Ну, здесь мы можем написать напрямую, как он предлагает. А мы, как я говорил, у нас есть функция Maybe, которая, да, вот есть готовая такая функция, которая позволяет интерпретировать. Ну, то есть как ещё раз эта функция называется деконструктор или как-то по-другому? Ну, деструкто можно говорить просто деструктор. Будет честно, надеюсь, потому что для опшников, конечно, такое слово прямо очень жёстко привязанное к деструкторам, да. Мо можно сказать интерпретаци. То есть мне гораздо проще, когда говорить про это, чтобы было понятно всем, говорить интерпретатор, а оно более-менее, да. Но мы видим сразу правило, да, паттерна, что оно у тебя возвращает тот же самый контейнер в обязательном порядке, да? Да. То есть у нас есть перезначение возраща у нас есть функция, возвращающая новый контейнер, и оно и оно как бы поднимает вот эту вот функцию наверх. То есть у нас получается вот тот же самый контейнер, контекст, который был. лучше контекст говорить, потому что с контейнером сразу начинают эти структуры, списки и прочее возникать. Да, это контекст, потому что он описывает, что здесь может происходить, где что здесь определяется уже конкретным интерпретатором, конкретным контекстом. Вот, соответственно, мы можем написать это для типа, напомню, data Acer. Он там и Ишку уже сразу. Кстати, про эти монады нам ещё поговорить надо будет, потому что мы, наверное, исходим из того, что те, кто слушают, они не знают про них. И надо вот сейчас эти кейсы мы с тобой и пообсу, да? Ну, собственно, это получается некоторый паттерн для структуры данных, которая описывает контекст вычисления, мы можем написать такую функцию. И если мы напишем такую функцию, то мы их сможем объединять в цепочке. Собственно, вот есть такой класс мод, его на самом деле уже нету, который, собственно, и определяет вот такую функцию, которую мы там писали внизу. Вот здесь вот это для нас было магией, теперь это перестаёт быть магией. Для структуры Ао у нас есть реализация класса Monad, которая, собственно, даёт даёт реализацию вот этой вот конкретную реализацию для этого оператора. И мы можем их так писать. Собственно, вот то, по сути, по сути пайп, да? Ну, по сути пайп, да, по сути, ну, особенный немножко, да, с правилами определённым. Здесь нужно очень правильно понимать. Оно создаёт зависимость по данным, но оно не создаёт зависимость по порядку выполнения. То есть на самом деле, а если у нас, э, тот интерпретатор структуры данных и тот, та, э, содержимая структура, вот которую мы используем для описания контекста, не содержит в себе в зависимость по данным, то у нас может вычисляться в произвольном порядке или не вычисляться что-нибудь. Вот. То есть это это говорит, это говорит, то есть эта история говорит, что если у нас есть какой-то контекст с эффектом, то мы можем построить более, ну, следующий шаг контекста. Если нам вытащим значение изнутри, я не знаю, плохо получается объяснить, но как-то так. Нет, тут всё понятно. Ну, по крайней мере мне, я понимаю, что опять же, когда люди смотрят, могут так не вникать, а, но тема сложная, да. И смотри, знаешь, какой вопрос сразу вот прямо для меня очевидным отсюда следует, который бы хотелось задать. Это всё работает вот если ты на парнын снова сверху посмотришь, то его определение оно очевидным образом подогнано, да, потому что мы парн сделали возвращающим ат, а не просто int. И в реальной жизни парн был бы написан вот именно так. И возникает вопрос: а в этом случае что делать? Потому что мы хотим соединить, но парн не соответствует давай деструктору. Правильно я говорю? Да, типу деструктора. Да. Вот тогда что делать? Да, вроде бы правильно. Соответственно, здесь на самом деле класс монад он требует и выполнения других. И есть и на самом деле для того, чтобы работать с этим, если опять математические термины использовать, которые используется в хаске, это аппликативный функции. То есть на самом деле ровно та же история. Если мы хотим так сделаем пайpe i. Вот это хорошо, да? Ну, у нас сейчас пайп нет пайпурио. Не знаю, как это написать. Вот. Ладно, напишу как есть. То есть у нас есть класс F. Ишка всё правильно пишет. Сложное, не сложное. Так, не будем пока сюда смотреть, посмотрим только сюда. Грубо говоря, если мы можем из А
так надо сюда смотреть, блин, неудобно. Так, вот сейчас какой-то космиче знак египетской мифологии получается, да? Да. И знаков знаков египетской мифологии много. Я думаю, как проще всего это объяснить, чтобы было понятно. Вот. Ну, грубо говоря, то есть как начнём с конца, какую историю мы решаем? У нас есть нам нужен некоторый комбинатор, который позволит написать что-то такое.
Да, в конце должно быть, ну, а, я слишком далеко. Нам достаточно просто функсать. Вот. А так, да, и, да, нам нужно что-то такое написать. То есть мы можем написать, напишу MAP IO. Но опять же для й плохо, потому что у нас нету интерпретатора и нам придётся его расписывать. Так, но, грубо говоря, если бы мы могли заглянуть вовнутрь АО, то мы можем такое написать, написать реализацию, собственно. Так, блин, я очень хочу вернуть My i. Давай, давай. Вот напишем так. То есть мы можем, если у нас есть такая штука, то мы можем написать Fmap my. Посмотрим, напишет ли нам его иишко. Давай проверим. Кажется мне, что она написала всё правильно. То есть если у нас есть возможность деконструировать my, то есть у нас есть функция здесь, а, которая принимает реальный мир и возвращает значение плюс реальный мир. Местами поменяем, потому что может так лучше. Вот так. Соответственно, мы должны вернуть функцию, которая принимает реальный мир. Принимает и возвращает. То есть, что мы делаем дальше? Мы вызываем функцию G, передавая ей реальный мир, и получаем некоторый новое знае состояние реального мира и то, что она навычисляла. После этого мы берём нашу функцию, которую чистую, которую мы взяли, применяем её к результату. получаем итоговый результат и возвращаем тот же самый новый реальный мир, потому что у нас чистая функция, мы его не меняем. Всё правильно. Таким образом, мы написали функцию, которая позволяет поднимать чистое вычисление вовнутрь нашего вычислительного контекста. Вот это вот мы написали для нашего i. Реальное айоличается не очень сильно, там много внутренней магии хаскеля, но по смыслу ровно то же самое. А вот, соответственно, мы можем сделать FMAP may точно так же для боле. Вот сейчас хочется примера, то есть покажи, пожалуйста, именно, собственно, соединение сs in, который уже такой обычный без заё. То есть мы возьмём Fmap in, напишем, и принимает он аргументом вот эту вот штуку. Проверим моя, да. F mapion. Проверим. Что-то не так пошло, потому что мы здесь должны написать так и должны написать, а
my readфай.
А, да, извините, здесь
так, я думаю, как это правильно сделать, чтобы это всё посмотреть. Ну, слушай, ну давай даже без вызова. Понятна идея, понятно, почему ты это делаешь. Да, давай, давай, давай. Так, вот так сработает. То есть у нас есть FMAP, который класс типов, у которого есть реализация. Это конкретная реализациямапа для my. А вот и всё так будет работать и здесь. Ну и понятно, что есть альтернативный синтаксис, в котором мы можем передавать это не в виде просто функции с аргументами, а цепочкой. Правильно? Да. Ну то есть по умолчанию Ну да, длямапа есть. Так 43. Что я потерял?
А я где-то определил в MAP. Да, этот класс уже существует. Сохранили, перезапустили. Всё, всё работает отлично, да? И есть альтернативная функция, которая называется вот так. И f ма равняется F мап. И я могу записать. Задолбаешься, конечно, такие символы писать, честно. Да. Ну, есть такое, когда непривычно на это всё смотришь. Та зато потом оно может очень хорошо смотреться, потому что, например, если мы хотим сделать какую-нибудь, а, саs, не знаю, вот так сделаем, in, я могу написать что-нибудь вроде плюс. Вон Ишка уже показывает всё, что я хочу написать. То есть, грубо говоря, мы как будто бы пишем обычный плюс, только разбавили его. вот этими смешными символами, которые позволяют всё поднять в наш контекст. И были пропозлы про то, чтобы сделать это ещё более лёгким. А вот, соответственно, таким образом мы можем объединять наше вычисления, всё запускать, всё делать. Вот знаешь, какой ещё хочется вопрос задать? Вот ты, как человек, который в этом хорошо разбираешься, видишь здесь случайную необходимую? То есть всё, что здесь есть - это необходимая сложность или есть всё-таки элементы случайности, связанные с историческим наследием хаскеля, с какими-то неправильно принятыми дизайнерскими решениями и так далее, или в любом другом языке, в котором мы бы хотели реализовать именно такую концепцию работы с управлением в ленивом языке, управлением порядком и контролем на всех уровнях, да, чтобы это на уровне типов было, нам пришлось бы в конечном итоге прийти к тому же самому, к чему мы здесь с тобой сейчас пришли. Есть некоторые проблемы, но они минимальные, грубо говоря. то вот, ну вот здесь мы смотрели, как классы устроены, как называются и как устроены классы. А решение было при было историческое наследие. То есть, грубо говоря, там в классе Монад есть вот этот вот ретрн, который мы обсуждали. На самом деле он должен быть выше. у нас там не хватает некоторых промежуточных, но помимо этого, а с подходом, когда мы, а эффекты определяем через структуры данных и, соответственно, вычисли как вычислительный контекст, я не вижу, что бы могло быть проще. Есть другие альтернативные пути, которые могли бы быть типа линейных типов, а, но это уже совсем другая ветка. С помощью неё можно было бы решить те же задачи другим способом, но я не сказал бы, что это было бы проще. А вот если а перед тем, как нырять прямо в монады поглубже, а вот, допустим, вот мы написали таким образом: "Давай сейчас подведём итог, чем это будет отличаться, если бы я то же самое, чтобы люди для себя это вынесли, вот то же самое я бы писал у себя в языке, в котором этого нет. JavaScript, Java, что угодно. Программа, написанная в таком стиле с применением. Ну, собственно, у нас контекст, мы его протаскиваем, все дела. Чем это будет на практике в рантайме отличаться? Или в compileтайме? В compile мы уже увидели, он нас будет заставлять писать определённым образом. Ну, допустим, мы ему удовлетворили всем его желаниям. Что изменится на практике? В Если мы начнём писать в таком стиле, то с большой вероятно прости, наоборот, я хотел сказать, что как раз-таки наоборот. То есть вот человек, который всё это увидел, может подумать и, наверное, справедливо, что что-то непонятно, нахера мне вся эта сложность. И вот хочется понять, то есть вот когда язык тебя заставил так написать, а рядом человек на Джаве сказал: "Да, я просто так прочитал файл и написал, как мне надо". При этом понятно, что в теории там могут быть ошибки. И вот они запустили в рантайме, например, ты можешь сказать, что в Хаскиле не будет никаких случайных исключений. Там будет вот так, а в Джаве, если ты там ложанул, у тебя просто шмяк и всё грохнулось. или это как-то по-другому проявится? Я бы был рад сказать, что это так, но на самом деле в Хаскеле исключение, работа с Сайо и прочее, там есть про ну не проблемы, то есть там ты точно получаешь больше гарантий. У тебя, когда ты написал функцию, у тебя никаким образом она не сделает случайное Ао. То есть, если ты так пишешь. И таким образом ты можешь записывать определённые ожидания, которые ты хочешь. Например, при работе с базой данных и транзакциями ты можешь выражать некоторые требования, которые ты хочешь. Например, ты хочешь, чтобы у тебя во время работы с базой данных и вызова транзакций никто бы в сеть не пошёл на какой-нибудь сервер, который отвалиться может. А может быть, ты так и хочешь, но ты хочешь это видеть. И если ты это тебе подходит и это то, что ты хочешь, ты хочешь явно принимать решение, да, это о'кей и нет, это не о'кей. Такой подход, который мы описали в том же самом Джаваскрипте, позволяет сделать, а описание вычислений, который даст тебе гарантию, что ты увидишь, что здесь что-то не то происходит. Ну, точнее, в Хаскели это по дефолту, а а там надо стараться и или как повезёт, да, в Хаски это по дефолту, ну, в случае ну как, ну, не совсем. Например, в данном случае Ао обозначает абсолютно любое ай. То есть там может происходить что угодно, но по идее ты можешь выделить более интересные подмножество, например, ходить, отличить ходить в сеть от ходить локально. И, например, тебе о'кей, если ты ходишь локально, что-то делаешь, но ты хочешь явно видеть то, что ты пошёл в сеть, и как-то на это отдельно реагировать. Если твоя функция ходит в сеть и может отвалиться по внешним причинам или заделеется на полчаса, потому что TCP, ты хочешь это явно видеть, и ты можешь это выразить стандартными средствами. Для этого тебе не нужно какие-то специальные библиотеки, логику. Это то, что описывается в стандартной библиотеке. Если переходить в JavaScript, то, соответственно, там можно как обычное IO выделить, так и выделять те же некоторые более высокоуровневые концепции, которые скорее за которыми скорее больше вероятности, что хочется посмотреть. Нугу. Я я скорее вёл к тому, что в любом случае в Джейс так никто делать не будет. Там даже проблема именно как раз, ну, допустим, кто-то это притащит. Помнишь, кстати, был период, э, лет, наверное, 10 назад, когда вот кложа, когда попёрли все эти вещи, и люди старались тащить функциональные либо к себе в языки, пайпы, там ещё что-то. Прямо очень много было для Пайтона появилась такая библиотечка для JS, для всего, короче, и всякила андекоры тоже как бы пытались. У них там ещё и FP-версии были, которые все такие карированные. Я просто помню, когда народ в это рванул и была попытка перейти на это, но она разбилась, ты сам понимаешь, на экосистему, на то, что у тебя есть всё равно стандартные пути решения проблем, и оно затухло. То есть периодически сейчас это возникает, но такого уже массового движения нет. Поэтому я не очень в это верю. Я скорее как раз говорю про то, что если мы просто берём стандартный язык, то ты это никак не выразишь. У тебя просто этого нет. Ты вот как написал, надеешься на чудо и надеешься на себя. Ну, на тесты максимум. особенность. То есть, что хочется подчеркнуть, система типов Хаскели - это не просто сложность, которую вам воткнули, чтобы вы плевались от того, что на языке сложно писать. Это попытка выразить этот мир, описать его типами не не описываемое вот так вот с ходу, да, так, чтобы это с этим можно было работать более надёжно. Причём я здесь подчеркну, что везде, где мы делали, то есть вот все эти штуки, вот, например, вот это вот это не прямо сходить в файл, это описание вычисления, которое сходит в файл. То есть всё, всё, что мы здесь делали, оно всё продолжает быть чистым. То есть у нас всё, что мы вызываем - это всё чистое. Но мы разделяем вычисление построение вычислений, построения вот этого вот графа, что мы будем делать от непосредственного выполнения. А это история, которая так или иначе возникает в разных языках. И на самом деле, если бы, ну, я не знаю, успеем ли мы, не успеем пройти дальше, то всякие вот такие истории - это то, что вполне может иметь смысл в джесе. И то есть как построить в глупых словах, как построить вычисление, которое мы можем удобно замокать. Я бы не сказал, что это главный способ использования для этого, но, грубо говоря, если нам нужно высоко абстрактное вычисление, что мы работаем с чем-то, а потом заменить это, мы сходили в local storage файл куда-нибудь ещё или, не знаю, достали ключик из тpmреджа и хотим мы это как-то красиво описать и описать вычисление вокруг этого, то это решение и это как бы готовый хороший работающий паттерн проектирования, который существует и в Хаскиле. проверен годами, десятками лет уже. Интересно то, что были эксперименты довольно серьёзные. А по поводу того, насколько это приживётся в других системах. Ты помнишь для Джеса пару языков, которые реально даже попёрли Элм, во-первых. Во-вторых, Pure Script, который был тоже, в общем-то, по сути хаскелем. И в конечном итоге оба проекта провалились. При этом, кстати, выжил немножко окамоловский вариант. Ну, это так просто к слову хочется сказать, что всё-таки на практике это упёрлось в одну очень простую историю. Там, ну, во-первых, интероп сумасшедший как раз, чтобы всё это ограничить. А, во-вторых, у вас весь браузер сплошная событийная фигня. А, и всё это как-то людям было очень сложно, на самом деле, контролировать, потому что там что не делает побочный эффект сплошной. И я как раз по этому поводу хотел сказать. То есть, допустим, у тебя вот опять же на Джесе, как будто ты пишешь на тайпри на, э, господи, на этом хаскиле подобном языке, и у тебя где-то в самом низу, ну, по какой-то причине ты захотел сделать какой-то эффект. Насколько сложно будет переписать всё, если у тебя его не было? Зависит от того, что сделать. Ну, то есть, если делать всё честно, красиво и хорошо, то сложно. Понятно, что в том же самом Хаскеле у нас есть ансы краси. Это вообще это типа давайте Эни в тайпскрипте поставим. Вот пример, мне кажется, из той же это, ну, это не совсем-то, то есть нужен, на самом деле, достаточно безопасный вариант, а там есть другие гораздо менее безопасные, но, грубо говоря, а если понятно, почему оно, то есть, unsave, по-хорошему, не везде следует, но, грубо говоря, а с, если мы вызываем функции, в которых написано save, то мы можем написать код, который сделает преобразование одного типа к другому. То есть, грубо говоря, если мы можем написать, у нас есть такая функция, которая превращает в значение одного типа в значение другого типа. Она тупо обманывает тепчекер. Э не стоит вызывать, если не знаете зачем, и использовать до неё не так просто добраться. Но, грубо говоря, если использовав, а, если мы можем с помощью функции реализовать такую штуку, то тогда у неё префиix un save. То есть это небезопасно, это плохо, так делать нельзя. Иногда так делать приходится, но обычно это приходится делать, чтобы сильно упростить себе жизнь. Вот. Но как бы escape HCH есть. То есть, грубо говоря, вопрос, зачем хочется где-то внутри использовать эффект? Если хочется использовать внутри эффект для того, чтобы логировать временно, пока пока отлаживаешь или пока смотришь, то так сделать можно. Это сделать достаточно просто. Если по каким-то причинам пришлось иметь эффект, и это часть, а, сигнатуры, часть, а, того, что мы говорим о том, как работает наш сервис, то придётся хорошо задуматься, а почему такое произошло? И если у этого есть хорошая причина, туда придётся парефакторить. Возможно, много парефакторить. Много, да, да, это звучит как, конечно, безумие, поэтому тут надо заранее реально много думать. А у меня есть прекрасный пример по поводу вот этого применения фактически преобразования типа, когда ты явно говоришь: "На самом деле это вот это". Мм, с один из самых типовых примеров, которые обойти невозможно. То есть это не вопрос того, что мы плохо проектируем, а вопрос того, как этот мир устроен. Это лиснеры в браузере. Ты, наверное, знаешь, да, когда ты вешаешь обработчик, у тебя, собственно, элемент, который приходит события - это всегда ивент, в котором у тебя есть просто элемент. Если, например, у тебя это произошло на форме, но у тебя нету вариантов никаких, кроме явно преобразовывать, что это был элемент формы, потому что listoner всегда принимает на вход одно и то же. Вот. Или, например, у тебя бывает такое, что есть он change или онклик, который принимает на вход, ну, допустим, а там, ну, короче, какой-то value просто, да? А дальше у тебя вызывается функция, которая принимает на вход, а, литеральный union, что у тебя конкретные значения, и ты никак не можешь тип этого анченджа поменять, просто потому что так работает дом API. Вот. И тебе там приходится это делать. Поэтому вот эта функция, она на самом деле, я вот не знаю, насколько Хаскель часто вы используете, но вот именно во взаимодействии дома с твоим кодом, особенно если твой код такой очень типизированный, сильно специфичный, когда у тебя там не просто строки, да, значения конкретные, то приходится регулярно использовать. В Хаскеле такое используется редко. И где используется, мне придётся очень много умных слов сказать, которые с которыми обычно, если хочется с языком работать, всё равно не ну не не столкнёшься многое время. То есть на самом деле это приходится использовать в тех местах, где э такая граница возможностей тапчекера. И это уже такие достаточно специфичные решения, сложные. То есть, грубо говоря, если делать аналог eventтлиistнера, то есть решения, которые позволяют не писать unsave performo. Потому что, в общем-то, когда у нас есть значение, а значение там, у нас это некоторая структура данных в Хипе, которая у которой есть ссылка на то, какого она типа. То есть мы можем знать, что это такое, потому что это нужно по Gbage для garbage коллектора. У нас есть некоторая метаинформация, и, грубо говоря, мы всегда можем написать функцию, которая проверяет, а является ли это значение значением типа, которая нам интересно. И в этом случае это преобразование является уже безопасным для того, чтобы написать эту функцию вот внутри неё в ядре и где-нибудь в ядре может быть unsafe, но в обычном пользовательском коде такого не бывает, ну, практически нигде в нём. Ну, соответственно, здесь, э, ну, например, в том же Хаскеле, если писать вот некоторые ивентлинеры подписывание на неизвестно что, то, грубо говоря, функция, которая вызывается, она знает, на что она подписывается. Соответственно, она может проверить, является ли это значение нужным или нет, или вернуть джаз значение нужного типа или нан. Плюс в современном хаскеле это ещё на уровне типов можно протащить, так что это будет прям просто максимально безопасно. Тайпчекер будет проверять всё, что угодно. Это круто, но это нужно смотреть какие-нибудь блоги по Хаскелю, типа видео типа Haskel Unfolder, там где рассматриваются всякие интересные фичика, потому что это очень классно, очень красиво, э, но прямо нужно много контекста. Стараться, да, надо стараться, а тем более Иишка, наверное, к счастью, такие вещи упрощает, но особенно для тех людей, которые в этом разбираются. Давай перейдём непосредственно вот к манадам побольше, посерьёзней, потому что мы, видишь, получается, как бы и ай цепанули, потому что про него надо понимать. И тут вот, собственно, и монады всплывают. А я бы сказал на уровне, давай с такого понятного для всех примера, это, собственно, ошибки, потому что и Mayби можно тоже к ошибкам прицепить в каком-то смысле, и айвермонаду можно прицепить. Вот в целом про проблематику поговорим. Наверное, нормально, если я скажу, что проable, вот optionalable, вот эти вот вещи, которые во многих языках есть, то, что в Хаскеле, каким образом это решается, да? Давай тогда посмотрим. А, соответственно, ну, наверное, имеет смысл сказать, да, что проблема вообще нала, а, во многих языках довольно серьёзная. Ну, я бы сказал, как это миллион долла. Есть две проблемы, по-моему. Это а окончание строк в в C проблема, да? И налы, когда у вас есть N pointer Exception, который раньше, да и сейчас, в общем-то, во многих языках это просто абсолютный капут. А и поэтому все современные языки делаются с понятиемable. В Хаскиле это было с самого начала, но совершенно другим способом, который многие пытаются или пытались адаптировать. Как минимум библиотеки точно везде есть. Зером он решил сломаться. у тебя как будто немножко звук потише стал, да, соответственно, что я даже не я не знаю, как исторически к этому подошли, но, соответственно, у нас есть некоторое значение и определённого типа, и у нас может оказаться так, что его может не быть на руках по каким-то причинам. Соответственно, мы, э, в этом случае, как обычно в Хаскеле, мы можем это явно записать. Собственно, наверное, мы даже так сделаем. Мы назовём, ну, наверное, часть того, что нала в Хаскеле просто нет. Как на самом деле есть в библиотеках, да, даже Pointer exception в тех библиотеках можно получить. Вот. Но это уже отдельная история. Это так. Так вот, то есть у нас это может быть либо ничего, один вариант, либо что-то. А, соответственно, как бы что думаем, то и пишем. А, соответственно, вот такая штука. Соответственно, во всех местах, где мы будем её использовать, оно, соответственно, прорастёт. А поэтому, если мы, не знаю, там захотим сделать что-то типа, а пик, взять что-нибудь из памяти, и у нас будет optional pointer, то нам придётся написать что-то, не знаю, там, optional string, что-нибудь ещё там случай, если мы из памяти читаем. Вот. Соответственно, все функции, в которых нам придётся работать, будут знать об этом. То есть у нас появился контекст, который никаким образом не потеряется, и мы не сможем вот это вот набл значение передать туда, где от нас ждут конкретное. Компилятор нас от этого защитит. Здесь как бы, наверное, всё понятно, нету никаких никакой магии. Так, но есть нюанс. А поскольку это настолько фундаментальная штука, пронизывающая всё, это обязано быть в ядре языка. А потому что у тебя даже стандартная библиотека с этим работает. Правильно я понимаю? Это в данном случае нет. Это обычная структура данных. Вот мы завели и здесь как бы ничего не требуется. То есть это не что-то в ядре языка. Это как бы Ну я скорее про другое. Про то, что вот стандартная проблема многих языков, которые пытаются себе: "О, смотрите, я тут монаду увидел, дайте все языки реализую". И получается, что ты ужас ежом скрещиваешь. У тебя половина библиотек работает на аналах, половина библиотека на этой штуке. Поэтому это важно как бы, чтобы оно было частью, то есть она технически обычная, но все должны работать с одной и той же фигнёй. Часть стандартной библиотеки, я бы так сказал. Ну да. То есть, если это является частью стандартной библиотеки, частью стандарта, который в Хаскеле даже есть, соответственно, что там должна должен быть тип Mayби, который который у которого такие-то конструкторы. И если кто-то захочет реализовать альтернативную реализацию языка, по-хорошему, они должны всё это сделать ровно так же. Вот, соответственно, вот у нас есть instance, как мы с ним работаем. Например, если мы хотим какие примеры с ним сделать хорошие, которые понятные, ну вот какую-нибудь функцию, да, где тут надо, знаешь, наверное, что показать, ну, реально функцию, которая это возвращает и, собственно, как мы с ним работаем, почему у нас не возникает проблем, что что нам компилятор всё подскажет, как надо правильно сделать. Да, давай с чистым или сразу с Айо будем? Слушай, а поче сразу давай сразу сйби. Почему бы и нет? Что нам придумывать? Мы Да, то есть давай сразу у нас есть можно для примера просто показать, как my Maybe вот типа My как бы выглядело, чтобы мы по Давай прочитаем файлик с Давай здесь, во-первых, мы напишем честный Maybe, э, чтобы видеть его перед глазами, как будто бы мы мы его написали. Nothing just - это всего лишь это конструкторы ведь называется, да? А, два конструктора, два варианта. Вот, соответственно, написали деструктор, ну, интерпретатор, который позволяет нам подставить дефолтное значение. То есть, если у нас держал nothing, мы подставляем дефолтное значение. Если у нас там значение, мы применяем к нему функцию и получаем. Такие есть, просто чтобы помнить. Дальше у нас есть функция RIDS.
То есть я наверху использовал функцию, которая позволяет прочитать строку и получить что-то и как бы распарсить её. А вот, соответственно, если оно не парсится, приводит к ошибке.
Подключим модуль стандартной библиотеки.
И он нам предоставляет классную функцию read. Кстати, интересно, а у тебя автоимпорта нету или ты не проверял в редакторе? Нет, не проверил. Не проверял. Так, May. Так, всё правильно. Аа, так. Вот у нас есть функция readm. Что оно делает? А оно применяется к строке, и оно, если оно парсится, возвращает may just значение, если не парсится, возвращает на как это может выглядеть? Сейчас мы напишем интерпретаторе. Это read may, которая считывает 3, возвращает int. А да, красивые символы, вроде type application, чтобы так много не печатать, я показывать не буду, чтобы не вводить лишний синтаксис. А, но в современном Хаскеле можно писать всё чуть-чуть приятнее. всё-таки напишу. А,
то есть можно написать read, работающий с типом int. Вот так вот. А вот если мы попробуем что-то такое распарсить, ничего не парсится. Вот. То есть у нас есть такая функция, которая возвращает ошибку. Вот. Отлично. А, соответственно, вот теперь мы хотим прочитать наш файлик. То есть у нас был read int from fil здесь мы или read мы хотели, чтобы оно по смыслу было. Вот у нас есть report pass Ишка, блин, уже сразу всё сказала. Вот.
Ну, кажется, мне не пришлось печатать, но мы сделали всё то же самое, да? Здесь, наверное, нужно поговорить про вот этот синтаксис. Грубо говоря, синтаксис позволяет просто упростить то, что мы пишем. То есть он создаёт блок, в котором появляется
такая штука, которая обозначает G
X и так далее. Угу. Ну, без привычки, конечно, сложно этот синтак воспринимать, хотя на уровне смыслов, на уровне смыслов он очень хорошо понятен. А я бы даже, знаешь, просто что предложил. То есть представьте, ребят, кто нас сейчас смотрит, слушает, вот этот гошный синтаксис проверки на ошибки. Айби это не совсем про ошибки, но в принципе идея похожая, да, что фактически вы делаете какую-то операцию, после этого ифом проверяете, а тут была ошибка или нет, и дальше, соответственно, если не было, делайте дальше. Вотду фактически за вас, ну, он не ошибку, а именно смотрит. Если был нан, она просто его протаскивает вниз, не вызывая, соответственно, функцию, правильно? И в конечном итоге у тебя просто уходят проверки. У тебя протаскивается значение в том случае, если только оно есть. То, что говори то, что ты говоришь - это правильно и хорошо. Но здесь написано не это. А чтобы написать всё и не вводить кучу всего, нам нужно написать немножко не так. Нам нужно сделать, чтобы у нас было type may t, например. Ну давай. И это было бы Т и О. А, то есть такая штука. И это, э, и это называется трансформер, а, который как раз, то есть, в чём у нас здесь проблема? У нас здесь есть io и есть may. Угу. И нам нужно, чтобы объединить, чтобы они работали вместе. То есть давай, например, сделаем другую штуку. Аа, сделаем бурепорт. Да-да. Да, то мы её, наверное, зря притащили, потому что у нас тут дополнительная проблематика появляется, да. у которого may be in, а и оно нам вернёт
пуре сум какой-нибудь сделаем. Вот. И если мы напишем такую штуку, ну или напишем лучше с дунтаксисом, то тогда будет всёвсё, что ты рассказал. Но как это работает? Для того, чтобы как это, э, как это работает, нам, чтобы показать, как это работает, нам нужно написать честно вот класс типов, который, аэ, как ту вот, вот так мы можем написать и будет ровно такое. Если мы это распишем вот в синтаксис, то будет как раз, как Ишка написала, это X mx y. Собственно, вот так. Это вот вот вот то, что наверху написано и то, что внизу написано, это ровно одно и то же. просто синтаксический сахар и преобразование на уровне лексера, кажется, которое преобразует вот вот то, что написано вот здесь в то, что написано вот здесь то, что то, что мы писали. Так и и вот если мы распишем то, как работает вот это вот связывание функции, как работает объединение контекстов, то мы увидим, что происходит ровно то, что ты описывал. Если мы вдруг оказываемся в случае ошибок, то эта ошибка убежит на самый верх. И таким образом, э, у нас практически во всех цепочках отсутствуют, ну, то есть проверки. Но тут, кстати, стоит показать, как бы выглядел код, если бы нам пришлось это ручками делать. У тебя бы кейс там возник. Это же кейсом или как это? Я вот забыл в Хаскеле, когда мы Да, да, если То есть, грубо говоря, для Maybe как работает вот мы там писали pipe may. Так, PE maybe. Вот сейчас мы его положим вниз вот сюда, чтобы нам его видеть, что это было бы. Вот, грубо говоря, вот это вот как раз определение операции. То есть, что мы делаем? Вот этот вот ду синтаксис ровно равен вот этому синтаксису. Это просто одно и то же, один в один. Никаких преобразований. Дальше, что такое вот эта вот история? Это вот то, что мы написали вверху. То есть мы пишем case mx of nothing, тогда возвращаем nothing. Если just, то тогда вот, собственно, вот это вот это, грубо говоря, благодаря ссылочной прозрачности мы можем вместо вот этой вот штуки написать её определение. Ну, то же самое, что PE Maybe, просто подставить. И вот мы всё заинлайнили и получили вот такой код. Вот. Ну, в итоге, короче, у нас, если делать это тупым, самым максимально таким способом, с ручным расписыванием, у нас получается паттерн матчинг с вот этой цепочкой вложенности на все уровни. Ну вот до самого конца. И как раз я про это и говорил, это будет выглядеть абсолютно так же, как обработка ошибок в GO, когда у тебя вот эта вот цепочка пошла. Да, понятно, что там можно всякое сделать, но но в принципе можно не делать эти проверки. Есть, правда, нюанс, да, когда у тебя возникают задачи, что посередине надо что-то воткнуть, и тогда у тебя цепочка немножко это сломается. Правильно я понимаю? Смотря, что именно воткнуть и какого типа. То есть, грубо говоря, что если мы расписываем просто Maybe, то да, и если IO Maybe, то да. Но если мы, например, делаем трансформер и, например, пишем maybo, то тогда, если нужно какое-нибудь аё или что-то воткнуть в середине, то мы сможем это сделать, потому что тот тип, который описывает контекст нашего вычисления, позволяет в него засунуть и то, что внутри, и то, что снаружи. Ну или если это сделано через эффекты, но это более сложная история. Знаешь, вот что всё это показывает, то, что мы сейчас делаем, конечно, насколько сильно тебе нужно думать о типах, о моделировании того, с чем ты работаешь, трансформеры, преобразование. То есть у тебя есть такая история, связана с тем, насколько типы помогают, да? Вот в большинстве языков у тебя типы находятся на каком-то уровне таком балансе, где можно говнокодить, но какие-то базовые вещи он проверяет. Хаскиль резко переходит это на такой уровень, при котором у тебя, ну, сильно больше надо об этом думать, потому что у тебя появляется вот эта вложность контекстов. Правильно, наверное, так говорить, да? Да, так можно говорить. Ну или об лучше говорить объединение контекста, потому что вложенность - это способ реализации объединения, один из способов реализации объединения конце. Да. И и у каждого из них есть определённые правила и способы интерпретации. Поэтому получается, что у тебя появляются такие вещи, как трансформеры. В общем, у тебя появляется довольно много кода обслужи и мыслей и моделей обслуживания всего этого добра, чтобы ты постоянно такой сидел и такой: "Ага, так типы, типы, типы, типы, типы". То есть люди, которые пишут на типизированных статических языках по сравнению схилистами, просто пишут на бестиповых языках, потому что так сильно о типах, наверное, они не думают, как нужно думать здесь именно из-за вот этой вот, я бы сказал, на многих мейнстрим языках, потому что есть типизированные языки, которые зашли гораздо дальше Хаскеле, и весь Хаскель можно считать типизированным по сравнению с ними. Ну, нормальные люди уже не знают об этих языках, да. Здесь самое главное, что когда мы думаем о типах, нужно думать, а то есть лично, что мне помогает. Не знаю, насколько это поможет всем, но мне удобно думать о смысле. То есть какой смысл несёт тот или иной тип, то или иное ограничение. И думать и какие свойства есть у той или иной структуры данных. И если думать на этом абстрактном уровне, то типы очень сильно помогают. То есть, грубо говоря, это частая история, когда фанаты нетипизированных языков говорят: "А вот эта штука может быть в одном контексте одним типом, в другом другим. Зачем нам вообще это выводить и так далее". А, соответственно, у меня немножко противоположный тейк о том, что если у нас, что любая вещь имеет тип, какой тип она имеет? Это то зависит от того, какие операции и как с ней работаешь. Соответственно, будь это тут, не знаю, какая-нибудь байтстрока или число, тебе нужно понимать, что ты с этим числом можешь делать, как ты сможем работать. И это определяет, какой тип. То есть, если ты сможешь объяснить, что это такое, что вот это не число, а это, не знаю, вещь, апельсин, а, сокет и так далее, то у этой вещи сразу же появляется тип, да, там появляются какие-то преобразования, хочется там в разных контекстах, что это одна и та же сущность в одном контексте работает, имеет одно тип, другом другой, но это всё выражается. Соответственно, как только это есть, то все ответы. Ну, на самом деле, что нам нужно думать, что трансформеры, не трансформеры, в общем-то, это набор паттернов, на самом деле, можно считать. И вроде бы вообще людей не сильно напрягает думать в терминах паттерна, в терминах вот мы это берём, это у меня, не знаю, там обсервер, это у меня прокси, а здесь у меня фабрика. И как-то всем нормально и отлично живётся в данном. И, в общем-то, не возникает вопрос. ни в какой момент. А что мне брать? Задача определяет, что мне брать и как с этим работать. Ну, Хаскель в этом смысле ничем не отличается. У нас есть задача, у задачи есть решение. Да, есть некоторые альтернативы, но зная ограничения, где что используется, можно выбрать из этих альтернатив. Ну или, как вариант, можно быть фанатом одной альтернативы и пользоваться ей, всё равно задачу решишь. Давай по поводу йби, поскольку мы так немножко поговорили, но до конца это не зафиксировали, зафиксируем несколько вещей. Я сейчас скажу, ты поправишь, да? То есть вот люди смотрят и думают: "Так, о'кей, хорошо, чем это отличается, допустим, от того, что мы возвращаем на а и просто, да, потому что по большому счёту, если вот мы смотрим на паттернматчинг, кажется, что точно такие же проверки". Ну, тут есть два момента. Первый момент, то, что он тебя всё-таки нал никто тебя не заставляет проверять, и если ты забыл, то получил проблему, то здесь, конечно, у тебя нету физической возможности просто пользоваться значением, потому что оно у тебя в контексте, который нужно уметь правильно обрабатывать, иначе просто не скомпилируется, поэтому ты уже получаешь это преимущество. Второе такое чисто удобное, это наличие нотации, которое при правильном вот этом подходе позволяет, собственно, ещё и ифы эти не писать на проверке, и у тебя получается последовательное красивое вычисление. Вот одна из самых таких классных вещей, которая по дефолту на уровне синтаксиса, по-моему, ни в один язык вот так вот не встроено. Ну вот из мейнстримовых, да. А при этом люди могут дальше возразить: "О'кей, с налом понятно, понятно преимущество, понятно, почему и как". Но если у тебя есть в языке, допустим, ты пишешь в котлине или в тайпскрипте, понятно, что проблема до нотации остаётся. Я имею в виду именно когда у тебя автоматической проверки. Набол всего лишь тебя требует проверить на этот if, но у тебя такая же длинная вложная цепочка получится, если, грубо говоря, тебе, ну, ты будешь это делать без вспомогательных инструментов. В этом ли только разница или в чём-то ещё? То есть типа потому что в современных языках всё-таки Монаду Mayбиты не внедряют. Внедряют простоable. А цепочку, ну, как бы никто не парится. Пишите и пишите. Никто хаски не видел. Цепочки пишут в целом. И в целом здесь можно, не знаю, взять какой-нибудь раст с с тем, что у него есть иable, там есть result, который аналог азера в чу. Это мы сейчас проговорим отдельно, да, я чуть-чуть его отложил. О'кей. Вот за что дают монады в Хаскеле и почему. И на самом деле это здесь, когда раз будем, затронем. Самое главное, что там это даёт? Мы вот сейчас я побольше сделаю интерпретатор. А мы можем подключить модуль contol монат и посмотреть там функции. Мы видим большую пачку функций, которые позволяют работать с любой монадой. То есть, если у нас есть штук вещь, которую для которой мы можем написать инстант класса монат, у нас получается огромная огромная библиотека. мы можем писать обобщённые функции, которые работают совсем. То есть, например, если нам нужно сделать какой-нибудь хитрый, не знаю, ретрай, который мы можем сделать, мы можем написать его в таких терминах, который будет работать для любого вычислительного контекста, который мы можем. И, соответственно, если у нас какие-то простые есть функции, типа там фильтрации, э-э продолжение, не продолжение, аэ, вычисления, мы это всё можем описать. И нам не нужно это реализовывать для каждого нового типа, для knowable, для result, для чего угодно. Оно у нас автоматом работает везде. И это большой плюс и большая крутая штука. И это очень сильно применяется. То есть, например, вот здесь был одно время такая модная история и модное направление, это комонада, например. Это, ну, не буду вдаваться, но, грубо говоря, это тоже математический концепт, который позволяет описать определённый ряд вычислений достаточно хорошо, но проблема в том, что для него не было такой же прикольной библиотеки и такого же прикольного использования, как для монат. В итоге его никто особо не использовал. И обычно это нишевая история, хотя достаточно интересная штука. И вот это, наверное, основной плюс, потому что это выводит абстракцию на новый уровень. И, собственно, почему хаскилисты говорят монады, монады, а не что-то более простое. Ну вот сейчас, наверное, до раста дойдём, да? То есть получается у нас, грубо говоря, берём раст там, ну, вообще, в принципе, современные языки, у тебя получается наable - это некая своя концепция, обработка ошибок своя концепция, у тебя нету никакой под ними общей базы, они просто тупо независимые элементы. Соответственно, вот всё, что ты сейчас показал, мы, конечно, не разбирали примеры, но, по крайней мере, людям теперь понятно, что Монада, мы сейчас это обобщим вообще, а что тогда такое Монада, да? Вот мы посмотрели на Mayби, а мы поработали с йо немножко с вот этим тоже историей, и получается, грубо говоря, она даёт просто больше возможности в этом смысле. Но Налбл сам по себе не является проблемой. То есть у него нету таких вот каких-то ограничений. Хорошая штука, работает, всё такое, но она довольно топорная, скажем так, да? То есть, если хочешь что-то поверх, будешь под неё конкретно это пилить. Давай теперь вот непосредственно, а что такое Монада? Вот, ну вот мы йби увидели. В чём в чём абстракция? Что их всех объединяет и что их делает монадами? Да вот что нам ответила Ишка. Да, не то, чтобы я с ней согласен, но да, наверное, это будет правильно, но, грубо говоря, это структура, это действительно структура данных и подход, который позволяет работать с различными вычислительными контекстами, позволяющими описать побочные эффекты. Если мы хотим зайти глубже и говорить, что такое монада, а не аппликативный функ, то это такая структура, которая позволяет создать зависимость по данным при работе с побочными эффектами. Грубо говоря, когда то, что мы сделаем дальше, какой эффект будет дальше, зависит от результата выполнения предыдущего эффекта. Сложно. И, наверное, мы это не дело не сложно. У меня сомнение, знаешь какое? А mayби, например, вот у тебя, допустим, функция, давай так. У тебя функция, которая ищет позицию э какого-то символа в строке и не находит. В обычных языках возвращает мину1. Допустим, это сделали монадой, которая возвращает либо позицию, либо nothing. Логично же. Я, кстати, не знаю, как в хаскеле реализовано. Давай посмотрю. А find вот, кстати, интересно действительно, потому что минус один это было бы не не оно там maybe возвращает mayби. Да, да, да. Просто тут-то побочного эффекта нет, получается. Во, find IN, скорее всего. Да, да, finde да, он берёт список, возвращает Maybe in. Естественно, с побочными эффектами история достаточно интересная. То есть здесь хорошо рассматривать контекст извне, если мы смотрим на findкс. То есть в чём важно, что важно, что Monada - это определённый это интерфейс для из работы с эффектами определённым способом. То есть, грубо говоря, если мы пишем какую-нибудь функцию, я не знаю, а, read by index, вот я хочу написать такую штуку, но мне сразу optional предлагает. Вот научился в нашем коде, да? А значит, сделаем finde мы сделали. findкс равняется а я не не какую-то чушь делаю, но пусть будет. Оно, ну, в смысле, оно не имеет физического смысла. Так, делаем вот так вот read. Мы здесь делаемстринг. Вот. То есть мы А блин, пла плохая. Я ни хрена не понял. Для меня слишком много символов. Ну вот, я надеюсь, ты объяснишь. А вот всё сейчас вот вот вот мы у нас у нас будет какой-нибудь предикат, естественно, можно так не делать. Это не лучший способ. В общем, у нас есть предикат некоторый. А, ну у тебя фильтр почти получился, да? Да. То есть это, да, даже я read -э будем в стиле этих модных книжек, про которым ты делаешь эти, делать длинные и глупые названия. Никогда в жизни так не делайте, пожалуйста. read first matching value read, а pars. Вот pars first matching value. Название абсолютно точно говорит, что мы делаем, но такую функцию делать не надо, потому что вот у нас есть некоторый предикат, у нас есть список, мы находим индекс в списке этого веща, а потом мы, а, делаем, считываем. Так, давай считываем её и вызываем функцию read maybe. Вот красота. Вот. А давай проверим, компилируется или должно компилироваться. А findкс мы не добавили. List find
так read may read нужно. Так, будем себе усложнять жизнь, не будем себе услонять жизнь. Будем
maybe in, потому что, ну, ну, чтобы не писать дополнительные штуки и не усложнять себе сейчас жизнь. А вот, соответственно, в данном случае тип maybe in - это обычный тип, обычная структура. Если мы смотрим на на него извне, нам неважно, мана это или нет. Но когда мы внутри делаем вычисление, мы работаем с ним как с монадой. То есть у нас есть монадические действия. У нас вот эта вот штука, она у нас типа may in.
И мы берём следующее вычисление, которое принимает у нас здесь. Эта штука у нас типа in, то есть мы вот эта вот штука у нас may in, и мы должны его передать. То есть у нас здесь происходит монодическое связывание. То есть мы используем вот эти вот монодические свойства и правила may для того, чтобы внутри сделать связывание. То есть у нас вот это вот всё штука, мы работаем. Вот это вот int, значение типа in. Вот здесь, если бы мы вот так вот посмотрели, это бы было тоже типа int. То есть мы работаем внутри контекста Maybe. Что? А что такое контекст Maybe? Это контекст вычисления, который может в любой момент сказать: "Извините, я кончилась". Ну и оно спокойно дойдёт, спокойно дойдёт до конца, да? Я, ну, и дальше, если оно закончилось, оно закончится до конца. То есть на самом деле там даже ничего вычисляться не будет по тому, как устроена вот эта вот штука, потому что мы окажемся в ветке, то есть мы видим NAN, и дальше мы уже вот здесь ничего не вызываем, мы просто говорим: "Всё, извините, всё закончи". Ну, то есть в голове можно же это как вот эту цепочку кейсов паттермачингов представлять, то есть, да? То есть монада - это и давай мы ещё какую-нибудь более страшную штуку сделаем. Мы сделаем if, even и dx. Если у нас индекс, если у нас индекс будет чётным, то мы вызравлящаем ничего, а иначе мы его интерпретируем. Норм общая история. То есть, чтобы показать, что мы получили здесь индекс, а то, что мы делаем дальше, оно вообще зависит от того, что мы здесь получили. То есть у нас получается цепочка вычислений с эффектом, который зависит от того, что мы получали раньше. И вот это вот монады нужны для того, чтобы описывать вот это вот. То есть, когда мы находимся внутри, мы говорим монада. Когда мы находимся снаружи, выражение монада не имеет никакого смысла, если мы если кто-то говорит, что для того, чтобы сказать, если у нас индекс в списке, мы возвращаем, мы воспользуемся монадой Maybe и вернём её, это не имеет не надо так говорить. А, ну слушай, у тебя действительно получается снаружи для тебя это, ну, реально можно считать как аналог возврата NAV. Ты просто кейс по нему делаешь, типа, если значение есть, ну, что-то делаем дальше, значения нет, ну, не знаю, завершаем программу, грубо говоря, да? То есть мы не воспринимаем это как что-то особенное. Ну, просто вот у тебя есть вариант Да, это то, что я с самого начала говорил. Монада и всё, что происходит - это не что-то особенное. Это обычная структура данных. Единственное, мы иногда хотим запрещать пользователю заглянуть в неё руками. и разрешить в неё заглянуть только через специальную обученную функцию, которая будет учитывать какие-то дополнительные инварианты, которые мы должны сохранять. Угу. Тут вспомнили про privциональными
языками, когда люди любят говорить, что типа вот смотрите, это так важно, инкапсуляция. Я говорю, я всегда привожу пример хаски, говорю, вы в принципе там не можете обратиться внутрь без функции. То есть вам всегда только так и надо писать. Видишь, ещё и ограничений сильно больше может быть. А слушай, тогда мы, может, закон это назовём? Ё-моё, давай так. Если просто человеку, как вот он ответит, скажет, что такое, что такое Монада, он такой скажет: "Ну, это определённый интерфейс, правила работы с ним". Это я расскажу подробнее, почему, как и откуда они берутся, да? Есть дополнительная история, что для того, чтобы всё работало, а, и вообще классы типов, ну, вот вот вот то, что класс в Хаскеле - это обычно называется класс типов, это хок полиморфизм. И обычно хаскилисты очень любят, чтобы все классы, которые используются, были сопровождены бы какими-то законами, а которые определяют правила и правила, по которым должны работать методы внутри и которые вносят дополнительные ограничения. Не прямо обязательное требование, но я ему более-менее симпатизирую. Это позволяет написать сразу хорошие тесты, а что-то проверять и компилятору жить проще. Вот. И обычно это нужно для того, чтобы то, что мы пишем, было sound, как это по-русски называется, имело смысл. Вот. А так, какие законы должны выполняться для монады? Отлично, Иишка ответила. Всё. Вот. Соответственно, это всё нужно для того, чтобы то, что мы пишем, имело смысл, и мы не могли бы сделать какое-нибудь неправильное преобразование или нарушить поведение. Собственно, все эти законы, они сводятся к тому, чтобы у нас выполнялись принципы ссылочной целостности. Вот, собственно, так. Можем по ним подробнее пройтись, но если честно, я рекомендую здесь больше курс Москвина. Я могу на паре слов сказать, да. Единственно, правильно ли, если я так скажу, а вообще необходимость их ведения заключается в том, что система типов не способна это сама проконтролировать. То есть, если бы был простой способ это проверять, никто бы не заставлял разработчика об этом думать специально. Ровно так или так? Да. Да, естественно, это правильное утверждение. Есть языки, где это проверять можно, но хаски близко не там. Собственно, там писать на этих языках вообще невозможно, я так подозреваю. Да, не можно, но требует определённой сноровки. Вот. И законы эти нужны для того, чтобы все функции, которые являются в стандартной библиотеке, работали бы логично и компилятор тоже мог бы делать различные преобразования. Классический пример. Является ли Промис в Джесе Монадой? Промес, кстати, не является, потому что там у меня было большое общение с чатом ГПТ на эту тему, когда я готовился. Оно было, к сожалению, слишком давно, чтобы я всё всё всё повторил. Промис нет, а Таск является. Вот что мне сказал чат ГПТ, я ему поверил. А насколько та это из котлина или или cшаp это в тайпскрипте. Вот я я что-то не знаю про Таска в тайпскрипте, да, мне кажется, может быть, он C#ARP имеет в виду, потому что я подозреваю, что там такой есть. А, да, я тоже много раз про это слышал. И более того, тоже у нас были дискуссии, но это много лет назад о том, что Промес не является, но он напоминает и у тебя образ мышления похожий, да, у тебя есть ден, ден и кетч, и, соответственно, он тебя протаскивает через эту штуку. В кеч ты попадёшь в конце, а, Дн, если только всё хорошо. Но подозреваю, ладно, я чувствую, сеча у меня мозг сейчас не способен будет быстро это воспроизвести. Там, скорее всего, не со всеми законами, особенно с этими с идентити у нас сейчас, что заворачивание. Если мы завернём промис и пере и передадим его дальше, то это то же самое, что мы вызовем функцию на то над тем, что передано в промис. Вот, ну, например, то есть, что мы здесь говорим? То есть мы создали промис, мы вызвали какую-то функцию внутри промиса, которая возвращает следующий промис. И это то же самое, что мы просто запустили промис. Кстати, я сейчас ещё вспомнил, там вся эта история при всей своей красоте и классности рождает, как вот раньше у нас были проблемы, там в Айо попадает Монадаби какая-нибудь, да, что у тебя, если естьби, который упо, ну, то есть у тебя появляется Mayби внутри, Mayби, там тоже начинается свистопляска с тем, чтобы это распаковать, правильно? Ну, здесь на самом деле вот вот на самом деле в математических в статьях изначальных, которых было, была ровно такая история. То есть на самом деле там был не вот этот вот банд, так называемый, похожий на пайп или что-нибудь, а там была как раз штука, которая позволяла разворачивать а двойной уровень, двойное вложение монаты. На самом деле, если идти с математических записей, то базовой структурой является именно функция join, которая позволяет Maybe Maybe превратить просто в may. Ну и, соответственно, айёа превратить просто вё. А вот на самом деле с ним всё хорошо здесь. Но эта штука лично в моём опыте пугала меня гораздо сильнее. Вот это вот всё. Вот это да. Ну и давай, наверное, закончим. Я знаешь, кстати, по пути понял, что мы с тобой про классы типов не поговорили. Это, по идее, надо было в прошлый раз ещё разговаривать. Мы так, знаешь, про них говорим, как будто люди про это знают. А, наверное, не будем. Ээ, тут надо почитать, э, на эту тему, потому что уже как бы будет нелогично возвращаться к этому назад, но это тоже очень важная часть концепции языка. А давай сейчас тогда поговорим про Монаду Айвер просто и про раз немножко, и про обработку ошибок в языках. Вообще в целом неплохая тема. Я думаю, сейчас за 10 минуток мы с тобой как раз управимся и примерно в двухчасовой наш, соответственно, вот наша история с Айзером. Выглядит подозрительно похоже на растовый resultт.
Вот, собственно, примерно этим же является тоже структура данных, для которой можно здесь реализовать все те же самые инстансы, которые у нас есть. Что это функция? То есть мы позволяет нам обычное обычную функцию в случае, если у нас всё хорошо, мы продолжаем вычисление, соответственно, продолжить вычисление с этой функции завернуть, если всё плохо, то вернуть ошибку. Ну и, соответственно, также продолжить. Собственно, это позволяет, а, выразить на уровне типов ошибки и, соответственно, позволяет сделать так, что компилятор заставит нас работать с этой структурой данных, проверить ошибку, ну или вернуть её наверх. Ну и, соответственно, позволяет точно также писать наши выражения в очень не проверяя ошибки постоянно. Да. Да. Не проверяя ошибки постоянно, позволяя перезаворачивать ошибки. Да, это приносит некоторый runтай overhead, потому что, ну, это куча локаций, но это то, что есть. Но в Хаскиле это быстро. Кстати, интересно, я сейчас знаешь что подумал? Вот Авер изначально он же leftра right. Я уверен, что когда он появлялся, его явно не для ошибок делали. Это просто была обобщённая история, что, ну, типа одно второе значение, да? Оно было, оно было во многом для ошибок. Для ошибок. А, ну смысле, оно сразу оно сразу там использовалось. Если вспоминать первые статьи, которые появлялись, то это был один из примеров. И как раз пример бы, ну, может, думали, что может быть шире будет использование, типа мало ли что там бывает, да? Потому что в Расте же вон конкретизировали. Так, хорош, нам не нужно общие два значения, давайте. Ну, правда, не два значения, в смысле, либо одно, либо второе, да, то они такие, давайте это таким образом ошибки обрабатывать, да, на самом деле в это напрягает в Хаскеле чи частенько. Вот. Ну, там это одновременно появлялось с тем, что у нас эти типы суммов, типы произведения и, ну, соответственно, параметры или разные варианты. А вот и, соответственно, тоже как понятный пример. А общий, кстати, наверное, и вправде прав. И оно раньше появилось, и потом просто придумали его использовать уже готовую структуру для описания эффекта. У меня вот про эту штуку сразу есть такая история. Если мы говорим проable, то там, очевидно, optional всякие это в языки встраивается, да, и это живёт своей некой жизнью очень базовой, то вот эта вещь, она, э, такая немножко специфическая. Почему? Потому что не можем просто так воткнуть её. Опять же, была история в каждом языке, я уверен, вы поищете, найдёте либы, которые это дают, люди радостно бегут это использовать, а потом оказывается, что он ни с чем не дружит. У тебя тут половина эксепшенах, там библиотечки, всё не так работает. И поэтому, например, если в Расти все счастливы и даже, может, не до конца понимают базис, который за этим стоит, я не в курсе, но мне кажется, что точно это надо дополнительно копать, да, чтобы понять, о чём идёт речь. Но вот я могу для про себя сказать. Мы, например, эту штуку используем явно в одном месте, где это просто фантастически классно. А это в сервисах. То есть у нас сервис всегда возвращают result для унификации. А, и поскольку они ни с чем не взаимодействуют, кроме твоего кода, то есть у тебя есть, ну, допустим, контроллер или там Джоба какая-нибудь, короче, любое место, где он вызывается, это исключительно твой код. Оно никуда дальше обычно не течёт и ни с чем больше не взаимодействует. И поэтому резал, а, фактически мы в рубях через вот этот сорбет, который типизацию добавляет, мы возвращаем, ну, почти то же самое. Оно там отличается микроскопически, но фактически, да, получается вот то же самое. И я прямо скажу вам, работает это очень круто, очень классно. А из там внутри, как как правило, контейнер, потому что не одно значение, то есть там это набор значений. Ну и, соответственно, вы всегда легко посмотрите, обработаете ошибку, и там мы тоже такой там типа кейс делаем, чтобы у тебя матчинг по типу был. Вот. Очень классная штука. Максимально рекомендую, если вы не пишете даже на расте, подумать в эту сторону, потому что все всё остальное рукопашное, ну, там будет проблемно с этим. Хотя цепочки редко при этом встречаются, потому что поскольку у тебя сервис, сервисы же так вот не вызываются обычно большим набором, да? То есть вот использование в функциях в обычных, когда ты это делаешь в том же Хаскеле, вот мы как с Mayби смотрели, да, ты можешь написать довольно много кода, и будет выглядеть, что ты не обрабатываешь ошибки, а на самом деле они, ну, автоматически прокатываются вниз. А ты не думал об этом? Вот я регулярно вспоминаю Го с этой проблемой. Я уверен, там все тыкают ему в разст и говорят: "Смотрите, вот же как они сделали". И в принципе с самого начала было понятно, что можно же делать вот так. Ты не общался с ребятами на эту тему, не смотрел ничего? Что они планируют сделать? Мне вот интересно. Я слышал о введении этих алгебраических типов данных там и и это уже позволит что-нибудь красивое написать. Но, если честно, конкретики я не видел. Вот пока все, с кем я общался, тщательно защищали текущий гошный подход. Что мне кажется крайне странным, но уже это уже будет в топиках сейчас. Ну, кстати, глядя вот сейчас на это, они действительно могут сказать: "Ребята, ну это какой-то просток". Но с другой стороны, мэджик бывает разный. Вот бывает рубишный мэджик, когда просто реально надо знать или ты не воспользуешься. По другое дело, у тебя это просто абстракция и которая очень хорошо типизирована, и она тебе просто не позволит сделать фигню, да, здесь или обойти что-то, да, здесь са самое классное это что здесь есть, что нас заставляет делать паттерн матчинг. И более того, мы не можем, как обычно, не вернуть что-то невозможное. Ну, то есть попасть в невозможную ситуацию. То есть, грубо говоря, если мы возвращаем пару, то значит у нас и каждый элемент нала у нас, возможно, здесь полный вариант, мы можем вернуть оба нала. Мы можем вернуть оба значения. Мы можем вернуть нал значение в любом порядке. То есть у нас, грубо говоря, четыре варианта. Но при этом из них, а если у нас первая - это ошибка, то тогда у нас, если у нас мы возвращаем ошибку, то второй должен быть нал по-хорошему. Ох. Ну что, Саш, тебе большое спасибо. Мы с тобой просидели примерно 2 часа, глубоко копнули, а при этом я подозреваю, что всё равно это ещё сильно не кишки Хаскеля в плане возможности всего остального. Ребят, я знаю, что было, наверное, даже тяжеловато местами. Надеюсь, что мы смогли как бы своими вопросами, уточнениями что-то вам объяснить, и вы лучше это поняли. Будет очень интересно, если вы расскажете, а что-то из этого, применяете ли вы в своих языках, потому что в Расти, понятно, у них из коробки есть. В остальных языках, кроме Хаскеля из массовых, там, как правило, элементы какие-то пихают, об этом, думают, какие-то библиотеки используют. Короче, напишите, если у вас есть какой-то такой практически успешный опыт использования того, что мы сегодня обсуждали, или, возможно, вас натолкнуло на наш разговор, на то, где вы могли бы это использовать, где бы вам это упростило вычисление. Ну, и вообще то, как вы строите свою программу. Как обычно, ставьте лайки, подписывайтесь на канал, следите за нами в Твиттере, следите за нами в У меня Telegram-канал есть. А у тебя есть Telegram-канал, Саша? Нет, сделай обязательно Telegram-канал, будем там рассказывать. Нет. Ладно, всем спасибо, до новых встреч. Пока. Спасибо, что пригласил. Всем пока.
เฮ