Образование и нейронки
Когда ChatGPT только появился, почти сразу начались обсуждения по поводу его использования в образовании (в основном со стороны студентов). А 2,5 года назад так вообще был защищен диплом, почти полностью написанный ChatGPT. Но на мой взгляд, использование нейронок, чтобы сдать какую-то работу — это не новая проблема, а скорее просто еще один фактор, который подсвечивает старые. Ну и делает все еще проще для студентов.
Ниже я выплесну свои мысли по поводу текущих проблем, в которых нейронки мало что поменяют (на основе личного опыта преподавания), расскажу о своем опыте с режимом обучения ChatGPT и поделюсь идеей учебной задачи с использованием нейронок.
Это не новая проблема
Мотивация
Если студент не хочет учиться и/или ему не интересен предмет, то насильно мил не будешь. Если студенту нужна оценка, а не знания/навыки, то и получит он скорее всего только оценку. Студент будет использовать любые средства слиться с любого задания, если ему лень, неинтересен предмет, он считает предмет не нужным, ему нужно работать, и т.д. и т.п. Избежит ли он работы с помощью нейронки или другими средствами — вообще не важно, конечный результат примерно одинаковый.
Усугубляется все это тем, что на предыдущих этапах обучения все было так же. Совершенно нормально, что студент технического вуза на третьем курсе может не знать, что такое логарифм, производная, предел. Но страшнее даже не это — студенты не хотят это узнавать. Зачем? Они все равно получат свой диплом с минимумом усилий. Однажды я поставил во второй раз двойку студентке, которая не ответила на тот же вопрос, за который получила предыдущий неуд. Более того, некоторые студенты даже не умеют искать материал — процентов 20-30 студентов я мог завалить вопросом “какого цвета учебник?”.
С точки зрения препода в таких условиях надрываться, чтобы студент сделал все сам, довольно бессмысленно. Особенно когда отчислить студента почти невозможно. Мотивированные студенты смотрят на весь этот балаган и в итоге тоже начинают забивать — их усилий никто не оценит.
Плагиат и ГДЗ
Вот возьмем тот же диплом. Раньше, до ChatGPT, нельзя было написать работу за кого-то другого? Или может, нельзя было скопипастить на 80% дипломы прошлых лет? Ну в конце концов, собрать диплом из нескольких готовых статей из интернета? Да, теперь студент это может сделать несколькими промптами бесплатно и с меньшим вовлечением мозгов, но с точки зрения конечных результатов мало что поменялось.
Какие-то контрольные мне и раньше сдавали, списывая статью из Википедии, даже не вникая в суть текста, и без понимания, является ли он ответом. Неоднократно были ситуации, когда списывали из 2 источников и текст ответа противоречил сам себе. И раньше многие математические примеры можно было скормить какому-нибудь WolframAlpha и получить относительно приемлемый ответ. Наконец, никто не отменял старшие курсы с их материалами — если программа и задания особо не меняются, то и готовые ответы будут почти на все.
Короче, и без нейронок будет миллион способов списать/сдать “на отвали”.
Актуальность программы и состава курсов
Чтобы “бороться” с переносом ответов между курсами, надо бы обновлять программу курса регулярно. Признаюсь, что уже на третий год мотивация это делать у меня, как у препода, была близка к нулю: программа семинаров после второго года менялась очень точечно (в последние года — в основном в сторону упрощения), а новые задачи я придумывал максимум по 2-3 штуки за год.
Ну а если пришел студент за одними знаниями, а получает в итоге другие — кто виноват и что делать? Зачастую программа специальности прибита гвоздями и будешь ты на технической специальности учиться рандомным фактам из истории, которые ужали в один семестр с зачетом, потому что “ну надо”. См. раздел про мотивацию.
Помогут ли как-нибудь нейронки актуализировать программы курсов? Теоретически да, но если задания генерит нейронка, то она же их и решать будет. С точки зрения подачи теоретических знаний не так уж часто что-то кардинально меняется. Можно обновить язык и попытаться попасть в текущие тренды, но это скорее мишура (и, вероятнее всего, будет кринжово).
Бороться с бюрократической шизой изменения программ специальностей я бы на месте уважающего себя ИИ не стал бы.
Формальные требования — формальный результат
Когда к диплому предъявляются требования в формате N страниц по структуре X, то и на выходе будет нужное количество страниц с водой, которые даже научный руководитель читать будет наискосок, и то в лучшем случае. И раньше там была сплошная копипаста с графоманией. Если есть пункт только для галочки, так и пусть его нейронка пишет (но надо хотя бы прочитать, чтобы откровенной ерунды не было). Могу сказать, что вода, написанная нейронкой и вода, скопипащенная студентом, практически не отличаются по качеству.
Вообще, если диплом читает в лучшем случае только научник, да и то не весь, почему кто-то так борется за качество текста в нем?
Вас много, я — одна
В идеале экзаменатор должен проверять степень усвоения знаний. Вот только как это сделать нормально?
Письменный экзамен с запретами? Десятки лет эволюции шпаргалок, устройства любого уровня компактности, тупая зубрежка, слитые вопросы и прочие смеются вам в лицо.
Персональные задания? Кто их будет генерить? Нейронка или тупой скрипт, где будут меняться циферки в шаблоне? Ок, задания будут не идентичны, и полные тупни их не вывезут, но существенно они вряд ли что-то поменяют.
Опрашивать каждого студента лично на экзамене и контрольных, чтобы понять его подходы к мышлению и глубину знаний? Вот это хороший вариант. Одна проблема — никак не масштабируется, когда у тебя на 1 лектора и 1 семинариста 100 человек на потоке, и ты хочешь не за красивые глаза оценки ставить. Да и студентам сдавать несколько экзаменов за сессию тяжело уже.
Можно еще давать NP-полные задачи, которые было бы тяжело решить, а проверить можно было бы быстро. Но генерировать интересные задачи в достаточных объемах весьма нетривиально.
Можно ли пользоваться X?
При оценке знаний, например на экзамене, можно ли пользоваться конспектом лекций? Учебником? Калькулятором? Шпаргалками? Интернетом? WolframAlpha? Питоном? ChatGPT?
Где та грань между знаниями, которые должны отлетать от зубов, и информацией, всегда доступной по запросу? Должен ли я помнить факт N? Ничего, что работаю я за компом, где все эти знания есть, а на крайний случай есть телефон?
Стоит ли тратить время на неуспевающих?
Без обратной связи любая система гниет. Если единственный способ у препода избавиться от студента — поставить ему положительную оценку, то корреляция оценки с уровнем знаний будет слабая.
Еще моя бабушка решала вопрос кардинально — ставила всем тройки на халяву: ей было жаль своего времени. Я был не согласен с ее подходом. Из года в год на моем предмете было больше трети недопущенных к экзамену в конце семестра. При этом планка требований падала ниже и ниже.
С точки зрения препода принимать задолженности у двоечников — очень демотивирующее занятие. Эта проблема элементарно решается организационными методами (например, давать только 2 попытки допуститься/сдать). Можно, конечно, решить нейронкой — препод генерит нейронкой задачу, студент нейронкой ее решает, все довольны:)
Нужны ли лекторы?
Ведь можно все что надо найти в интернете (ладно, можно не искать, сейчас все нейронка выдаст) и прочитать, да?
Удачи с поиском в мертвом интернете, да еще ровно в том объеме и ровно с нужного ракурса, чтобы усвоить материал по предмету в рамках нужной специальности.
Гугл сейчас дает “умные ответы от ИИ” на почти любой запрос — как вам, нравится?:)
Когда я учился, то вопрос полезности лекций и, в частности, качества донесения материала, тоже постоянно обсуждался. Правда, тогда учебник все еще использовался как дополнительный аргумент. Однако, однажды перед сдачей экзамена пришлось править статью в Википедии, потому что формула там была неправильная. А еще я был на очной лекции курсов повышения квалификации преподавателей, где нам дедок-лектор рассказывал про неэффективность очных лекций для передачи знаний.
Можно конечно посмотреть рандомного лектора на ютубе, вероятно индуса. Изменят ли тут нейронки что-нибудь? Ну окей, будет тебе трап-аниме-вайфу кавайным голосом рассказывать про символ Лежандра, может это улучшит усвоение материала на пару процентов в абстрактных попугаях, но не более того. А будет ли это стоить потраченной энергии?
И так далее
А еще есть бюрократия, мотивация преподов, миллион дополнительных обходных путей вроде сдачи зачета “нужному” преподу, вопрос “а нужно ли высшее образование вообще”, баланс практики с фундаментальными знаниями и т.п. Продолжать список проблем можно до бесконечности. В этом посте я накидал крупными мазками основное, чтобы проиллюстрировать мысль, но это только верхушка айсберга. Сейчас хорошим преподам приходится искать компромисс среди всего вороха проблем, чтобы хоть какой-то положительный след в умах учеников оставить. Диплом — это уже практически справка о том, что ты не дебил.
Эксперимент с режимом обучения в ChatGPT
Ладно, может, тогда нейронки могут заменить преподов и вузы? Вон, целую школу открыли для 4-5 классов, почти без людей, AI-driven!
А сравнительно недавно (3 месяца назад) ChatGPT представил режим обучения. Вводишь запрос, и нейронка сама тебе подберет программу обучения. Твой личный препод, полная персонализация, кожаные мешки больше не нужны! Вот только после релиза особо новостей про него что-то больше и не видел.
Решил попробовать — попросил ChatGPT рассказать, что такое О-большое в этом режиме. И, как ожидалось, это полная шляпа.
Знания выдавались очень поверхностные, какой-то сложности (кек) или глубины в них не было. Уровень рандомного видоса на ютубе. При этом не были даже упомянуты модели вычислений, элементарные операции, худшие/лучшие случаи, оценки памяти и т.п. Статья из Википедии даст больше контекста, да и страничка из учебника — тоже.
Адекватных проверочных заданий бот не дал, пока я явно не попросил. В итоге я получил… тест с вариантами ответа! Первый вопрос был уровня “Не в Москве ли находится Московский кремль?” Второй вопрос был примерного того же уровня, но вдобавок еще и был не совсем некорректно сформулирован. После третьего вопроса для даунов, бот предложил мне рассказать побольше про O(n log n). Я согласился, и тут мы резко перешли от “оцени сложность цикла” до объяснения, почему сложность сортировки слиянием — O(n log n).
Иллюстративный код был написан на питоне, в котором было копирование слайсов (что на асимптотическую сложность не влияет, но влияет на расход памяти и на реальное время исполнения). Я решил воспользоваться этим и попробовал загазлайтить нейронку, что сложность у этой паршивой реализации будет другой. ChatGPT в ответ каждый раз выдавал простыню текста и постоянно предлагал мне нарисовать картинку, но я игнорил его и настаивал на своем. Еще он пытался слезть с реального кода и вернуться к теории. В итоге я преуспел: мне удалось убедить его, что сложность сортировки слиянием это O(n² log n):
Реально в Python: c_copy(n) и c_merge(n) растут с n, поэтому наблюдаемое время работы может выглядеть как O(n² log n) или даже хуже, особенно при больших объектах или сложных типах.
“You’re absolutely right!” Уже на этом можно было эксперимент закончить. В моей практике неоднократно были ситуации, когда какой-нибудь студент был ну ооочень уверен в своей правоте. Я думаю, что текущие нейронки подобные кадры задушили бы с легкостью. Напоминает истории про то, как люди использовали ChatGPT как психолога/партнера и в итоге отъезжали в психушку/мир иной.
Под конец я добил бота парочкой каверзных блиц-вопросов из своих материалов. В целом бот справился, но у него была глубина ответа среднего студента, а не преподавателя или специалиста. С учетом того, что он генерирует простыни на простейшие запросы, мог бы и получше ответить.
Как все-таки использовать?
Получается, что нейронки для обучения в целом бесполезны? Нет, нужно просто использовать их с умом.
Я надеюсь, что каждый адекватный человек уже задался вопросом — если мои задачи может делать нейронка (при текущем уровне развития), то нафига я нужен? Разумеется, нейронку стоит использовать как инструмент, а не замену себе. Многим нравится еще аналогия, что они руководители, а нейронка — линейный сотрудник.
Но в таком случае, надо знать ограничения и понимать, когда нейронка генерирует дичь. Для этого надо обладать какими-никакими фундаментальными знаниями в области (в том числе, чтобы правильно сформулировать запрос) и уметь перепроверять выплюнутый результат.
Из этого вытекает учебное задание — попросить нейронку что-то сделать, проверить ее результат и перечислить ошибки/недочеты. Давным-давно такое еще на собесах использовали — найди ошибки в кусочке кода — позволяет неплохо оценить глубину знаний.
Причем идея проверки за нейронкой вовсе не нова. Есть история с реддита, и в МГУ есть положительный опыт подобного использования. Из очевидных плюсов — демистифицирует могущество ИИ и позволяет наработать практический опыт: для чего подходит, для чего не очень.
Итого
Еще в январе 2021, за год до первого релиза ChatGPT, я подготовил письмо о своем увольнении из вуза. Его я так в итоге и не отправил (проработал еще 3 года), но часть тезисов из него попали сюда. Основная идея была в том, что текущая система имеет недостатки, я устал и я в ней лишний. Без всяких нейронок.
Еще раньше, в январе 2020, преподаватели кафедры бурно обсуждали проблемы текущей системы. Многие проблемы, судя по мемчикам, за 5 лет особо не изменились (некоторые усугубились).
В общем, на мой взгляд, образование у нас уже давно катится в известное место. Проблемы связаны и усиливают друг друга. И это еще в моем вузе не такая страшная ситуация (хотя объективно есть места, где все же лучше и, можно сказать, не так уж и плохо). Нейронки могут добавить немного скорости к этому движению, но кардинально ситуацию они не поменяют. Безусловно, задача образования — адаптироваться к новым реалиям, но сначала нужно решить старые проблемы.
Еще немного Koka
Финальная (надеюсь) часть истории про Koka. Предыдущие части: первая, вторая.
По сути, доделал, что хотел доделать, и поэкспериментировал с чем хотел.
Получил обратную связь
Запостил ссылку на свой проектик, получил небольшой фидбек от одного из разработчиков. Мелочь, но приятно. Многого и не ждал, т.к. номер обсуждения — девять, и это первое обсуждение в категории “покажи свой проект”.
Когда переходил по ссылкам, внезапно узнал, что ссылки на обсуждения на GitHub имеют сквозную нумерацию с тикетами и PR в репозитории организации. Например, эта ссылка на первое обсуждение перенаправляет на первый PR.
Улучшил сборку
Отрефакторил пайплайн сборки — в итоге сам поиск собирается в релиз при публикации нового тега, и уже он используется при сборке сайта. Все обмазано кэшами. Попутно оптимизировал сборку, чтобы было не 60 мегов, а 31. По умолчанию все печально.
Посмотрел на action от разработчика и на разрабатываемый пакетный менеджер — ну, такое, мягко говоря… Остался на своих велосипедах.
Обновил версию языка и библиотеки.
Увы, особо много интересного не было, за исключением возможности использовать _ в лямбдах как в Scala.
Внес вклад в сообщество
Я открыл еще 8 (восемь!) тикетов, 5 пулл-реквестов и одно обсуждение с тупейшим вопросом. Как будто уже на них работаю, кек:) В библиотеки сообщества все быстро приняли, а в основном репозитории пока почти нет реакции.
Навалил фич
В первую очередь сделал префиксый поиск для последнего токена — наконец-то пригодилось ДДП!
Правда, пришлось писать самопальный поиск элементов, следующих за данным.
В стандартной библиотеке толком не используется факт упорядоченности map и никаких методов нет.
Вообще, уже одна эта фича улучшила поиск существенно. Несмотря на искусственное понижение результатов в выдаче, именно с префиксного поиска больше всего полезных результатов выпадает.
Пока вспоминал базу с ДДП, написал первые тесты. Не обошлось без проблем, но поправил сам, и получилось терпимо.
Наконец, написал стеммер. Смысла в нем не очень много, но изначально хотел его сделать и решил все-таки поставить галочку. Сам алгоритм оказался гораздо проще, чем я думал, практически вызов цепочкой однообразных функций с разными параметрами. До самих функций конечно надо додуматься, но на это не ушло много времени.
Уже на стадии чтения описания алгоритма я почти сразу придумал слово, которое будет плохо обработано: “карась” будет урезан до “кар” из-за возвратного “сь”. Поэкспериментировать самостоятельно можно тут, таких примеров можно найти много. Но многого ожидать от бесконтекстных стеммеров не стоит, да мне и не надо.
У стемминга есть нюанс, что он потенциально не очень хорошо сочетается с префиксным поиском для поиска длинных строк, но я не заметил разницы на реальных данных. Но у него есть и безусловный плюс — уменьшение размера индекса почти в 1,5 раза (JSON с индексом теперь 1,1 Мб вместо 1,6 Мб). Ну и точность ранжирования должна быть получше за счет объединения терминов.
Больше времени убил на борьбу со стандартной библиотекой.
Не смотря на то, что там есть проекция (view) строки, нужных мне методов там не оказалось, и доступа к отдельным символам по индексу тоже.
Я сначала честно пытался что-то построить с существующим API, но в итоге признал поражение и написал свою урезанную проекцию из велосипедов и грязных while с нужными мне методами.
Но на этом приколы со стандартной библиотекой не закончились.
Внезапно обнаружилось, что в ней нет flatmap для maybe.
Позже — совсем мрак: нет contains для списка!
Ладно, есть any, но это как вместо isEmpty писать .count > 0 (передаю привет C#).
Наконец, последней фичей стало переключение раскладки — “ns gbljh” чтобы искалось. Реализуется тоже элементарно, главное учесть, что смену раскладки надо сделать до токенизации (иначе всякие бюжъх потеряются), но после приведения к нижнему регистру.
Заключение
В целом, не ожидал, что после первой провальной попытки будет столько мотивации работать над этим проектом. Результатом я весьма доволен, получилось то, что я хотел. И что-то новое по дороге узнал, и со старых навыков сдул пыль. Надеюсь, что после пары минорных правок успокоюсь и переключусь на что-то другое:)
Мышки плакали и кололись, но продолжали есть Kokatus
Несмотря на проблемы с Koka, о которых можно почитать в первой части, я все-таки решил продолжить :/
Поддержка Юникода и сишная вставка
Начал я с потрахушек с сишной вставкой для поддержки Юникода — она не работала из-за undefined behavior, потому что я пытался использовать ICU библиотеку для изменения строки “на месте”, а чатгпт мне врал и газлайтил про сломанное окружение. Пока сам внимательно не посмотрел на код и не подумал, что может пойти не так, ничего не получалось. Справедливости ради, сама библиотека для Юникода, такое ощущение, тоже меня газлайтила — API у нее, мягко говоря, не очень, и пару раз я получал ошибки весьма сомнительного качества, которые скорее запутывали, чем давали понять, что не так. Но после того, как сделал микро-файлик с изолированной проблемой и догадался про UB, все относительно быстро решилось. Пиковый опыт разработки на сях + пиковый опыт общения с “ИИ” помощником.
Были еще проблемы с тем, чтобы понять, что нужна статическая линковка, откуда взять библиотеки, откуда взять C++ библиотеку, можно ли как-нибудь это не руками делать и т.д.
Отдельной историей была попытка разобраться с менеджерами пакетов, conan и vcpkg, но это какая-то своя вселенная с типичным (херовым) UX, и я быстро решил эту идею свернуть, так как еще разбираться подробно с ними и их интеграцией в Koka — нет, спасибо.
Даже по вайбу — в одном тебя лягуха JFrog встречает с пикой точеной с установкой через pip, в другом — мелкомягкие с установкой через git clone и несколько ручных действий.

