Предыстория #

Где-то в конце октября захотелось мне добавить на сайт поиск, даже для этого на 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 ) : string
  • fun string/replace-all( s : string, regex : regex, repl : string, atmost : ? int ) : string
  • fun 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, много параллелей с ним. Крутые фишки так и не смог полноценно оценить.