Обновить
4K+
9
Максим Яковлев@PunGy

Senior Software Engineer

23
Рейтинг
Отправить сообщение

Спасибо большое за такой тёплый ответ!

Статья имеет сильный уклон в практику и попытку "продать" этот язык - это моя ошибка. Комментарии вполне справедливые, с учётом того, на чём в статье я акцентирую внимание.

Я создал язык не чтобы покрыть эту нишу и представить миру новый инструмент - мне просто хотелось создать язык, который удовлетворяет моим представлениям прекрасного и интересам, и при этом решает прикладную для меня проблему) Это не первый и не последний мой язык - но первый, который действительно можно к чему-то применить.

Меня сводит с ума простота Lisp - чистое вычисление, минимальное количество правил. Но так же привлекает структурность Haskell, что код пишется как таблица связей и трансформаций, которые работают по фундаментальным математическим правилам.

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

Этот язык - моё видение как Lisp можно сделать структурнее. Shik - это лишь первая итерация развития этого языка. В разработке так же находится другой язык - denshik. Он будет попыткой объединить dependent types с динамикой и простотой Lisp. Shik - это сокращение от denshik (денщик), его урезанная версия)

Всё верно, и спасибо за развёрнутый разбор - я прям углубил своё понимание bash.

Я не спорю с тем, что у каждого решения в bash есть причина. [ ] vs [[ ]], --, word splitting - всё это имеет логику и контекст. Статья не про то, что bash плохой и его надо чем-то заменить (это пытаются делать другие инструменты, такие как zsh/fish/nushell, от которых я заранее в статье открестился) - а про то, что его модель (запуск программ, их композиция через пайпинг, fork/exec) оптимизирована под одни задачи, а мне нужен инструмент под другие. Я перестал писать bash-скрипты - но не перестал им пользоваться.

Shik не пытается починить bash. Это отдельный язык, максимально практичный для своей задачи, который просто удобно использовать рядом с bash, особенно людям, которым нравится такой функциональный/декларативный стиль.

Сделать язык супер-производительным у меня не было цели, однако там где можно и не муторно старался сделать оптимальней.

Замерил на проекте самого shik (~5500 строк Rust, 30 .rs файлов). Задача — подсчёт суммарного количества строк.

Shik:

file.glob :./src/**/*.rs $>
  list.map (file.read-lines #> list.len) $>
  list.sum $> print

Python:

from pathlib import Path
print(sum(len(f.read_text().splitlines()) for f in Path('./src').rglob('*.rs')))

Bash:

find ./src -name '*.rs' -exec cat {} + | wc -l

Результат (hyperfine --warmup 3 -N, macOS, Apple Silicon):

  • Shik:

    • Время: 4.4 ms

    • Память: 2.6 МБ

  • Bash:

    • Время: 9.1 ms

    • Память: 2.1 МБ

  • Python:

    • Время: 30.3 ms

    • Память: 12 МБ

Startup Rust-бинарника делает своё дело — нет тяжёлого интерпретатора, нет tracing GC - управление памятью через Rc/RefCell, все встроенные функции на нативном Rust, для glob поиска используется библиотека. На больших объёмах данных bash+coreutils скорее всего обгонит — потоковая обработка на C vs чтение файлов целиком в память. Но для типичных скриптовых задач (десятки-сотни файлов) разница не ощутима.

Но это актуально лишь для IO-bound операций, где всё решается нативным Rust кодом. Если какая-нибудь алгоритмическая задача, например, задачка с подсчётом суммы выигрыша на костях, то реализация на Shik в 10 раз медленней чем на Python(Python 30ms, Shik 323ms) - тут уже вступает в роль CPython и байткод, Shik же реализован как TreeWalk интерпретатор, без каких-то сложных оптимизаций.

Если интересно - вот реализация на Shik.

Подробнее про внутренности и процесс разработки — планирую отдельную статью.

Благодарю за развёрнутый комментарий!
Да, Python достаточно хорошо читается, и вокруг него большая инфраструктура. Но мне, для этой задачи, не подходит его императивный стиль - вложенные циклы и условия вместо цепочки трансформаций, и плохой discoverability - в Shik достаточно набрать help file. чтобы увидеть все функции для работы с файлами...

По поводу Nushell - у них мощная идея, но это другая ниша.
Во-первых - оболочка. Это не значит что ей нельзя пользоваться совместно с bash, а что она в первую очередь должна быть удобна навигации по системе и вызова системных команд.
Во-вторых - в ней используется полностью отдельный DSL для написания сценариев только в терминале. Shik - Lisp-flavor с правилами аппликации как в Haskell, если человек знаком с этими языками, то я уверен, что овладение Shik на 100% займет меньше часа. Это именно то, что я хотел - мне просто было нужно что-то проще чем JS или Python, с максимально примитивными, но мощными правилами. Язык Nu же крайне специфичен, и пример сгенерённый ИИ идеально это демонстрирует - снова флаги, магические переменные и свойства, уже имеет deprecated синтаксис, вызов системных программ перемешан с конструкциями языка, так ещё и не работает

Переместить все файлы в topics, внутри которых есть строка "- links"
Переместить все файлы в topics, внутри которых есть строка "- links"

Справедливо конечно. Но процесс тот же: формулируешь промпт -> ждёшь -> запускаешь -> не работает -> уточняешь -> ждёшь снова. Иногда быстрее гугла - но это всё ещё цикл "попроси кого-то написать код за тебя и надейся, что он правильный".

Shik про другое - про то, чтобы ты сам мог написать скрипт за 30 секунд, понимая каждую строку, и не потратив на изучение языка несколько недель. Без посредников - не нужно выходить из терминала. Всё прямо тут, в REPL, через help, здесь и сейчас.

Но, замечу вот что - у этого языка вполне себе прямой посыл: "удобство использование в REPL и для планирования сценариев".
Например - у меня есть набор коммитов которые надо черри-пикнуть в ветку. И я возьму и спокойно напишу:

[:xfsc1 :fac3r :cx5nj :00dser] $>
  list.iterate fn [commit] shell "git cherry-pick {commit}"

Это не займёт у меня хоть какой-то когнитивной нагрузки, с учётом что язык знаком. Использовать ИИ, гуглить, пытаться написать это на bash или fish - да куда проще будет уже просто скопипастить пять раз `git cherry-pick {commit}` с конкретным коммитом. Вот для этого я и создал этот язык)