Я еще подумывал о том, чтобы поправить пулл-реквест по итогам обратной связи, которую мне все-таки дали, но решил, что покрывать все нюансы практически вслепую — так себе опыт, и отложил это дело когда-нибудь “на потом”. Тем более не могу сказать, что почувствовал четкость и уверенность в ответе — как бы потом еще раз переделывать не пришлось.
Парсер JSON
В момент написания собственно поиска я осознал, что в ни в стандартной библиотеке, ни в библиотеке от сообщества нет парсера JSON. Только писатель.

Ладно, ладно, что-то есть, но это выглядит настолько не очень, что лучше бы его и не было.
В какой-то момент я хотел написать свой кастомный текстовый формат для сериализации, но потом все же решил написать свой мини-парсер JSON, без вещей, которые мне не нужны. При этом какие-то функции для преобразования JSON в нормальный тип в стандартной библиотеке все-таки есть, и если приправить их щепоткой магии с имплиситами, работают они неплохо.
Парсер в функциональном стиле — интересная зарядка для ума, компилятор хорошо помогает с точки зрения покрытия различных состояний. Первую версию я написал относительно легко, она была уродливой, но моей, и, что самое главное, работала.

Не очень понравилось, что нет толкового копирования структур. Есть немного сахара, однако, как я понял, это не мой случай из-за того, что у меня типы-объединения. Хотя в каком-нибудь Котлине с sealed-классом и выводом типов это сработало бы без копипасты. Позже я немного отрефакторил код и копипасты стало не так много.
Фронтенд
Моя любимая тема:) Следующий челлендж — сделать что-то интерактивное, чтобы поиском можно было пользоваться на сайте. Для интеграции с браузером нет библиотек из коробки — ну, ничего, уже умеем делать внешние вызовы :)
Думал было побыть модным и молодежным и скомпилировать все в WASM, однако для него на кой-то ляд нужны сишные библиотеки. Честно, даже не стал вникать, решил, что

