Марк Шевченко — Воркшоп «Практические задачи решаем функционально» (Часть 1)

Информация о загрузке и деталях видео Марк Шевченко — Воркшоп «Практические задачи решаем функционально» (Часть 1)
Автор:
DotNext — конференция для .NET‑разработчиковДата публикации:
05.04.2024Просмотров:
1.2KОписание:
Транскрибация видео
Друзья, всем привет.
Мы начинаем, и я должен признаться.
Возможно, после этого признания меня выгонят из этого зала, но я не всегда пишу тесты для своего кода.
К счастью, у нас на сцене присутствует спикер, который сможет меня переубедить, наставить на путь истинный.
Встречайте, человек с огромным опытом в разработке, организатор встреч Московского клуба программистов Марк Шевченко.
Спасибо большое.
Да, постараюсь немного про тест переубедить при докладе.
Обратите внимание, что воркшоп Марка состоит из двух частей.
Обязательно приходите к нам на вторую часть доклада после перерыва.
Также готовьте ваши вопросы.
Их вы сможете задать спикеру в дискуссионной зоне именно после второй части.
Марк, прежде чем мы начали, то, что тест надо писать, это понятно?
Особо просветленные личности утверждают, что их надо писать еще и до того, как ты сделал саму реализацию.
Как поступаешь лично ты?
Ты пишешь их до, после, во время?
Вот очень хороший вопрос.
Да, писать надо до, но в процессе работы получается иногда так, что ты чуть-чуть вперед код написал, пока еще тестов нет, и тесты приходится дописывать.
Либо иногда ты пишешь несколько тестов, а потом код.
И мы это сегодня увидим, но я покажу.
Отлично, Марк, хорошего доклада.
Спасибо большое, спасибо.
Здравствуйте, я сейчас устроюсь здесь.
Дело в том, что это воркшоп, не просто доклад, поэтому мне нужно будет сидеть и кое-что делать.
Вы тоже можете открывать ноутбуки.
Я постарался, я понимаю, что набирать текст будет неудобно, поэтому будет возможность копипаста.
Я покажу страницу, где будет...
Текст, его можно будет скачивать.
Ну, копипастить в задачи.
То есть, если вы с ноутбуком, у вас есть Visual Studio или Rider, можно делать.
Так.
Начало небольшое введение.
Да, Филипп меня представил.
Зовут меня Марк Шевченко.
Программист с большим опытом.
Писал на сотни разных языков там.
Ну, ладно, на нескольких десятках.
В последние годы пишу на C-Sharp.
И вот на F-Sharp тоже какое-то время.
Здесь поэтому...
к QR-коду несколько ссылок есть, сейчас вам покажу, что это.
Вот все, на что я буду ссылаться, это вот здесь на этой странице перечислено, вы потом по QR-коду можете попасть и можете, ну даже сейчас открыть, я буду говорить, вы можете заходить и проверять.
Так, ну это введение немножко себя, а теперь как бы о чем мы будем сегодня разговаривать.
История из жизни у меня была связана с какой-то моментом.
У нас офис, где я работал тогда, мы стали переходить на gRPC.
Ну а до этого писали REST API backend.
Ну в REST API все у нас просто.
То есть если мы хотим протестировать, ну мы написали код.
Хорошо, ладно, у нас внутренние тесты.
Но вот мы хотим убедиться, что все работает.
Какие у нас есть возможности?
У нас есть Swagger, у нас есть Postman, у нас есть CURL в конце концов.
То есть мы можем с помощью каких-то разных вещей вот это все сделать.
GRPC мы сделали, например, ну какой-то endpoint, а как его тестировать?
Совсем непонятно.
Значит, если это REST API, мы готовим какой-нибудь, ну, предположим, да, у нас сервис какой-то, который вычисляет расстояние на земной поверхности.
Вы ему передаете координаты двух точек, он вам возвращает расстояние.
Значит, здесь первая точка — это Москва, вторая — Санкт-Петербург, расстояние должно быть где-то 634 километра.
Вот, мы передаем такой JSON, у нас сервис выполняет, присылает нам ответ.
Ну, как-то вот мы, например, с помощью CURL, как можем сделать, ну, какой-то у нас сервис есть, мы отправляем этот JSON, нам приходит результат.
Вот, 634 километра и с какой-то точностью, с какой-то дикой точностью.
Хорошо.
То же самое, как дело в gRPC.
В общем, тут история, значит, про что такое gRPC.
Это история про успешного успеха, про компанию Google.
Когда мы пришли, стали жить в 21 веке, нам сказали, вы знаете, бинарные данные, это уже все прошлый век, давайте все писать в JSON-ах, передавать.
Это очень легко читается.
Но то, что это, ну, немного больше места занимает, да кого это волнует в наше время быстрого интернета?
А то, что там приходится сервизовать, десервизовать, ну, тоже не так страшно.
У нас быстрые компьютеры.
И это, в принципе, правда так, но это так, пока мы не компания Google, потому что Google, там, ну, условно, миллиарды запросов в час или в минуту, как-то так.
И для них вот эти сервизации, это правда, значит, какие-то, может быть, сотни, тысячи даже компьютеров, которые будут что-то, может быть, другое, более полезное делать.
Поэтому они придумали HTTP2 и они придумали вот этот алгоритм бинарной стерилизации и десерилизации.
И чем мы будем заниматься сейчас?
Мы хотим тестировать GRPC.
У нас есть CRL, например, утилита CRL.
Что такое gRPC?
У нас описывается схема взаимодействия.
То есть мы описываем модель, что мы будем отсылать.
Distance request состоит из двух точек, from и to.
И, возможно, опционального метода вычисления.
Там есть две основные формулы.
Это по формуле косинусов и по формуле гаверсинусов.
Предположим, мы это все даем возможность.
Затем у нас есть еще одна...
Структура тоже называется message.
Это point, это ширина, широта, долгота.
И в конце концов enumerable, перечисления, методы вычисления.
Это первое, что у нас есть для стерилизации.
Второе, это текст запроса.
Но правда, мы хотим отправить бинарные данные, но нам же нужно сначала их как-то описать.
Есть язык тоже, он описан у гугла.
Как это можно делается?
Вот такая структура данных, она немножко похожа на JSON, но это не JSON, чуть отличается.
Мы серилизуем, у нас получаются бинарные данные, мы передаем их в URL на вход, он отправляет, нам приходят бинарные данные, мы их тоже десерилизовать должны.
У нас есть такое же точно...
такая же схема, и приходит ответ, мы его диссерилизуем, у нас вот что-то такое должно получиться в конце концов в текстовом формате.
Это вот то, чем мы будем заниматься.
У нас будет такая утилита, и она правда, ну как бы возникла необходимость в ней практически.
То есть когда я с этим столкнулся, я думаю, а как это все делать?
И потом я узнал, что да, есть уже готовые инструменты, но мне стало интересно, насколько это вообще можно сделать быстро.
На функциональных языках кажется, что быстро.
Так, значит, и вот сейчас, если кто будет участвовать в живом кодинге, кто хочет у себя все это проверять, пожалуйста, заходите сюда.
Это проект, я его сделал, в принципе, вот он живой.
Нам там потребуется один файл.
Я подержу немножко, чтобы вы могли там вбить.
Вы можете пройти по ссылке, там одна из ссылок была, это исходный код проекта.
Где он?
Вот он.
Здесь это, собственно, весь проект.
Тут есть сама утилита, она называется Protos.
Это значит Protobuf сервизатор.
Сервер – это приложение тестовое, которое вычисляет расстояние.
Я его просто написал, чтобы можно было…
тестировать свой сервизатор.
Правильно, там есть REST, вход-выход, но мы этим сегодня не будем заморачиваться.
Нам нужен файл, который называется .nextmd.
Мы его открываем, потому что здесь у нас будет описание процесса, что нужно делать.
Кусочки хода для копипасты и прочее.
Я на английском написал, не знаю почему, мне казалось, что это может быть кто-нибудь из мирового сообщества в этот проект залезет, но...
Черт его знает.
В общем, придется переводить, если вдруг кто не знает.
То, что нам нужно сделать, это создать новое консольное приложение.
Сейчас просто начнем делать.
Вот я запустил у себя райдер.
Это будет новый solution.
Пусть он называется так.
.next demo.
Что это должно быть?
Ага, это должно быть консольное приложение.
Вот у меня стоит седьмой SDK, F-sharp язык, .NET 7.
Все нормально.
С шестым тоже все нормально будет работать.
Значит, хорошо ли видно шрифт?
Я буду переключаться в режим презентации, но иногда мне придется... Так, это он старые файлы показывает.
У меня на самом деле это... Глюки.
Большие глюки райдера.
Я просто прогонял несколько раз доклад, и у меня вот этот файл я уже создавал несколько раз.
Так, значит, и тестовый проект мы тоже создаем.
Мы его назовем protos.tests.
Значит, это SDK, это xunit fsharp.net7.
Он нам здесь добавил файл, который называется tests.fs, но он нам не нужен.
Значит, вот это наша исходная история.
Два проекта.
Основной и тесты к нему.
И мы сейчас тогда... Да, нужно добавить... Нам, поскольку придется парсить текст, мы добавим сюда зависимости этот f-парсик.
Набираем.
Версия 1.1.1 работающая.
Добавляем в оба проекта, там в обоих это все пригодится.
Дальше что?
Ага, и добавить референс на ссылочку.
Тестовый проект должен смотреть на основной проект, чтобы видеть эти все методы и чтобы их можно было тестировать.
Ага, ассет.
Ну все.
Возвращаемся в презентацию.
Итак, значит, вот мы сами запустили, сделали проект, и теперь давайте смотреть, что у нас...
с f-парсиком, зачем мы его взяли.
Значит, у нас одна из задач, это у нас есть текстовое описание схемы и текстовое описание данных.
И мы хотим что-то с ними сделать, как-то их превратить в какую-то внутреннюю структуру для того, чтобы потом можно было сервизовать и обратно десервизовать.
Вот есть такая теория парсер-комбинатора.
Мы на практике сейчас это все попробуем.
В Fsharp есть готовая популярная известная библиотека fparts, которая для этого используется.
Мы его сейчас добавили.
И сейчас мы немножко просто про это поговорим, чтобы было видение, чтобы понятно было, что и как устроено.
Что такое парсеры в функциональных языках, в функциональном подходе?
В функциональном подходе все функция.
Значит, парсер — это тоже функция.
На вход она получает какую-то последовательность.
То есть это не секрет, что функции могут работать не с единичными какими-то числами, а получать на вход что-то.
Массивы, строки, списки.
Здесь в каком-то виде у нас поток символов, что-то такое.
Парсер, например, pfloat, это парсер, готовый из библиотеки в парсик, он умеет разбирать, опознавать и собирать числа с плавочной запятой.
Если ему подать на вход такой поток, то он его прочитает, все символы, которые являются частью числа, и вернет это число, то есть 3,14, 15 и так далее.
А в потоке у нас останется все, что не прочитано, начиная с запятой, то, что там, где у нас закончилось число.
Но есть парсер, опять-таки, identifier в парсике.
Он умеет читать идентификатора.
Если мы подадим ему такой поток, он вернет нам ошибку.
потому что, ну, должно начинаться идентификатор либо с подчеркивания, либо с буквы.
В данном случае он ничего не прочитает, вернет ошибку, и у нас поток исходный останется нетронутым.
То есть это парсер, это такая функция, она возвращает какой-то составной объект, либо успех, либо ошибка, на вход у нее поток, и она этот поток может поменять.
Тут вот варианты разные, но в...
Не будем углубляться.
В каком-то случае она может вернуть на это новое состояние потока.
И давайте посмотрим, какие у нас вещи есть, на что нужно обратить внимание, что у парсеров есть результат.
То есть то, что он прочитал, он потом нам возвращает.
Например, есть парсер, который называется Digit.
Или Digit, как правильно.
Он умеет читать один символ, и этот символ должен быть цифрой.
Если он его прочитал, он возвращает эту цифру как символ, не как число.
То есть это у нас единичка будет в апострофах.
Следующий это у нас такой составной парсер.
Many one cares это такая функция в парсике есть.
Ей на вход подается тоже какой-то парсер символный.
Она его вызывает, пока он выполняется успешно, и все символы, которые он прочитал, она складывает в строку.
То есть если там в начале потока есть цифры
100 и 500, то она прям прочитает вот так вот.
Но нам часто это вот второй, у нас результатов этого парсера, то что слева от стрелочки, это такой комбинированный парсер, а справа результаты этого парсера.
И нам часто нужен же не просто строку из символов, мы хотим это привести в число и с числом что-то потом делать.
И есть для этого тоже подходящий механизм.
Это вот такой оператор, состоящий из вертикальной черты и двух знаков больше.
Такая стрелочка своеобразная.
Этот оператор тоже определён внутри F-партика.
В F-Sharp во многих функциональных языках можно свои операторы определять.
Это те же самые функции, но просто более удобные для вызова.
Обычно не рекомендуют этим злоупотреблять, потому что тот, кто придет читать ваш код, он не знает, что вы там определяли в операторах, и это может выглядеть очень страшно.
Но если речь идет о устоявшихся библиотеках, там, Ho-Pack, Актеры или...
то да, эти операторы можно запомнить и вот как бы ими пользоваться.
Функция преобразования это int32, она встроена в fsharp.
Что происходит здесь?
У нас получается партер, у которого есть результат строка, мы вызываем функцию преобразования, она берет этот результат, к нему преобразует int32 и получается то же самое 100500, но уже как число.
Здесь еще один момент.
Вот то, что я показываю слева от стрелок, это такие тоже более сложные парсеры.
Это вот и есть те самые парсер-комбинаторы.
Что это вообще такое?
У нас есть простые, скажем, парсеры, а сама библиотека, смысл ее работы заключается в том, что мы берем из этих простых парсеров, не очень сложно, делаем более сложные, которые умеют распознавать такие довольно большие конструкции.
И мы с этим сегодня будем как раз знакомиться.
Предположим, у нас есть парсер, который называется digits.
У него тип parser in 32 unit.
И вот он тот, который мы с вами описали.
То есть он читает все символы не меньше одного.
Many one cares он должен прочитать минимум один символ.
Тогда он сработает.
Он считает несколько цифр и преобразует их в целое число в результате.
Вот парсер int32.unit — это тип функции.
Что такое unit?
Про это я вам показываю.
Вы на это просто не... Ой, куда я скакнул?
Хотел выделить.
На это не смотрите.
Там библиотека сложная.
У нее есть работа с состояниями.
То есть вы можете читать поток и еще какое-то состояние держать.
Но нам это не потребуется.
У нас вот этого состояния не будет.
Мы просто будем использовать unit.
Это некий аналог void из C-sharp.
То есть это что-то, какой-то тип, который...
имеет одно единственное значение, он просто показывает, если мы ничего не хотим возвращать, то это вот unit должен быть.
И это первый пример, как бы вот парсер комбинатора, а вот чуть ниже, чуть более сложный пример, посмотрите.
Pint32 это встроенный комбинатор в парсик, ну там он по-своему определен, но предположим, что мы его можем, хотим определить сами.
Он умеет делать то же самое, что и
Но он умеет ещё отрицательные числа распознавать.
Вот что здесь у нас получается.
Это просто я рассказываю, как устроены вот эти вот парсер-комбинаторы, как они строятся.
У нас есть левая часть и правая часть.
Они разделены вот этим вот красным символом.
Это операция или, это вертикальная черта, вокруг нее угловые скобки.
Это тоже парсер-комбинатор из f-парсика.
Значит, у нас или слева должен сработать, или справа должен сработать парсер.
Один из них.
Если он сработает, то результат будет...
Справа у нас просто цифры, значит будет целое число.
А что слева?
Слева более сложная конструкция.
Мы должны прочитать символ, дефис.
HR это и дальше дефис, ну то есть минус.
Затем у нас есть странная конструкция, две стрелки, точка.
Но по сути она означает, что мы сразу за дефисом должны прочитать цифры.
И вот из этих, это будет два парсера, нам результат левого не нужен.
То есть то, что дефис слева у нас, нам вообще не нужно.
Мы его отбросим.
Поэтому у нас стоит точка справа от стрелочек.
Но это мы еще чуть позже обсудим.
В любом случае, здесь получается, что результатом вот этого партера будет как раз число без знака «минус».
И что мы делаем?
Мы еще вызываем к ней функцию, которая его превращает в отрицательное.
Она записана очень странно.
В скобках таких это фиолетовым цветом «тильда» и «минус».
Но это такой способ в F-sharp вызвать функцию, которая оператор.
Если это унарный оператор, мы должны поставить тильду.
В общем, таким образом, если мы сделаем, у нас получится вот парсер-комбинатор, который сможет читать и положительные числа, и отрицательные.
И мы теперь готовы переходить к настоящему разбору.
Значит, про разбор.
У нас есть два файла в текстовой.
Proto – это описание схемы, и текст Proto – описание данных.
Сейчас мы поговорим немножко про схему.
Здесь сразу ссылочка на спецификацию, и сейчас попробуем вспомнить.
Нет, это encoding.
Да.
Что такое спецификация?
Это вот на сайте Google у нас описано все про…
Про то, как эти файлы строятся.
Здесь у нас описание букв, описание decimal digits.
Вот этот язык описания называется BNF, Backus Now Form.
Это с тех самых времен, когда Backus, он руководитель разработки языка Fortran, 1950-е годы, компания IBM Соединенных Штатов.
Для того, чтобы описывать формально грамматики языков, они придумали форму.
Она очень похожа на описание грамматик, например, в компиляторах компиляторов делается.
В такой программе, как Як, тоже очень похожим образом описывается.
Здесь что такое?
Буква – это что-то от А до З, символы.
С детства мал диджит это 0,9.
Мы вот такие примитивные не будем определять.
Почему?
Потому что они в партике все есть.
Нам нужно будет что-нибудь более интересное.
Например, идентификатор – это буква, за которой следует буква или цифра или знак подчеркивания.
Так.
Значит, вот и мы как раз к этому переходим.
Как у нас будет работать F-парсик?
Мы сейчас будем описывать.
Предположим, у нас есть…
Некий идентификатор.
И вот вверху мы видим, как он написан в спецификации.
Мы только что это смотрели.
Давайте еще раз покажу.
А то скажете, что я вас обманываю.
А я вас не обманываю.
Нет, не сюда.
Где вообще эта страница?
Вот это, наверное.
Нет.
Потерял.
Вот.
Идентификаторы.
Мы прямо берем спецификацию.
Вот эта строка вверху.
Вот как внизу выглядит описание идентификатора в нашем коде на F-sharp.
Значит, мы вызываем вот эту функцию manyOneCurseTwo.
То есть она что делает?
У нее есть два параметра.
Она читает один символ первый, который первый параметр, потом много раз читает символ второй.
Она как раз для таких штучек сделана как идентификатор.
И мы видим, что в принципе очень похоже у нас описание.
То есть у нас первое здесь letter, во втором ascii letter.
Ну это просто название, в спецификации написано так, ascii letter это функция парсер готовый из f-парсика, но там он так называется.
Дальше.
Здесь у нас много раз в фигурных скобках это то, что повторяется ноль или более раз.
У нас может быть буква или цифра или символ подчеркивания.
Это в спецификации.
Здесь мы описываем то же самое.
Это буква или цифра или символ подчеркивания.
То есть очень похожее описание.
В этом большое преимущество функциональных языков, потому что вот именно так всегда это будет у нас выглядеть.
Но чтобы не говорить голословно, давайте перейдем вот сюда.
Давайте попробуем это реально сделать.
Но перед тем, как делать, нам потребуется несколько дополнительных функций для того, чтобы мы могли писать тесты.
Так, первое, мы создадим модуль assert и туда вот этот текст скопируем.
Это нужно сделать во втором проекте с тестовыми данными.
Так, и давайте посмотрим на этот текст попристальней.
Что это такое?
Значит, модуль assert, у нас, ну вы знаете, вызов тестовых методов, это assert точка что-то, assert equals, not equal is true, not true, вот такие вот.
И мы в F-sharp можем просто добавить, как будто это будет выглядеть так, как будто мы в тот же самый модуль добавили несколько новых функций.
Значит, у нас есть внутренняя функция tryParse, и она три случая отрабатывает.
Она вызывает некоторую внутреннюю функцию run.
Run это из f-парсика опять-таки.
Ей на вход подается парсер, ей подается строка source.
И она возвращает либо результат, что ей удалось разобрать, и она возвращает какой-то результат.
Мы проверяем, удалось ли нам прочитать всю строку до конца.
Для чего это нужно?
Это нужно, чтобы проверять негативные разные варианты.
В частности, предположим, у нас мы передаем строку...
состоящую из, ну, дробное число, то есть целая часть, точка и дробная часть.
И мы применяем к ней парсер, который читает целые числа.
Вот он прочитает, правда, да, он прочитает, он прочитает, потому что все, что до точки, это целое число.
Но, как правило, если мы хотим проверить парсер, мы хотим убедиться, что он прочитал все до конца.
Поэтому мы должны проверять, что мы...
что после чтения у нас позиция равна длине строки.
Если нет, тогда мы говорим, что паттерн мы не дочитали до конца.
А если да, мы возвращаем результат.
Ну и если у нас ошибка, мы тоже возвращаем ошибку.
Вот из этих трех вариантов у нас есть три функции, которые мы будем пользоваться.
Parse, ParseEqual.
Parse это просто говорит, да или нет.
Удалось или не удалось.
Мы передаем ей строку, передаем ей парсер.
Если удалось распарсить, она говорит, что все нормально.
ParseEqual, передаем строку, передаем партер и передаем результат.
Она, результат, ну, ожидаемый.
Она сравнивает и говорит, да или нет.
Но Parse это для отрицательных сценариев.
Мы точно знаем, что не должно вот это вот разобраться, и мы проверяем, что да, не разобралось.
Значит, и вот этот простой...
Код вставили и теперь самое интересное начинается.
Тесты.
Мы вставляем новый файл, он называется Proto3Parser.
Что-то он не удалил.
Угу.
Значит, и вы знаете, в тестах рекомендуют списать сначала код теста, потом уже код метода.
Но проблема в том, что у нас все эти IDE, они у нас поддерживают IntelliSense.
То есть мы начинаем вводить, если у нас этого метода нет, они начинают жутко тревожиться и не дают нам работать.
Поэтому, ну, мое решение такое, что я сначала пишу сигнатуру метода, который возвращает какое-то, ну, дефолтное значение.
В данном случае pReturn — это...
парсер, который возвращает переданное значение.
Ничего он не будет читать, он сразу просто вернет.
Здесь мы описываем тип.
Обычно в Sharp умеют выводить типы, но это случается не всегда.
В данном случае не тот самый момент.
Нельзя.
Чуть позже, нам это уже не придется делать, но это просто совсем примитивные парсеры.
Он просто, ему не на что опираться, он еще пока не знает, какие типы могут быть.
Хорошо, значит, этот return, почему файл называется proto-3-parter?
Вообще, раньше назывался proto, proto-parter и proto-parter-test.
Но пока я это все делал, выяснилось, что там есть две версии стандарта, вторая и третья, они отличаются.
И, в принципе, правильнее делать их, если я писал утилиту, которая то и другое умеет парсить, мне бы это лучше было написать в двух соседних файлах.
Поэтому я решил, что это будет Proto3, и вот, значит, Proto3 парсер.
И одному мы должны написать тест.
Да, кстати, в F-Sharp очень важно, на каком месте находится программка здесь, в этом списке.
Программа должна быть в конце.
Здесь вещи, видите, я буду рассказывать, куда именно вставлять, показывать.
Интересно, сколько народу сейчас спит в зале.
Тема самая интересная.
Так, вот мы тесты написали.
Что такое эти тесты?
Давайте посмотрим.
Значит, два модуля мы открываем.
XUnit и ProtoTreeParser.
Вот у нас в код теста выглядит так.
Fact – это атрибут, который мы должны ставить перед методами, которые именно тестовые.
Названия методов, видите, они такие странные, как будто целая обычная строка.
Это часто используют возможность F-sharp.
Мы можем писать идентификаторы любые с пробелами, если мы их заключаем вот в эти обратные апострофы.
Там, где буква «ё».
Два апостера в начале, два в конце.
Тогда у нас идентификатор может быть любой.
И мы пишем, например, «abc» — это идентификатор.
И вызываем на нами написанный метод «parse», «assert parse».
Передаем ему строку «abc» и передаем парсер, который мы написали, идентификатор.
И он должен сработать.
То же самое «a», подчеркивание «b», подчеркивание «c» — тоже идентификатор.
«abc123» — тоже идентификатор.
Ну как бы простые вещи.
Мы сейчас запускаем тест и убеждаемся, что по идее они не должны работать, потому что мы не написали код теста.
Так, а он что-то нам говорит.
Где-то я случайно стер символ в комментарии.
Что такое?
А, да, нам надо вот это сюда.
Порядок файлов имеет значение.
Так, ну давайте.
В конце концов он должен не заработать.
Угу, ругается.
Все наши три теста ругаются.
Вот тут смотрите случай какой.
Я сел и сразу написал несколько тестов.
Хотя, в принципе, рекомендуют по одному тесту.
Мы пишем один тест, потом пишем код.
И, в принципе, в случае, когда вы не очень хорошо пока еще пользуетесь тестами, или там сложный код, вы не знаете, как его писать, вы можете идти маленькими шагами.
Но если вы чувствуете себя комфортно, то вы можете идти большими.
В данном случае у нас...
Мы можем идти большими, то есть мы написали функцию, три теста к ней.
Теперь мы давайте сразу напишем вот это вот, а мы практически этот код уже с вами разобрали.
Это вот я вам рассказывал как раз совсем недавно, мы смотрели на слайде.
Что такое идентификатор?
Это функция many one cares two, которая берет два парсера символьных, убеждается, что первый символ вот именно первым парсером, а потом много-много символов вторым парсером разрабатываются.
Значит, если мы этот код написали, у нас сейчас все тесты должны пройти.
Сейчас мы это проверим.
Это должно стать зеленым.
Замечательно.
Мне все нравится.
Вам все нравится?
Прекрасно.
Значит, следующая часть.
У нас есть просто идентификатор, а есть еще full идентификатор.
Полный full identifier.
Значит, что это такое?
Ну, если посмотреть вверху, опять-таки, у нас в BNF описан формат этой штуки.
Это на самом деле такой идентификатор, в котором внутри есть точки.
То есть a.b.c.
Это вот как раз такой длинный идентификатор.
Но не может быть точки в начале, не может быть точки в конце, только где-то в середине.
И для того, чтобы его написать, мы можем воспользоваться функцией готовой из strings.setby1, тоже из f-парсика.
Тоже она принимает два строковых парсера.
Первый это то, что в начале, а второй это то, что разделитель.
Аналог идентификатора, разделитель, идентификатор, разделитель.
Вот она сколько сможет, столько прочитает.
Но мы опять-таки не будем делать сразу так, мы сделаем хитро.
А, нет, у нас есть еще негативные тесты, давайте их добавим.
Я поспешил.
Негативные тесты про идентификаторы.
Это, конечно, хорошо, что мы убедились, что...
ABC это идентификатор.
Но вот если начинается с подчеркивания, мы знаем, что там так нельзя писать.
Подчеркивание BC это не идентификатор.
И мы используем вот эту вторую функцию not pairs.
То есть если мы придем подчеркивание BC, то идентификатор не сработает.
Точно так же все, что начинается с цифр, это не идентификатор.
Тоже не сработает.
И у нас, смотрите, какой момент получается.
Мы написали несколько позитивных тестов, потом дописали функцию, потом подумали и докинули еще негативные, чтобы убедиться, что у нас в обратную сторону тоже все работает.
Негативные тесты очень важны, поэтому нам приходится думать и туда, и обратно в обе стороны.
Так, а теперь... Да, вот теперь полный идентификатор.
Значит, снова мы создаем сначала функцию, которая ничего не делает.
Так, вот это нам не нужно, и это нам не нужно.
Серф нам тоже не нужен.
Здесь у нас в партере мы описываем все эти функции.
Пока она ничего не делает, она просто всегда будет возвращать пустую строку.
Потом мы записываем несколько тестов.
По сути, они проверяют, что... Ну, два позитивных теста.
Они проверяют, что у нас обычный идентификатор тоже является полным идентификатором.
Это может быть неочевидная вещь.
Но abc это полный идентификатор, то есть это как бы частный случай.
abc.defhig тоже полный идентификатор.
Вам видно или может быть опять побольше сделать?
Давайте.
Содержимое тестов.
Вот они о чем.
То есть у нас и abc должен распознаваться как полный идентификатор, и то же самое с точками abc.defheek тоже должно распознаваться как полный идентификатор.
И даже у нас не просто так.
ParseEqual у нас сравнивает.
То есть он не просто распознавался, а еще и результат проверяет, что именно эта строка прочиталась так.
Где мы сейчас находимся?
Секундочку.
У нас 10 минут осталось.
Так.
Значит, мы тесты написали, но мы их сейчас запускаем, и они у нас не должны сработать.
Понятно, мы еще не написали код.
Да, вот ругается как раз на два новых наших теста.
И следующее, что мы делаем, это копируем вместо старого определения новое.
То есть вот тот самый string step by one, идентификатор и символ точки, строка, состоящая из точки.
Теперь он должен, этот метод, который мы используем из fparts, он нам позволит строить такие длинные идентификаторы.
Мы сейчас делали с вами, ну так, работу, как это сказать, погрузились в то, как это все делается.
Дальше мы так не будем делать.
То есть мы сразу будем писать нормальные, как это сказать...
Ну, просто при разработке это нормально, а здесь у нас времени так много.
Значит, вот давайте мы научимся разбирать булевые и строковые литералы.
Ну, булевые они простые.
Здесь у нас есть парсер string return, он называется.
Он берет строку какую-то на входе.
Так, наверное, даже нужно перевести на новую строку.
Берет строку, если это true, то он возвращает результат true.
Ну true это второе, это уже булевое значение константа.
И у нас есть вот этот оперант, это или, то самое, комбинатор парсеров.
То есть у нас или true, или false.
Он умеет распознавать и возвращает булевое значение true или false.
Он простой.
Со строками сложно.
Строки это вот прям кошмар.
Честно говоря, мы так сейчас и не увидим.
Попробую вот так сделать.
Чуть поменьше.
Я рассказывать не буду, останавливаться, но тут в конечном итоге вот всё, что там описано в спецификации, здесь тоже всё делается, включая Unicode-ные символы.
И мы сейчас просто, ну вот, если вы хотите просто научиться разбираться, вы можете смотреть на спецификацию, потом можете смотреть сюда и такие, ага, понятно, как это сделано.
Но если не понятно, вы потом мне можете написать и спросить, а как это сделано.
Потому что, ну, рассказывать, правда, долго.
Но разбираться интересно.
Теперь давайте напишем тесты.
Мы уже не пишем тесты, мы их туда просто копипастим.
Но извините.
Теперь так.
10 минут осталось.
Вот этот интересный тест, видите, он проверяет, что у нас число записанное через Unicode.
Это на самом деле вот такой смайлик.
Запускаем тест и проверяем, что все нормально.
Запускайтесь, проверяйтесь.
Так, у нас еще довольно много, а нам надо... Хорошо, ну давайте тогда поспешим.
Значит, просто будем копировать.
Этот элемент, который сейчас я делаю, это в самом начале файла...
прото должен быть указанием синтаксиса.
То есть синтаксис равно прото 2 или прото 3, обязательно в кавычках, обязательно точка с запятой в конце.
Значит, у нас результат у этого, если вы увидите, это unit, unit, это значит, что результата нет у этого партера.
Он нам не нужен.
Мы просто должны убедиться, что эта строчка в начале файла есть.
А мы ее как обязательно запишем, этот партер, и тогда, если в начале файла не будет вот этой строки, но он будет ругаться.
И мы рядом записываем два теста, которые умеют распаршивать, и они проверяют, что...
Первый, смотрите, смотрит на синтаксис, который сейчас.
Синтакс равно прота 3, точка с запятой.
Он его должен распарсить.
Второй, синтакс равно прота 3, но без точки с запятой.
Он не должен распарсить.
Вот у нас тест, который этот момент проверяет.
Дальше начинается интересный момент.
У нас есть ключевое слово «пэкэдж».
Раз посмотрим.
Выглядит это так.
Наверху опять-таки из спецификации.
То есть package это что?
Это слово package.
За ним идет полный идентификатор, потом точка с запятой.
И мы когда распаршиваем, снизу вот у нас код этого нашего парсера, он пропускает строку package skip string.
Затем вот эти вот стрелочки и точечки, что они означают?
Видите, они вначале синие, а потом я их фиолетовыми раскрасил.
Потому что у нас точки стоят сначала справа, а потом слева.
И это интерпретируется каким образом?
Мы отбрасываем тот результат, который слева, а берем тот, который справа, в начале, точка справа.
Отбрасываем skip string package, возьмем space s1, должен быть обязательно один пробел.
Space s1 это тоже встроенный парсер.
Потом мы берем full identifier, мы его определили, у нас такой парсер есть.
И мы именно это строковое значение будем использовать.
Затем у нас вид стрелочек меняется.
Теперь мы берем значение слева.
Это значит, что fall identifier и spaces будет отброшено.
Skip chart точка запятой будет отброшено.
Спейс будет отброшено.
И у нас станет результат fall identifier.
Это будет строка.
И затем мы вызываем функцию, которая называется package.
Что это за функция?
Откуда она?
Почему с большой строки?
А это...
Это та тема, к которой мы сейчас переходим, важная.
Это абстрактное синтаксическое дерево.
Что у нас в результате должно получиться?
Вот мы парсим какой-то текст.
Внутри у нас получается структура данных, которая описывает вот эту структуру этого текста.
Это абстрактное синтаксическое дерево.
То есть там узлы разных типов.
Они вот так вот вместе образуют какую-то сложную иерархию.
И у нас...
Нам нужно будет теперь добавить модуль, в котором у нас будет описываться наше синтаксическое дерево.
Он будет называться просто Proto3.
Мы его должны поднять повыше.
Сюда мы запишем наш первый код.
Это вот как раз...
Здесь.
Это новый тип.
Тип называется Package, и его конструктор тоже называется Package.
На самом деле, синие и красные Package могут не совпадать.
И то, что мы сейчас видим, это так называемый Single State Discriminated Unit, U9.
Это такая штука, которая позволяет защитить, чтобы мы вместо одной строки, вместо имени не использовали другую строку, фамилию ещё как-то.
Мы можем...
Для каждой из них сделать свой тип оберточный.
Внутренняя всегда строка, но мы не сможем теперь присвоить, например, имя поля имени сообщения и наоборот, потому что нам уже F-Sharp будет считать их разными типами.
Это такие приемы защитного программирования.
Я слышал, что в Java все Sharp тоже их используют, но там они достаточно корявые.
То есть вам для такой штуки приходится создавать новый класс, и он громоздкий.
А в F-Sharp у вас это просто одна простая штука.
Мы далее в парсеры вставляем парсеры, которые умеют распознавать эти... Это вот то, что мы сейчас... Так.
Нет.
Что я куда вставил?
А, ну да.
Эта вещь нам нужна.
Мы должны к этому...
Сюда в начало вставим.
О, теперь нормально.
Все хорошо.
Так, и вставляем тесты, которые умеют это все разбирать.
OpenProto3, это значит, что мы должны к модулю Proto3 достучаться из парсера и так далее.
Так, а нам нужен тестер.
И здесь.
Так, и вот что у нас тестер делает.
Мы передаем ему
package.abcdef.ghi, точку запятой, и это должно распознаваться как package.
ParseEqual так и делает.
Мы ему передаем эту строку, передаем парсер, который называется package, и у нас результат это package и строка.
Package и строка это как бы константы этого типа.
Конструкторы, вызванные вместе со строкой.
И я не буду погружаться в это защитное программирование, потому что тема интересная, она большая, главное.
Значит, есть ключевое слово «импорт», дальше мы про него…
Значит, импорт бывает какой-то слабый, какой-то публичный, и, значит, у нас импорт-вик или импорт-паблик должно встречаться, и потом строковый литерал, а потом у нас точка с запятой.
И вот тип, который описывает вот этот импорт, это очень похоже на то, что мы сейчас делаем, но у него не один кейс, а три.
Это импорт, weak import и public import.
А к нему, соответственно, парсер будет выглядеть такой.
Он чуть более сложный.
Мне он заругается и говорит, что мы уже с вами 45 минут.
Мне еще минут 5 нужно будет.
Мы с вами еще поговорим.
Смотрите, вверху у нас еще раз описание, как это в спецификации.
Type import – это то, что у нас в абстрактном синтаксическом дереве будет.
Это у нас какой-то узел, который мы будем использовать.
Let import – это описание нашего парсера, который мы сами должны написать, чтобы он распознавал импорт weak, импорт public и возвращал нужный тип.
Нам да.
Мы это просто сейчас перенесем сюда.
Я вот не знаю.
Дело в том, что, наверное, не сможем обсуждать, как это все сделано.
Но вы можете это задавать в секции вопросов, а особенно в секции дискуссий.
Потому что я понимаю, что вот этот код не очень понятен, если вы в Offsharp не работали особенно.
Но он и не сложный, с другой стороны.
То есть мы написали сейчас в абстрактном синтаксическом дереве, потом в парсерах, потом...
Тесты.
Он разные варианты должен проверять.
И мы должны убедиться, что у нас все работает.
Вот эти тесты мы не просто так пишем.
Ну, по крайней мере, я. Я когда это все писал, у меня прям такой был подход.
И некоторые вещи я прямо исследовал.
Так, все зелененькое.
В этом проекте, кстати, больше 100 тестов.
Он же сейчас готовый.
На самом деле, вот исходный код здесь.
Дальше я предлагаю, знаете, что сделать?
Вот эти вещи мы не будем сейчас.
Вы, если собираетесь сделать, вы делаете, значит, 14, 15 и что-то там messages
Это просто копипастом делается.
Дело в том, что мы все равно не будем это подробно обсуждать, но мы можем поговорить в дискуссионной зоне.
Идея в чем?
В том, что мы просто берем и закидываем новые структуры в абстрактное дерево, такие все более общие и большие.
То есть сначала у нас опции, потом перечисления, потом сообщения.
И вот мы доходим до самого целиком файла.
Целиком файл, протофайл состоит либо из строчки импорт, package, option, message и нам, и пустого сообщения.
И здесь еще есть протосервис, описание сервисов, оно нам не нужно.
Мы просто сообщение же децерализуем, децерализуем.
И этот код...
Переносите вручную.
Да, вот у нас финальный тест.
На него, наверное, интересно посмотреть, как он выглядит.
Значит, мы берем, он обозначен как интеграционный, это значит, что он такой прям суровый.
На вход у нас подается строка такая.
Это на самом деле proto-файл.
У него должна быть строчка syntax.proto.package.
Я его откуда-то взял.
Я проверял, что он, правда, должен отработать.
Здесь описание нескольких месседжей.
Так, а инумов нет.
Вот это, кстати, зря.
Надо было с иномами поискать.
И вот это описание структуры, которая должна у нас вернуться.
Это вот абстрактное синтаксическое дерево.
Это какая-то сложная структура.
Это какой-то список, в котором будет package, будет option, будет import, будет message.
Message – это обязательное имя.
В нем будут поля.
Видите, это вот такая сложная структура.
Это константа.
То, что мы ожидаем.
Она большая.
Там правда много месседжей, ну, когда вот так вот это описать.
И у нас в конце вот эта инструкция parseEqual.
Мы берем вот этот наш экзампл, передаем протопарсер, и у нас должен получиться вот этот expected, наше ожидаемое дерево.
Если посмотреть, код выполнится.
Давайте, значит...
Мы сейчас делаем перерыв, но вы вопросы позадаете.
И мы с вами вот до 18, до 19 доходим 19 пункта, если кто делает.
Там просто написано, что создайте файл, скопируйте содержимое.
Создайте файл, скопируйте содержимое.
Вот, все.
Сейчас давайте, да, вопросы можно задавать.
Марк, спасибо.
Обожаю смотреть, как другие работают.
Господа, у нас есть время на пару вопросов.
Пожалуйста, поднимайте руки, у кого есть что спросить.
Давайте.
Вот я вижу руку в конце зала.
Я сейчас...
Экспрот там.
Вот.
То, что в первой части я рассказывал, можете задавать, чтобы не забыли.
Добрый день.
Большое спасибо вам за воркшоп.
Подскажите, пожалуйста, мне как человеку незнакомому с F-Sharp прикол с порядком файлов.
А, очень хороший, да.
Это хороший вопрос, да.
И дело в том, что большинство людей, программистов, которые только приходят, они говорят, что за фигня, зачем вот так это сделано страшно.
Ни в одном другом языке этого нет.
Мало того, что порядок файлов, тут порядок определения очень важен.
То есть все, что вы описываете сейчас, если вы чем-то пользуетесь, оно должно быть описано выше.
И это прям может составлять проблему, но это как бы, когда вы изучаете, вы говорите, вот все шарпи, не так, да везде можно, вот в любом порядке.
Но, когда вы заходите в новый проект, это единственная вещь, которая вам реально помогает, потому что вы берете проект, и вы знаете, в каком порядке его читать.
Это специально Дон Сайм, создатель языка, придумал, ну и, кстати, наверное, из Вакамбли тоже такая же ситуация, придумал для того, чтобы можно было читать последовательно.
То есть вы берете первый файл, читаете сверху вниз, идете второй, сверху вниз.
И это так удобно.
То есть те люди, кто пересел на F-Sharp, считают это чуть ли не одной из важнейших фич языка.
То есть когда вы понимаете, что это прям вам дает возможность быстро познакомиться с проектом, вы такие, блин, ну классно.
Никакого больше C-Sharp'а.
Вот.
Получается, циклических зависимостей не может быть.
Может быть, но, во-первых, циклические зависимости у вас внутри одного файла, у вас есть специальные штучки для этого.
Вы либо функции, несколько функций вместе, если рекурсивно вы их определяете, либо типы тоже вы можете вместе.
Тут у нас, кстати, в тексте это есть.
Плюс еще в...
При парсинге это тоже используется.
Если хотите, можете подойти в дискуссионной зоне.
Мы с вами или просто после доклада я вам этот код покажу.
То есть люди это как бы обходят в тех случаях, когда это надо.
В принципе, циклические зависимости внутри одного файла можно сделать.
Так, а если я вот не в райдере работаю, а беру и в блокноте просто пишу?
Как этот порядок файлов определяется?
В принципе, если вы где-то там
в IDE используете, в которой есть поддержка встроенная для, ну как, language server подключенный, там тогда эта возможность тоже появляется.
То есть вы можете, вам дается возможность управлять порядком файлов.
Но это же сохраняется в каком-то файле в итоге, порядок?
Да-да-да, этот файл называется project file.
Сейчас едет...
Вот, ну это тот самый описание Солюшена, вот файлы в каком порядке идут, они здесь.
Спасибо.
Ну не Солюшена, про проекта.
И у нас есть еще один вопрос, вот малой человек рядом с колонной.
Большое спасибо за доклад.
Очень интересно, ничего не понятно.
У меня есть на самом деле история успеха про F-Sharp.
Это достаточно специфичная задача.
В одном из прошлых проектов на C-Sharp возникла необходимость парсить все свишки и превращать их в бизнес-сущности сразу.
Код на C-Sharp был достаточно труднопонимаемым, а на F-Sharp занял одну страничку.
Поэтому я это сказал, чтобы люди не теряли веры, на самом деле, в функциональные языки, в шарп.
Поэтому вот такие дела.
Спасибо.
Спасибо, что сказали.
Давайте просто, ну, чтобы у нас... Так, а где вот код?
Вот он.
Реально у нас код парсера, ну, например, вот этот прото-три парсер полностью готовый.
Это все сообщения, месседж.
Он занимает 200 строк.
И мы можем парсить вот эти все протофайлы.
Но вы такого на C-Sharp не напишете.
Правда.
Это все.
Вот тут все.
Больше ничего нет.
Это очень круто.
Абстрактное дерево, кстати, сколько занимает?
100 символов.
Ой, 100 строк.
Это очень короткий код.
То, что мы пишем на F-Sharp.
Это одно из огромных преимуществ.
И это только поначалу непонятно, потому что на самом деле это просто язык, это синтаксис.
Как только вы к синтаксису привыкаете, для вас это становится нормальный, понятный код.
Отлично.
Всем напоминаю, что будет вторая часть этого воркшопа в 16 часов после перерыва.
Возвращайтесь к нам.
Нужно довести дело до конца, узнать, чем закончится эта история.
После второй части будет сессия опросов и ответов в дискуссионной зоне.
Там вы можете поймать нашего спикера.
Похожие видео: Марк Шевченко

Марк Шевченко — Воркшоп «Практические задачи решаем функционально» (Часть 2)

Денис Цветцих — LINQ Expressions: искусство запрашивать данные

Анатолий Кулаков — Build as Code

۱.۱. مقدمات ریاضی | عملگر توان

Почему ассемблер остается актуальным в 2025 году? | Дмитрий Коваленко | #28

