Есть ли что-то вроде замыканий для zsh?

Есть ли что-то вроде замыканий для zsh?

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

Поэтому я клонирую тему по умолчанию и вдохновляюсьэта почта(который я тоже использую для обучения), я делаю что-то вроде этого (позже добавлю цвета):

function precmd {
    local cwd="${(%):-[%~]}"
    local who_where="${(%):-%n@%m}"
    local git_info=${(%)$(git_prompt_info)}
    local right_prompt="     $git_info [$who_where]"
    local left_prompt="${(r:(($COLUMNS - ${#${right_prompt}})):: :)cwd}"

    echo "$left_prompt$right_prompt"
}

И это работает. Но я не могу не задаться вопросом: zsh определяет все эти переменные каждый раз, когда вызывается precmd?

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

В качестве примечания, и только если это имеет отношение к теме, что означает «иметь загруженную функцию»?

решение1

В Zsh нет ничего похожего на замыкания, пакеты или пространства имен. В Zsh не хватает кучи вещей, необходимых для настоящих замыканий:

  • Функции не являются первоклассными. Вы не можете передавать функции в качестве аргументов другим функциям, и функции не могут возвращать другие функции. (Вы можете передаватьимяфункции для вызова, но это не то же самое, что передача самой функции).

  • Вы не можете иметь вложенных функций. Все функции в zsh являются глобальными. Вы должны добавлять префиксы к именам своих функций, чтобы избежать конфликтов. Обратите особое внимание, что функции будут затенять внешние программы с тем же именем. Если у вас есть функция с именем ls, она будет вызвана вместо программы ls. Это может быть полезно, если только вы не сделаете это случайно.

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

  • Зшделаетимеют анонимные функции, но без всех этих других вещей они бесполезны.

Таким образом, лучшее, что вы можете сделать, — это снабдить префиксами все свои функции и глобальные переменные.

Я также хочу отметить, что вам следует определить это precmdследующим образом:

% autoload -Uz add-zsh-hook
% add-zsh-hook precmd my_precmd_function

add-zsh-hookпозволяет вам подключить свою функцию, precmdне перезаписывая при этом какие-либо другие функции, которые также могут захотеть подключиться precmd.

Что значит иметь загруженную функцию — это отдельный вопрос. Zsh имеет функцию автозагрузки, которая загружает функции с диска только тогда, когда они фактически вызываются. Когда вы это делаете autoload -Uz foobar, это делает названную функцию foobarдоступной для вызова. Когда вы фактически вызываете foobar, это загружает определение с диска.

решение2

Нет, замыкания слишком сложны для zsh. Zsh разработан для интерпретации небольших скриптов, которые не так уж далеки от прямого взаимодействия. В нем нет причудливых языковых возможностей, которые очень полезны для программирования в больших масштабах, но менее полезны для небольших задач, для которых обычно используются оболочки.

Обратите внимание, что если бы существовала некая форма замыкания, которая позволяла бы вычислять значения переменных заранее один раз и навсегда, а затем сохранять их, то значения не обновлялись бы, если бы что-то изменилось и информация стала недействительной.

$git_infoи производные переменные могут измениться в любое время из-за изменения файла, добавленного в git или в репозиторий git. Так что их в любом случае нужно пересчитывать каждый раз.

Вы можете кэшировать значения cwdи who_whereв глобальной переменной, поскольку они не изменяются при нормальной работе. cwdизменяется при изменении текущего каталога, поэтому его нужно будет обновить из chpwd. Однако эти переменные вычисляются очень быстро, поэтому нет смысла беспокоиться. Дорогостоящее вычисление здесь выполняется git_prompt_info, и это может измениться в любой момент.

Когда вы отображаете информацию между каждой командой, может быть лучшей идеей поместить ее как часть приглашения ( PS1или psvarмассива). Zsh знает, что он должен повторно отображать приглашение в различных обстоятельствах, тогда как он ничего не знает о том, что вы печатаете из precmd.

решение3

Да, эти переменные (пере)определяются каждый раз, когда вы вызываете функцию.

Если вы хотите инициализировать их только один раз, вы можете просто переместить их на верхний уровень, за пределы функции.

решение4

Чтобы иметь замыкания, язык должен иметь возможность манипулировать функциями как элементами или объектами, что невозможно в zsh, за исключением строк и evalвстроенных функций (как в других оболочках). Но это очень ограничено, поскольку вам нужно обрабатывать все низкоуровневые вещи самостоятельно (например, кавычки). Однако, когда аргументы не имеют специальных символов (таким образом, не нужно обрабатывать кавычки), можно легко сделать некоторые простые вещи, используя эту идею, и в zsh анонимные функции могут немного помочь. Например, чтобы определить функции, которые вычисляют a*x+b*y, где aи bявляются константами, указанными в определении функции, xи yявляются аргументами функции:

mk_ax_plus_by() { echo "() { echo \$((($1)*(\$1)+($2)*(\$2))) }" }

fct_2x_plus_3y=$(mk_ax_plus_by 2 3)
fct_5x_plus_7y=$(mk_ax_plus_by 5 7)

Итак, есть функция fct_2x_plus_3y, которая вычисляет, 2*x+3*yи функция fct_5x_plus_7y, которая вычисляет 5*x+7*y(обратите внимание, что я выбрал имена функций только для удобства чтения, это могут быть любые имена, и вам даже не нужно хранить содержимое в переменных). Обратите внимание также, что это на самом деле строки (не функции в терминологии оболочки), но они будут вести себя как функции со evalвстроенной функцией. Пример использования:

% eval $fct_2x_plus_3y 4 9
35
% eval $fct_5x_plus_7y 4 9
83

как 2*4+3*9дает 35 и 5*4+7*9дает 83.

Обратите внимание, что в отличие от функциональных языков, здесь нужно различать параметры, которые приходят из окружения (без кавычек) и параметры определенной функции (в кавычках), т.е. те, которые будут оцениваться только при вызове функции. Но можно изменить реализацию так, чтобы она больше походила на функциональные языки, и использование будет таким же:

mk_ax_plus_by()
{
  local x='$1' y='$2'
  echo "() { echo \$((($1)*($x)+($2)*($y))) }"
}

Связанный контент