Такое ощущение, что JS для фронта был сделал по остаточному принципу. По ходу выяснились какие-то достаточно детские болячки: например, при создании библиотеки игнорируется флаг пути вывода — алло, как мне ее использовать? Или, например, если вдруг у обертки и реального вызова в JS одно имя — здравствуйте, бесконечная рекурсия.
Состояние
Одним из проблемых вопросов, который возник почти сразу при написании морды — как хранить состояние (загруженный индекс)? Мы же в чистом ФП, при каждом запросе запрашивать и парсить JSON — отвратительно, т.е. его надо передавать как параметр к фунции. Вот только кто это будет делать и откуда? Функция по идее будет вызываться только при изменении поля ввода, т.е. вызываться браузером.
Я находил какие-то ошметки доков про то, как сделать что-то подобное в общем случае, но… ничего толком не понял, потратив кучу времени :/ Даже думал тупо забить: пусть передается вызывающим кодом, т.е. оставить небольшую обертку из JS, в которой будет изолировано все состояние и все эффекты. Задал вопрос на форуме, ни на что не надеясь, однако мне ответили быстро и по делу. Стало понятнее, но тогда я временно отложил это дело. А в итоге советами не воспользовался и выкрутился путем рефакторинга — состояние (индекс) в итоге хранится в замыкании, без дополнительных фишек языка и всего такого.
Превозмогаем fetch
Первая попытка запуска поиска была, разумеется, неуспешая, потому что
created : .koka/v3.1.2/js-debug-4de7e1/search__main.mjs
error : command failed (exit code -11)
command: node --stack-size=100000 .koka/v3.1.2/js-debug-4de7e1/search__main.mjs
А потом еще и
...
node --stack-size=100000 .koka/v3.1.2/js-debug-4de7e1/search__main.mjs
Segmentation fault (core dumped)
В итоге всемогущий print-debug вывел меня на fetch, который безуспешно пытался загрузить JSON-файлик с индексом.
Кажется, одна из проблем была связана с асинхронностью, поэтому я сначала решил пойти по “простому пути” и сделать вызов fetch синхронным.
“Вертел я еще в этом зоопарке с асинхронностью разбираться”, — подумалось мне.
Разумеется, так делать нельзя, зря что ли два цвета у функций?
А синхронный метод (XMLHttpRequest) устарел (да и выглядит уродско).
В итоге нашел адекватный способ и адаптировал его под свой случай.
Поковырялся немного, чтобы использовать голый async и не использовать unsafe-as-string, но это был провал.
Попробовал сделать еще нормальную ошибку при отсутствии файла.
Тут интересно, что расширяется стандартный интерфейс exception-info:
abstract extend type exception-info
con JSError(error: jsobject<any>)
Это круто, вот только доступа к типу нет, и его нельзя никак проверить/показать, потому что он не публичный. Уже когда писал эту статью, отправил PR с исправлением, вмержили сразу.
Верстаем
Когда разбирался с fetch, решил попробовать просто что-то абстрактное в браузере запустить, прямо из консоли, но не тут-то было: библиотеки компилируются в модули, к функциям внутри них получить доступ из консоли — нетривиально.
Штош… придется писать SPA и генерить HTML кодом (sic!).
По умолчанию вывод main идет в
<div id="koka-console" style="...">
<div id="koka-console-out" style="...">
<a href="https://ya.ru">test</a>
<br>
</div>
</div>
Причем с экранированием, т.е. просто выплюнуть HTML не получится. Пытался разобраться, как поменять эту обертку на что-то другое — это закопано в v1 библиотеке, которую еще и хрен подключишь.

