Переходим на субстанции потяжелее — 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 ) : 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, много параллелей с ним. Крутые фишки так и не смог полноценно оценить.