Сегодня я хочу рассказать вам о таком замечательном инструменте как "dotnet interactive". Я покажу на своём примере как и для чего я начал его использовать, и вкратце опишу с чего начать.
Проблема
Моя работа связана с разработкой программы, предназначенной для оценки эффективности средств физической охраны. Данный продукт в своём ядре содержит имитационную модель, результатом которой является файл протокола, в котором описаны события моделируемого эксперимента. Это могут быть такие события как «нарушитель попал в зону камеры» или «группа реагирования ведет преследование нарушителя» и прочие подобные события, которые могут происходить при моделировании.
По итогам протокола считаются различные статистические метрики, которые потом уходят в отчёт. Сейчас формулы, как считать нужную нам статистику, разбросаны по различным этапам ТЗ, поэтому, когда я вчера узнал о "dotnet-interactive" мне сразу пришла мысль о создании notebook'a в формате "Описание метрики"-"Формула"-"Код"-"График", в котором можно будет загрузить любой файл протокола и в интерактивном формате проходить и считать интересующие нас метрики.
Приступаем к созданию notebook'a
Прежде всего, у вас должен быть установлен .net5 sdk и последняя версия VS Code. Далее, нужно лишь установить расширение ".NET Interactive Notebooks". Данное расширение сейчас имеет статус "Preview", однако уже сейчас там можно делать много интересных вещей.
Когда мы установили расширение, можем создать рабочую директорию, в которой будут лежать нужные библиотеки, скрипты и файлы. Открываем её в VS Code и окне команд вбиваем ".NET Interactive: Create new blank notebook" и начинаем наполнять наш notebook.
В первом блоке кода я определил загрузку файла протокола:
#load "Load.fsx"
open Load
let Experiment = loadSep "2021.02.03_15.55.58_gen.sep"
Здесь я на F# подключил скрипт, который инкапсулирует в себе логику открытия файла и xml-сериализацию:
#r "nuget: System.Text.Encoding.CodePages"
#r "AKIM.Protocol.dll"
open System.IO
open AKIM.Protocol
open System.Xml.Serialization
open System.Text
let loadSep path=
let deserializeXml (xml : string) =
let toBytes (x : string) = Encoding.UTF8.GetBytes x
let xmlSerializer = XmlSerializer(typeof<Experiment>)
use stream = new MemoryStream(toBytes xml)
xmlSerializer.Deserialize stream :?> Experiment
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance)
deserializeXml (File.ReadAllText(path, Encoding.GetEncoding(1251)))
В этом скрипте я подключаю nuget пакет для кодировки и свою библиотеку с dto-классами, написанную на C#.
Во втором блоке notebook'a уже в c#-intaractive я подключаю нужные для работы с экспериментом пространства имён и шарю объект в с# из f#, определенного в первом блоке
#r "AKIM.Protocol.dll"
using AKIM.Protocol;
using AKIM.Protocol.Events;
using AKIM.Protocol.Events.OperatorSvn;
using AKIM.Protocol.Events.OperSb;
using AKIM.Protocol.Events.RespUnits;
using AKIM.Protocol.Events.Intruders;
using AKIM.Protocol.Events.Sens;
using AKIM.Protocol.Events.System;
#!share --from fsharp Experiment
Собственно дальше мы можем в произвольном порядке описывать и считать нужные нам метрики, используя для этого объект эксперимента. Например, следующий блок выводит общее количество проникновений на объект и количество отказов нарушителя от проникновения
var allTests = Experiment.Tests.Count;
var penetrations = Experiment.Tests.Where(t => t.Events.Last() is PenetrationEvent).Count();
var nonPenetrations = Experiment.Tests.Where(t => t.Events.Last() is NonPenetEvent).Count();
var eve = Experiment.Tests.First().Events.FirstOrDefault(t => t is VisContactEvent);
Console.WriteLine(eve?.GetDescription());
Console.WriteLine($"Количество проникновений {penetrations} из {allTests}")
Нажав на запуск выполнения кода мы получаем следующий вывод:
А дальше я могу использовать полученные значения для построения красивой диаграммы или графика, например:
#r "nuget: XPlot.Plotly"
#!share --from csharp penetrations
#!share --from csharp nonPenetrations
#!share --from csharp allTests
open XPlot.Plotly
Chart.Pie(seq {("Кол-во проникновений",penetrations);
("Нейтролизовали",allTests- penetrations-nonPenetrations);
("Отказ от проникновения",nonPenetrations)}) |> Chart.Show
При выполнении график открывается у меня в браузере, хотя я видел, как в некоторых туториалах он открывается снизу блока с кодом (upd: используйте XPlot.Plotly.Interactive. Примеры построения графиков на c# и f# есть в репозитории проекта (ссылка внизу)).
Что дальше
В дальнейшем, есть идея описать язык взаимодействия с событиями на F# в виде отдельной библиотеки или скрипта, как это было описано, например, тут. Так как, на мой взгляд, подобный notebook должен заполнять аналитик проекта, а не программист.
Что не работает?
Мне не удалось загрузить файл скрипта на С# с расширениями "*.csx" в с#-interactive. Возможно еще не завезли, возможно не правильно готовлю. Плюс не удалось решить, почему графики открываются в браузере, а не снизу блока с кодом. Также в markdown блоке не хотят отображаться формулы в формате $$...$$. (upd: в примерах в репе есть вариант выполнения латех кода, но тогда формула отображается в блоке вывода, а хотелось бы иметь красивую формулу в markdown блоке)
Выводы
Я считаю, что этот инструмент должен попробовать каждый .net разработчик. Вариантов использования масса: обработка результатов, прототипирование каких-то идей, изучение F# или C#, скриптинг для работы с операционной системой, например, чтобы manage'ить какие-то файлы. Лично я, когда случайно узнал вчера об этом инструменте, был в диком восторге, что и побудило меня сделать этот пост, так как мало, кто об этой штуке слышал (но это не точно).
Хочу поблагодарить своего подписчика на ютубе, Arkadiy Kuznetsov, который подсказал мне о существовании такого инструмента.
Жду отзывов об использовании этой штуки в комментариях. Советую скачать все примеры notebook'ов из репозитория проекта и прощупать их в VS Code.
Спасибо за внимание.
Полезные ссылки
Официальная репа, в которой есть также документация
.NET Interactive + ML.NET
Новые фичи f#(в начале видео использует dotnet-intaractive)