В итоге решил, что не стоит это того, и решил через нативную функцию заменять код страницы. Это сработало. Получается, не просто будем генерить HTML кодом, но еще будем это делать в стиле реакта, когда весь HTML генериться бразуером на лету. Best practice, однако (устаревший).
Пока разбирался с изменением кода, попутно нашел и библиотеку для генерации HTML.
Вот тут (наконец-то!) смог оценить подход с эффектами.
В компонентах с их помощью собираются элементы: билдер создает эффект, а build все “ловит” — выглядит очень круто, абсолютно ничего лишнего!
Из минусов — не очень расширяемо: например, на input нельзя добавить атрибуты и нужно использовать более низкоуровневое API.
Однако это можно исправить, не меняя сам подход, и скорее проблема малого использования библиотеки и обратной связи для нее.
В итоге я сам это и поправил.
С точки зрения стилей — сначала хотел сделать все с нуля, а потом подумал: нахера изобретать велосипед?

Переделал тупо теги и классы под то, что и так у меня на сайте, и загрузил CSS оттуда ужаснейшим методом. А единственное непокрытое место, ширину поля ввода 100%, тупо сделал inline-стилем: не делать же ради этого дополнительный файл.
Интеграция с Web API
Пробую запустить — не работает: не вызывается функция поиска.
При отладке выясняю интересную особенность — имена манглируются: do_search → do__search, search-frontend → search_dash_frontend.
Зачем — не очень ясно.
Было ли это проблемой?
Нет.
Потратил ли я на это время?
Да :(
Основная засада — импорты.
Наружу торчит только main.
Попробовал привязать вызов функции к атрибуту поля ввода oninput — мимо, потому что функция в области видимости модуля.
Ладно, попробуем через addEventListener — тоже мимо, потому что непонятно, как пробросить название вызываемой функции правильно.
Если пробросить саму функцию — то она как будто не вызывается :(
Нашел как сделать перезагрузку скриптов и вызвал функцию напрямую, получил исключение.
Поганый JavaScript не может нормально сказать, в чем проблема :/
Ошибка оказалась где-то в std_core_hnd.mjs — не определена переменная.
Т.е. видимо я ухожу из контекста Koka и не получается ничего сделать.
Ну, хотя бы научился редактировать скрипты в браузере.
Хотел сделать простенький репродюсер, чтобы отправить баг, а в нем все, сволочь такая, работает! Методом тыка выяснилось, что если в функции есть хоть один сайд эффект, то все, “усе пропало”. Было бы классно получить нормальную ошибку, и это минус интеграции языка с браузером.
Еще и JavaScript поднасрал своими API — потратил время на какую-то ерунду: одна и та же функция работала на кнопке, но не вызывалась на поле ввода.
Все для того, чтобы обнаружить, что правильное событие — input, а не oninput.
Разумеется хотя бы предупреждение в консоли показать слишком просто :/
Вдобавок есть небольшая проблема с типами: это тебе не TypeScript, в котором определил структуру типов и радуешься.
Тут определил структуру — получи [object Object].
Поэтому в коде остался унылый Jsobject(event).get-obj("target").get-string("value").
Все выше перечисленное чатгпт с точки зрения фронта сгенерировал бы за 1 простой промт, конечно.
Отлаживаем сам язык
В какой-то момент словил баг компилятора:
(1, 1): internal error: unable to read
CallStack (from HasCallStack):
error, called at src/Common/Range.hs:65:53 in koka-3.1.2-2NpoSf9Uv2JEsgjfT7jQ0Q:Common.Range
readInput, called at src/Compile/Build.hs:1317:40 in koka-3.1.2-2NpoSf9Uv2JEsgjfT7jQ0Q:Compile.Build
getFileContents, called at src/Compile/Build.hs:644:17 in koka-3.1.2-2NpoSf9Uv2JEsgjfT7jQ0Q:Compile.Build
moduleLex, called at src/Compile/Build.hs:631:43 in koka-3.1.2-2NpoSf9Uv2JEsgjfT7jQ0Q:Compile.Build
(1, 0): build warning: interface async found but no corresponding source module
При этом помогало удалить .koka.
Сузить проблему не удалось, поэтому много на нее времени не тратил.
Поборов интеграцию функции поиска с полем ввода, получил новый ворох проблем в лицо. Вот какая-то ошибка в КЧД при десереализации:
_mlift_init_10105/index__size< - search_dash_frontend.mjs:108:21
ReferenceError: $std_data_rbtree is not defined
Ну епрст, если еще и дерево хреновое, то это вообще жопа.
Обложился трейсами — теперь другая ошибка в другом месте, каеф.
string_fs_show - std_core_show.mjs:326:18
ReferenceError: $std_core_vector is not defined
В принципе “логично”, трейс выводит в консоль, функция вывода не работает — получаем ошибку. В процессе дебага увидел, что находится внутри встроенной строки — связные списки (бля, это жесть 🌚). Соответственно, работает это все не медленно, а ПИЗДЕЦ как медленно.
Ошибка с выводом поначалу пофиксилась… убиранием флага -O3.
Не, ну я конечно понимаю, что один из таргетов — это Си, но чтобы еще undefined behaviour из него тащить — это смело!
Оказалось еще, что даже с -O1 код работает медленнее, чем без оптимизаций вообще.
А вот фикс для работы с КЧД оказался просто конченным: добавить лишний импорт.
Это мне еще повезло, что ошибка на верхнем уровне в подконтрольном коде, иначе бы я не знал как это исправить.
Предыдущую проблему с show это не решило.
Поборов проблему с импортами, получил следующую ошибку (я уже в шаге от MVP, честно-честно, надо чуток потерпеть…)
Paused on exception
$regexExecAll - std_text_regex.mjs:128:25
TypeError: $std_core_types._vlist is not a function
Оказалось, что проблема даже не в сгенерированном JS, а… в обычном! Это баг стандартной библиотеки языка: метод вызывается из одного модуля, а по факту находится в другом.

Проверил через перегрузку скриптов тупейший фикс — работает :/ В итоге сделал микро-PR, чтобы починить, и его приняли за полчаса, ух!
На этом баги библиотеки не кончились — Юникод нанес еще один удар, теперь по регуляркам! В сях и шарпе они юникодные, а вот в JavaScript — нет, потому что флаг не поставлен. В итоге еще один пулл-реквест, который тоже относительно быстро вмержили.

Организация кода
До этого времени все было в одной папке и в дополнение к ней была помойка utils.
Захотелось более четко разделить ядро, фронт и вещи, специфичные для бложика.
Тут используется хаскелловский подход к импортам.
Вроде все ок, но красиво не очень получается: либо добавлять всю папку src, чтобы сохранить иерархию, либо добавлять используемые модули по одному, но тогда теряются префиксы.
С core еще ладно, но вот utils хотелось явно оставить.
В итоге оставил -isrc, тем более это все-таки best practice, как я понял.
Получил унылое:
src/extractor/extract.kk(44,37): type error: types do not match
context : serialize(index)
term : index
inferred type: index/index
expected type: core/index/index
но в итоге порешалось использованием везде правильного префикса для импортов.
Основная логика
Вы ведь все еще помните, что я поиск пишу, да?
В черновике заметки между пунктами выше у меня было “написал индексер”, “написал поиск” и т.п. Когда находишься в чистой абстракции, то и проблем не так уж и много. Скажем так, собственно логика поиска и “движка” была самой простой и беспроблемной задачей.
В первой итерации я сделал обычный TD-IDF.
Во второй итерации решил сделать чуть лучше — BM25.
С ним интересная оптимизация получилась: если известно k и b, то можно предвычислить коэффициенты.
Ради этого даже поддержку дробных числе в парсер JSON добавил.
Еще одна ошибка с импортами
После рефакторинга получил еще одну ошибку, но теперь уже в индексаторе:
core/indexer(1, 1): internal error: Core.Parc.getDataDefInfo: cannot find type: std/data/rbtree/rbtree
CallStack (from HasCallStack):
error, called at src/Common/Failure.hs:46:12 in koka-3.1.2-2NpoSf9Uv2JEsgjfT7jQ0Q:Common.Failure
raise, called at src/Common/Failure.hs:32:5 in koka-3.1.2-2NpoSf9Uv2JEsgjfT7jQ0Q:Common.Failure
failure, called at src/Backend/C/Parc.hs:988:34 in koka-3.1.2-2NpoSf9Uv2JEsgjfT7jQ0Q:Backend.C.Parc
Failed to compile src/extractor/extract.kk
очень интересно и ничего не понятно :(
Причина оказалась во вспомогательной функции
pub fun values<k, v>(m: map<k, v>): list<v>
m.rb-map/values
Видимо, совпадающие имена в комбинации с псевдонимом типов не работали.
Возможно, из-за подобной ошибки были проблемы с импортами и в JS.
Заменить на квалифицированный .map/values не сработало, переименовать в get-values — тоже.
В итоге тупо переписал код, чтобы это не использовалось.
Обновление версии языка
Попробовал зарепортить ошибки с импортами — а они поправлены в последнем мастере! Отлично, попробуем обновить версию языка.
Пробую собрать из исходников — фигушки, не перепробрасываются cclibdir.
Пробую собрать из альфа-релиза — фигушки №2, \r в юникс-скрипте (sic!).
Дальше скрипт становится еще лучше!
./install.sh --url https://github.com/koka-lang/koka/releases/download/v3.1.3-alpha17/koka-v3.1.3-linux-x64.tar.gz
Installing koka v3.1.3 for ubuntu linux-x64
warning: unknown option "--url".
Installing dependencies..
Using generic linux bundle
Downloading: https://github.com/koka-lang/koka/releases/download/v3.1.3-alpha17/koka-v3.1.3-linux-x64.tar.gz
Т.е. жалуемся на неправильный аргумент (который есть в --help между прочим), а потом его используем :/
Зато после обновления (с 3.1.2 до 3.1.3):
- решилась проблема с
values(хотя уже не очень-то и нужно было). - внезапно отвалился
string/replace-all, но простоreplace-allнорм работает. - пофиксился ублюдочный баг с импортами в JS.
- за счет двух моих вмерженных PR можно было убрать костыли.
- даже проблема, из-за которой надо было
rm -rf .kokaделать, стала реже появляться (однако потом снова появилась с большей силой — теперь надо было иногда еще и демон компилятора убивать). - добавили метод
expectдляmaybe(правда, имя конченное), удалил свою реализацию вutils. - смог вернуть
-O3на фронте.
Bleeding edge разработка, епта!
Потом еще и версия 3.2.0 вышла, пока я сопли жевал со статьей, а потом еще и сразу 3.2.2. Там есть пара интересных фич, но я уже немного устал.
Имя проекта
Перейдем к самому главному!
Рабочим названием проекта было looKfor.
Оно было унылым.
Чатгпт предлагал всякую дичь, больше всего понравилось grepka.
А в итоге в Википедии нашел Klava Koka, до этого про нее вообще не слышал. Жена мне запустила 2 “известные” песни с телефона. Мне показалось, что цитата “Че тебе нужно” из “хита” с чумовым названием “ЛА ЛА ЛА” как раз подходит для проекта. Ну и сферическая баба Клава тоже должна знать, что где лежит.

Муки оптимизации JSON парсера
Моя реализация была медленной, но она работала. Решил попробовать как-то ее оптимизировать, потому что 10+ секунд — как-то совсем не комильфо.
Первую альтернативу попробовал сделать через нативный вызов JSON.parse — уж быстрее этого ничего не будет.
Однако поверх него была еще обертка по преобразованию JSON-объекта в JSON-объект Koka — по сути, большой if-else-if с проверкой типов и рекурсивными вызовами с конструкторами оберток.
Пробую запускать… и код прерывается в браузере без ошибки :(
Пробовал и так и сяк — ну не работает, и все!
При этом в консоли все работает нормально.
Я конечно понимаю, что JSON размером 1,6 Мб не так-то просто распарсить, но не настолько же:(
Убийцей оказался опять -O3.

Попробовал воспроизвести проблему с -O3 в изоляции — получилось плохо.
Методом научного тыка выяснил, что виноват async — он плохо взаимодействует с рекурсией.
Причем эта падла (эффект async) проникает в main, и как-то изолировать десериализацию, чтобы она была вне пространства с этим эффектом, не получилось.
Наконец, в дебаггере докопал до too much recursion
Какого лешего это раньше не всплывало — неясно.
Узнал интересности про рекурсию в браузере.
И понял, почему в консоли работало: там тупо поставлен больше размер стека вызовов: node --stack-size=100000.
Что делать с этим — не очень ясно.
Хотел попробовать использовать sslice (read-only view поверх строки) в первой версии парсера — стало уже не ФП, и все равно медленно.
Еще и оказалось, что в мутабельную переменную нельзя присваивать результат match — только “простое выражение”, чтобы это ни значило.
Решилось промежуточной переменной, но скорости все это не прибавило.
Еще раз попробовал переписать парсер, теперь уже в мутабельном стиле с sslice и рекурсией.
Открыл наконец, как можно сделать паттерн-гарды, однако счастье было недолгим:
because : guard expressions must be total
т.е. от мубального зависеть там нельзя. Но в итоге и эта реализация тоже оказалась медленной. Даже в консоли это было медленное говно, медленее любой из предыдущих реализаций.
Пробовал ускорить этот медленный вариант через оптимизацию вставки в списки (вставлять в голову, а не в конец) — стало быстрее, но не существенно. Никогда бы не подумал, что буду думать об этом в нормальном коде за пределами абстрактных лаб.
Попробовал vector-list из библиотеки сообщества — хуже моей обертки сработал.
Даже словил
mimalloc: error: buffer overflow in heap block 0x020080000580 of size 65528: write after 65528 bytes
Вернулся к первой попытке, ужасному ФП коду, впендюрил туда свой тупейший хак для списка — и это наконец заработало!
Получается, как будто зря писал еще две с половиной реализации.
Но благодаря этому узнал что-то новое и смог существенно упростить оригинальную реализацию.
А еще получил ачивку — сотый коммит в библиотеку сообщества.

В продакшен
Попробовал сначала встроить на сайт через iframe — господи иисусе, это такой ящик пандоры с разнообразными проблемами, что мама не горюй.
Очень быстро от этого отказался, обошел фиксом, что не тело документа меняется, а выделенный контейнер.
Ну и файлик на сайте, который создает контейнер и грузит основной модуль, все-таки пришлось сделать.
GitHub Action для сборки намястрячил примерно так:

На стадии экспериментов выяснил, что индексер работает медленнее при (это я тупанул и не учел, что индексеру еще скопилироваться надо).-O3, но это некритично, 20 секунд индексации сайта на холодном старте меня устраивают
Итого
В итоге эта лабуда работает!
Это было достаточно больно, но все-таки интересно. Фишку с эффектами таки удалось оценить. Какой-никакой вклад в опенсорс добавил.
Наконец-то, написал свои поиск и JSON-парсер! Еще в изначальном плане был стеммер, это проще чем думается, но решил все-таки отложить на попозже. А то так можно дойти до идей с поиском по префиксу, триграммам и хаком для неправильной раскладки…

UPD: у этой истории есть еще и третья часть.
Переходим на субстанции потяжелее — Koka
Предыстория
Где-то в конце октября захотелось мне добавить на сайт поиск, даже для этого на GitHub Actions перешел. Плагином пользоваться не хотел, да и как-то скучно это. Тем более что встроенный плагин по сути выплевывает огромную JSON-нину, по которой и идет поиск — звучало это не очень оптимально.
Думал посмотреть на что-нибудь типа SQLite, запущенным в браузере и использовать его полнотекстовый поиск, но там как будто задолбаешься кастомизировать. Вылезло (сам себе придумал) требование, что во время индексации при сборке сайта и при собственно поиске должен использоваться один и тот же морфемный анализ. Потом уже настолько сильно погрузился в тему во время исследования, что захотелось написать что-то самому.

Было еще интересно что-то попутно поизучать (вспомнил свой опыт с Elm). С учетом требования про один язык на стадии индексации и при поиске, я искал что-то новое и с мульти-таргетом и наткнулся на язык Koka.
Впечатление о языке
Документация и инструменты
Описания языка в докладах (1, 2) звучит весьма интригующе: тут тебе и алгебраические эффекты, и широкие возможности с точки зрения комбинаций исключений и типов, и парадигма FBIP: Functional but In-Place (джва года все ждали такое), и приятности типа dot-apply (когда x.func(1,2) эквивалентно func(x,1,2)).
Плюс язык может быть транслирован в Си, JavaScript или C# (ну, создатель в Microsoft работает).
То, что язык непопулярный, можно понять хотя бы по тому, что расширение не скопировано в неофициальный маркет VSCode (поэтому был и пост про это). Другой индикатор молодости/непопулярности языка — чат-боты на любой вопрос по нему выдают полную хрень. И подсветки на гитхабе нет.

Справедливости ради, ботов можно понять: документация у языка не супер.
Документация стандартных библиотек, на которую сразу с сайта и не выйдешь, чуть более полезна, но тоже не восхитительна.
На обоих ресурсах нет поиска (ироничненько).
В итоге самым адекватным оказался поиск по исходникам на гитхабе.
Но даже там было не то чтобы много результатов.
Отмечу, что все это надо было еще откопать, а я всего-то хотел узнать, как получить аргументы командной строки — и еле нашел ответ в примере про КЧД.
Найти fst и snd для кортежа было сущим кошмаром из-за интересного выбора названий.
Добавляет боли и то, что в расширении для VSCode толком нет автодополнения. И автоформатирования. Спасибо, что хотя бы за счет языкового сервера ошибки некоторые подсвечиваются.
Основы
В целом, жить можно, но проблем хватает.
Вы думали, я шутил про поиск в исходниках вместо документации?
Так вот, чтобы откопать ++ для конкатенации — тоже пришлось попотеть.
Наверняка знающие люди насуют мне, что надо знать Haskell/ML-подобные языки и там это естественно, но епрст, я не помню наизусть этот синтаксис:(
Глобальные импорты — вроде удобно, но до первой неоднозначности. Не очень понятно, что происходит с функциям у которых одинаковое имя, но при этом даже в стандартной библиотеке есть:
fun replace-all( s : string, pattern : string, repl : string ) : stringfun string/replace-all( s : string, regex : regex, repl : string, atmost : ? int ) : stringfun replace-all( s : string, r : regex, repl : (list<sslice>) -> e string, atmost : ? int ) : e string

Отдельного упоминания стоит их реализация: посмотрите, как красиво:
// Replace every occurrence of `pattern` to `repl` in a string.
pub inline extern replace-all( s : string, pattern : string, repl : string ) : string
c "kk_string_replace_all"
cs inline "(#1).Replace(#2,#3)"
js inline "(#1).replace(new RegExp((#2).replace(/[\\\\\\$\\^*+\\-{}?().]/g,'\\\\$&'),'g'),#3)"
// Count occurrences of `pattern` in a string.
pub inline extern stringpat/count( s : string, pattern : string ) : int
c "kk_string_count_pattern"
cs inline "Primitive.Count(#1,#2)"
js inline "((#2) ? ((#1).match(new RegExp((#2).replace(/[\\\\\\$\\^*+\\-{}?().]/g,'\\\\$&'),'g'))||[]).length : 0)"
Определять show для типов — правильно, но хотелось бы из коробки что-то для DTO.
Ленивости не хватает: например, нет ничего с ленивым выкидыванием исключения. Да и вообще, ленивость как идею особо не наблюдал ни в документации, ни в коде.
Немного странно, что эффект pure “грязнее” просто функции без эффектов, т.к. у него есть эффект div.
Понятно, что это вроде как должно быть удобно для всякой арифметики, но:

При разработке на Elm я жаловался на отсутствие хэш-таблицы из коробки. Посмотрите, какая восхитительная реализация ассоциативного массива у Koka:
/* Map
Todo.
*/
module std/data/map
type map<k,a>
Это она целиком, если что. И да, в примерах есть реализация КЧД, но в стандартной библиотеке ничего нет.
Ладно-ладно, я ее позже все-таки нашел: есть экспериментальное ответвление стандартной библиотеки, которое когда-нибудь через никогда может быть будет вмержено в основной репозиторий языка.
Управление зависимостями
Пришло время рассказать про менеджер пакетов. Тут все как в меме:

Т.е. его нет. А как какать? Официальный ответ — использовать гит модули (sic!).
Хотел через гит-модули подключить, но там нельзя только подпапку выдрать, а шелл-скриптом — некрасиво… в итоге из-за сложности принятия решения тупо забил на этот проект аж на месяц.
Потом наконец-то вернулся и решил, что пошло оно все нафиг, и будет скрипт “менеджер пакетов”, который клонирует библиотеку(и) в ./libs
При попытке использования альтернативной библиотеки возникла проблема: внутри нее есть ссылки на std.
koka -i./libs my-source.kk — вот так вроде правильно, но альтернативная std будет после стандартной.
В итоге мой говноскрипт тупо копирует альтернативную стандартную библиотеку в ./std.
И при таком расположении используется именно она.
Отладка и дебаг
Ошибки компилятора, мягко говоря, не очень понятные:
uncaught exception: unexpected Nothing in std/core/maybe/unjust
Это, если что, вся ошибка.
Впрочем, сам виноват: нечего было unjust использовать.
Пришлось попыхтеть, чтобы понять, что в этой функцию поиска по ключу надо было добавить… доказательство что есть ==!
fun get(maplike: list<(k, v)>, key: k, ?(==) : (k,k) -> bool)
(этот код я удалил, когда в экспериментальной стандартной библиотеке нашел hashmap).
Но даже с hashmap я все равно задолбался подбирать правильные функции, которые надо добавить в область видимости.
В итоге создал тикет, и что круто — почти сразу ответили, баг поправили и я смог кодить дальше.
Дебаггера нет, поэтому по первости использовал println.
Но есть нюанс: это же эффект!
Поэтому надо добавить эффект консоли по всей цепочке вызовов.
Правильно, но неудобно.
С другой стороны — если прям обрабатывать все ошибки, то сузить проблему можно довольно быстро.
Чуть позже обнаружил, что есть trace("ololo"), который не добавляет эффект, но выводит в консоль.
И там уже появляется … unsafe-total — по сути, бэкдор для системы эффектов.
Поддержка Unicode
Довольно быстро обнаружил, что JavaScript не поддерживает юникод с точки зрения \b и \w в регулярках (место для вашей rage-картинки про JS).
Соответственно, Koka, который транслируется в JS, имеет те же проблемы.
А вот с собственно символами все еще хуже. Я бы даже сказал, что поддержка чего-то, что не ASCII отсутствует, несмотря на какие-то ошметки кода в стандартной библиотеке.
Думал отказаться от Си-таргета…. чтобы обнаружить, что в JS-таргете не работают хэшмапы из‑за отсутствия сида. Но это хотя бы можно поправить костылями — например, использовать КЧД. А вот с файлами теперь работать не получится, потому что откуда эти API в браузерном JS?

Это открытие меня весьма расстроило, и проект опять был надолго поставлен на паузу. С учетом того, как быстро ответили на первый тикет, было печально не увидеть никакой реакции на второй. Я даже открыл PR, чтобы добавить базовую поддержку Unicode в Cи-таргет, но тоже не получил ни ответа, ни привета. При этом не могу сказать, что я был в восторге от процесса разработки — одно только тестирование путем сравнения вывода с текстовым файлом о многом говорит.
В общем, на этой стадии я решил, что потраченного времени жаль, и мужественно решил забить на все это окончательно.
Итого
В целом, опыт был интересный. Немного жаль, что в итоге никаких толковых артефактов не получилось. У языка есть потенциал, и идеи интересные, но все слишком сыро и недружелюбно. ПСТР от Elm, много параллелей с ним. Крутые фишки так и не смог полноценно оценить.

UPD: и через какое-то время я собрался с силами и продолжил.
Про телеметрию
TLDR: следить за пользователями — это обычно плохая идея.
Вроде бы это должно быть очевидно (с точки зрения пользователя), но у всяких там продактов иногда возникает соблазн “собрать немного данных”. Поэтому некоторое время назад я решил поисследовать эту тему поглубже, чтобы иметь более аргументированную позицию.
Источники
Есть отличная серия постов от разработчика Go, в которой разобраны плюсы телеметрии, аргументы за нее и обозначены лучшие практики по ее внедрению. Вкратце:
- Баг-репорты содержат недостаточно информации.
- Опросы пользователей содержат недостаточно информации и нерепрезентативны.
- Многие негативно относятся к телеметрии из-за в основном предубеждений и стереотипного мышления (“они собирают каждый наш клик!”), но в некоторых случаях это вовсе не преувеличение.
- Без статистики использования непонятно, какие функции используются/нужны, а какие нет, и время разработчиков может быть потрачено впустую.
- Пользователи не знают, как должно вести себя ПО, поэтому не дают достаточно хорошие баг-репорты, а некоторые проблемы могут остаться незамеченными несколько релизов.
- Телеметрия должна быть прозрачной: надо собирать как можно меньше данных, показывать пользователю, что собирается, не содержать идентификаторов, ее должно быть легко отключить, данные должны быть текстовыми.
- Решение о внедрении телеметрии должно быть публичным.
- Собирать со всех все не нужно, достаточно сравнительно небольшой выборки для репрезентативных данных.
- С каждой установки данные собираются не постоянно, а изредка.
- Все собранные данные должны быть публичны.
- Телеметрия должна быть включена по умолчанию, но ее отключение должно быть простым.
Последний пункт был отредактирован, потому что после обратной связи от сообщества разработчики решили отключить телеметрию по умолчанию, т.е. пользователь должен осознанно ее включить. Вообще, opt-in (телеметрия по умолчанию выключена) против opt-out (включена по умолчанию) — один из ключевых вопросов в этой теме. В этой же серии постов можно прочитать про это подробнее: тяжелее получить данные, надо пиарить подключение, согласившимся нужно будет чаще посылать данные, будет перекос в собранных данных и т.п.
А есть мнение, что замена на opt-in сделает телеметрию бесполезной. Т.е. по сути opt-out — плохо для пользователей, а opt-in — бесполезно для разработчиков. Но в той же статье есть и аргументы за телеметрию, причем в оригинальном видении из цикла статей. При этом отмечается, что не последнюю роль в негативной реакции сообщества сыграло то, что автор предложения работает в Гугле (а он уже давно не “don’t be evil”).
Разработчики Audacity хотели внедрить телеметрию, но в итоге после негативной реакции пользователей пришли к более скромному варианту, который покрывает только вопрос более подробных баг-репортов.
Некоторые аргументируют, что телеметрия бывает разной, и не вся телеметрия — это слежка, а стоит думать о ней как о голосовании, мол, ваш “голос” будет учтен. И вообще, open source разработчикам тяжелее получать данные о пользователях, чем разработчикам проприетарного ПО.
Если почитать статьи про телеметрию с более пользовательской точки зрения, то там будут в основном негативные или язвительные комментарии. Примеры:
- Telemetry is definitely your enemy
- The Telemetry Fallacy
- Fedora Project mulls ‘privacy preserving’ usage telemetry
Отдельно можно отметить комментарии на HackerNews, тысячи их:
Там есть мнения как с точки зрения пользователя, так и с точки зрения разработчика.
Я постарался как-то это все агрегировать ниже.
Аргументы “за”
Вкратце: Больше данных хорошо, проще получать инсайты, проще приоритизировать задачи и развивать продукт.
- Без телеметрии невозможно собрать статистику об использовании продукта.
- Статистика использования позволяет приоритизировать разработку функциональности.
- Статистика позволяет удалить ненужную функциональность без негативных последствий.
- Люди лгут, телеметрия — нет. Опросы пользователей содержат недостаточно информации и нерепрезентативны.
- Телеметрия позволяет обнаруживать проблемы, о которых люди и не задумывались.
- С телеметрией предоставляется больше данных чем в обычных баг-репортах.
- Баг-репорты требуют усилий и не все пользователи их отправляют/могут отправить, телеметрия экономит усилия пользователя по отправке данных.
- Телеметрия позволяет делать более надежные продуктовые решения. Без данных принимаются плохие решения.
- Телеметрия — это способ “голосования” людей за улучшения в продукте.
- Обратную связь разработчики получают только от самых “говорливых” пользователей, а с телеметрией — от всех.
- Понять сценария использования системы и интегрировать эту информацию в процесс разработки и тестирования весьма тяжело без метрик.
- Тяжело тестировать оптимизации ПО без метрик с пользовательских машин, держать парк своих серверов для тестирования производительности — дорого.
- Телеметрия может помочь создать более прибыльный продукт.
- Все веб сервисы и так собирают данные при каждом обращении к сервису, чем телеметрия хуже?
- Большинству пофигу на “приватность”.
Аргументы “против”
Вкратце: данных и так полно, качество данных от телеметрии сомнительно по многим причинам и не даст всей картины, на основе телеметрии можно сделать неверные выводы, надо думать о ИБ.
- У вас и так полно данных о платформах, которые вы получаете от статистики установок, User-agent и т.п. Интернет так работает и всех это устраивает, т.к. большинство понимает, что посылается и когда. Активная отсылка данных с клиента — плохо, потому что это вторжение на пользовательский клиент, которое пересекает границу, созданную браузером/протоколами.
- Обложите бекэнд трейсингом, и получите дофига данных.
- Если у вас не веб-приложение, то вместо телеметрии можно добавить функциональность по отправке ошибок.
- Если у вас не веб-приложение, то возможно ваши пользователи достаточно умны, чтобы написать нормальный баг-репорт.
- Если вы не знаете, как работает ваш продукт или что нужно пользователям, то телеметрия не решит проблемы вашей разработки. Наймите нормального продакта и нормально делайте исследование своих пользователей, прежде чем внедрять телеметрию. Желание внедрить телеметрию может быть сигналом о том, что продукт стал таким сложным и/или хрупким, что пользователи не напрягаются давать обратную связь. Или отправить баг так сложно, что никто этим не заморачивается.
- Просто данных недостаточно для того, чтобы делать какие-то выводы. Нужно строить гипотезы и подтверждать их, иначе это будут просто догадки. Легко “переоптимизировать”. Любая метрика перестает быть хорошей когда она становится самоцелью. Для оценки качества есть соблазн использовать эту же метрику. Телеметрия не покажет ни намерений, ни эмоций пользователя.
- Если фича редко используется, это не значит, что ее можно удалить (аргумент про огнетушитель и бэкапы).
- Телеметрия может быть формой дискриминации: “только 5% пользователей пользуется фичей, давайте ее удалим” или “если ты не шлешь телеметрию, то твое мнение не учитывается”.
- Разработчики опенсорса не должны заботиться о том, как используют их бесплатное ПО пользователями, которые ничего не дают им взамен. А ответственные пользователи помогают проектам, открывая пулл-реквесты и баг-репорты.
- Телеметрию нельзя использовать на удаленной фиче или на желаемой фиче, для этого надо использовать другие источники информации и знать своего пользователя.
- С телеметрией легко скатиться к “среднему пользователю” и “среднему по больнице”, что обычно плохо.
- Для новой фичи данные будут искажены из-за ранних пользователей и не дадут хорошей картины.
- У вас и без данных от телеметрии полно работы (кто-нибудь работал в успешном продукте, где не было большого бэклога?). Если у вас есть четкое видение продукта и вы нормально исследуете пользователей, то у вас достаточно и работы и приоритетов.
- Многие продукты больших компаний (Google, Facebook и т.п.) обложены телеметрией — становятся ли они со временем лучше? Точно ли телеметрия дает выгоду пользователю?
- Анонимизация снижает качество собираемых данных, но при этом способы деанонимизации совершенствуются. Т.е. если данные нормально анонимизировать, то они станут бесполезны, а если это не делать — то это нарушит приватность пользователей. Некоторые еще переживают, что с ИИ анонимность вообще недостижима, а пользователей с уникальными (странными) окружениями легко сдеанонить фингерпринтингом.
- Если продукт с открытым исходным кодом, то легко его модифицировать, чтобы посылать мусор вместо данных.
- Компании плохо заботятся о ИБ, данные пользователей рано или поздно утекут. Даже если собираемые данные надежно защищены, не факт, что это не поменяется в будущем или что они не будут слиты по первому запросу госорганам.
- Opt-out — плохо для пользователей, а opt-in — бесполезно для разработчиков.
- Opt-out — в серой зоне с точки зрения легальности (с точки зрения GDPR и конвенций ООН).
- Телеметрия выгодна только разработчикам и нужна, чтобы сократить расходы.
- Телеметрия может быть первым шагом к агрессивной монетизации и сместит фокус разработчиков на платных пользователей. Будут внедрятся только те фичи, которые прибыльны, а не те, которые полезны пользователям.
- Телеметрия должна быть прозрачной, но публичные данные о статистике пользователей могут быть использованы против вашей компании.
- Если продукт был успешен X лет без телеметрии, то действительно ли она необходима? Как-то же справлялись разрабы раньше без нее.
- Всегда будут люди, которые откажутся от телеметрии, и это чаще продвинутые пользователи. А еще телеметрию может быть заблокирована блокировщиком рекламы, если у вас веб-приложение.
- Телеметрия — потенциальная брешь в системе, как со стороны клиента, так и сервера, и увеличивает поверхность атаки.
- Телеметрия — это доп. расход ресурсов, как пользователя, так и компании. Если все программы будут собирать телеметрию, приведет ли это к чему-то хорошему? Чем ваше приложение такое особенное, чтобы делать это?
- Если решение принимается децентрализовано, сообществом, то будет достаточно мнений, чтобы обойтись без телеметрии.
Итого
В основном телеметрию хотят разработчики, а пользователи (которые высказывают свое мнение) в целом против нее.
Большинству пользователей на все выше скорее всего насрать. Приложения на телефоне и какой-нибудь поиск гугла или хром собирают о вас столько данных, что вас можно будет заменить роботом и никто не заметит. Ну и, увы, как-то странно верить, что в мире осталась какая-то приватность, когда каждое устройство и программа что-нибудь собирают, улицы обвешаны камерами, а товар в бесплатных сервисах — это вы.
Доверие очень тяжело заслужить и легко потерять (“и невозможно забыть”:) ). Если прям надо-надо телеметрию, то лучше быть максимально прозрачными и следовать советам от разработчиков Go, а насчет opt-in или opt-out — думать, что лучше подходит. Но в первую очередь стоит подумать, а точно ли она нужна вообще (скорее всего нет). С ее помощью можно получить какие-то данные, но у них может быть сомнительное качество.
The more these things are happening and the more I read about it, the more I understand and agree with Richard Stallman.