SQL-подобные запросы к файловой системе и структурированным данным — это довольно точно описывает Nushell! Там данные идут по пайпу как таблицы, можно фильтровать, сортировать, группировать — почти как SELECT/WHERE/ORDER BY, только в терминале. Думаю вам будет интересно посмотреть в его сторону)

Именно об этом и речь в статье - я даже отдельно разбираю однострочник grep -l | xargs mv. Да, для конкретной задачи "посчитать строки" можно собрать конвейер из find/cat/wc. А завтра задача чуть другая — и снова гуглить, какой флаг у find для maxdepth, нужен ли + или \; в -exec.

Shik не про то, что bash не может. Bash может всё. Shik про то, чтобы выражать цепочки действий единообразно - одними и теми же правилами, без зоопарка утилит и флагов. Если ежедневная работа с этим связана - то конечно естественно их все знать. Но если нужно сделать что-то такое раз в неделю - гуглёж из раза в раз утомляет

И о чём этот пример говорит? Что spread оператор сбрасывает аннотации?

class Animal {
  genus: number = 0
}
class Cat extends Animal {
  clawSize: number = 0
}
type ContravariantMethod<in V> = {
    process(v: V): void;
}

declare let _processCat_: ContravariantMethod<Cat>
declare let _processAnimal_: ContravariantMethod<Animal>

let processAnimal = (animal: Animal) => {}
let processCat = (cat: Cat) => {}

_processAnimal_ = _processCat_ // Ошибка
_processCat_ = _processAnimal_ // Ок
_processAnimal_ = { ..._processCat_ } // Снова ок, но потому что typescript сбросил аннотацию

processAnimal = processCat // Ошибка
processCat = processAnimal // Ок
processAnimal = { ...processCat } // Всё ещё ошибка, потому что функции всегда контравариантны

Вот буквально вырезка из этой же документации, которая приводит в пример эту же ситуацию, и прямо заявляет, что "measurement can be inaccurate"

Because variance is a naturally emergent property of structural types, TypeScript automatically infers the variance of every generic type. In extremely rare cases involving certain kinds of circular types, this measurement can be inaccurate. If this happens, you can add a variance annotation to a type parameter to force a particular variance:

// Contravariant annotation
interface Consumer<in T> {
  consume: (arg: T) => void;
}

Прекрасно сказано: "Никогда не используйте аннотации вариантности которые не совпадают со структурной вариантностью типа".

По вашему мой пример выше это НАРУШЕНИЕ структурной вариантности? Изменение вариантность с бивариантной, которую в области вариантности можно воспринять как any, которая в примере выше создают runtime ошибку, на контравариантность - стандартное поведение TypeScript с функциями, что от ошибки избавляет.

Какое тогда по вашему мнению правильное использование аннотаций, которое не меняет тайпчекер?

Документация предостерегает к необдуманному использованию, это верно. Однако, в случае что я привёл, прямо видно проблему которую создают бивариантные методы. Следующий код, являясь переписанной на классы аналогией секции про контраварианты, ошибку больше не показывает:
TypeScript playground

class AnimalFood {
  protein: number = 0
}
class CatFood extends AnimalFood {
  fishness: number = 0
}

abstract class Processor {
  abstract process(food: AnimalFood): void;
}
class AnimalProcessor {
  process(food: AnimalFood) {}
}
class CatProcessor {
  process(food: CatFood) {}
}

const animalProcessor = new AnimalProcessor()
const catProcessor = new CatProcessor()

/**
 * Перед подачей обработаем корм
 */
function serveAnimalFood(processor: AnimalProcessor): void {
    const food = new AnimalFood();
    processor.process(food);
}
function serveCatFood(processor: CatProcessor): void {
    const food = new CatFood();
    processor.process(food);
}

// оказывается, теперь всем по нраву рыбный вкус
serveAnimalFood(catProcessor);

serveCatFood(animalProcessor); 

Так что аннотации вариантности нужны именно для таких случаев. Тут in аннотация увеличит безопасность кода и предостережет от ошибки. Для этого они и созданы.

Информация

В рейтинге
358-й
Откуда
Москва, Москва и Московская обл., Россия
Дата рождения
Зарегистрирован
Активность

Специализация

Фронтенд разработчик, Архитектор программного обеспечения
Ведущий