`

СПЕЦІАЛЬНІ
ПАРТНЕРИ
ПРОЕКТУ

Чи використовує ваша компанія ChatGPT в роботі?

BEST CIO

Определение наиболее профессиональных ИТ-управленцев, лидеров и экспертов в своих отраслях

Человек года

Кто внес наибольший вклад в развитие украинского ИТ-рынка.

Продукт года

Награды «Продукт года» еженедельника «Компьютерное обозрение» за наиболее выдающиеся ИТ-товары

 

Древняя, новая, будущая

0 
 

Виртуальные инструменты Tcl "разложены" в системе по десяти "ящикам" (раз уж используется "инструментальная" аналогия, то будем ее придерживаться). В документации, поставляемой с бинарным дистрибутивом от ActiveState, детальное описание содержимого каждого из "ящиков" дается в разделе "Встроенные команды Tcl" (Tcl Built-In Commands). Инструментальный набор дает в руки разработчику команды для следующих основных категорий:
  • манипулирование переменными и процедурами;
  • управляющие конструкции языка;
  • поддержка списков;
  • операции со строками;
  • операции ввода-вывода;
  • служебные, библиотечные, системно- и платформенно-зависимые операции.
Наше знакомство мы начнем с инструментов, поддерживающих реализацию одной из самых важных абстракций в высокоуровневых языках программирования -- подпрограмм (функций или процедур).

Процедуры Tcl, как и аналогичные конструкции в других языках программирования, предназначены для инкапсуляции некоторых последовательностей команд и введения локальной области видимости для переменных. Объявление процедуры осуществляется с помощью "инструмента-команды" proc, принимающей три параметра, которые заключаются в фигурные скобки (объяснение, для чего это необходимо, будет далее):

proc имя_процедуры список_параметров реализация_или_тело_процедуры

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

Самое время проверить, как же функционируют процедуры Tcl, а заодно на элементарном примере ознакомиться с еще одним важным понятием языка. Итак, наша первая "программа" (вовсе не традиционная "Hello, world!"):

set Glob_Var 0
proc P2 a b
set ::Glob_Var "$a+$b"

P2 3 4
puts Glob_Var=$Glob_Var

В ней объявляется процедура с именем P2, принимающая два параметра (a и b соответственно). "Сложная задача", решаемая P2, -- изменение значения глобальной переменной Glob_Var на строку вида “значение_параметра_a+значение_параметра_b”. C чем, собственно, P2 и справляется. Попробуйте в этом примере убрать подавляющую группировку имени процедуры (фигурные скобки в подстроке “proc P2”) -- ничего не изменится. Теперь замените параметры тестового вызова P2, скажем, так:

P2 (3-5) 4*2

Древняя, новая, будущая
Рис. 1
В данном случае реализация P2 получает от диспетчера интерпретатора Tcl в качестве параметров две строки -- “(3-5)” и “4*2”, и результат будет должным -- значение переменной Glob_Var становится строкой “(3-5)+4*2”, что подтверждается снимком экрана (рис. 1). Обратите внимание на синтаксис вызова процедуры и, вспомнив "адскую механику" работы интерпретатора Tcl, попробуйте самостоятельно с ним разобраться. В дополнение к приведенным сведениям углубленное изучение соответствующих разделов документации целесообразно предварить одной небольшой деталью -- Tcl позволяет описывать процедуры с так называемыми значениями параметров по умолчанию. Это означает, что если процедуре при вызове будет передан неполный список параметров, то никакой исключительной ситуации такое действие программиста не вызовет -- недостающие параметры получат указанные в объявлении процедуры значения. Синтаксис задания значений параметров по умолчанию весьма характерно отражает самостоятельность команды proc в трактовке параметров:

proc имя_процедуры имя_параметра значение_по_умолчанию ... ... тело_процедуры

Под самостоятельностью здесь понимается то, что не являющиеся параметром какой-либо команды строки вида “имя_переменной значение” в Tcl лишены семантики -- это просто строки.

Теперь давайте сконцентрируем внимание на одном-единственном нюансе нашей первой программы, а именно на двух двоеточиях, поставленных перед именем переменной Glob_Var в теле процедуры P2, и рассмотрим базовые инструменты из еще одного набора Tcl, отвечающего за реализацию пространств имен.

Tcl, хотя ему и можно приписать "скриптовую несерьезность" (скриптинг в расхожем понимании подразумевает нечто утилитарное, сделанное на скорую руку), располагает весьма развитыми механизмами, предназначенными специально для поддержки разработки больших приложений. Пространства имен (namespaces) -- один из главных таких механизмов, несмотря даже на то, что namespaces Tcl создавались больше для программиста, чем для среды времени исполнения (runtime) языка. Последнее означает, что во время исполнения никакого дополнительного контроля над пространствами имен Tcl не осуществляет (это связано с динамическим характером языка и ни в коем случае не является ограничением). По сути, пространство имен вводит дополнительную степень упорядоченности в программу, позволяя инкапсулировать процедуры и переменные и открывать доступ к ним посредством квалифицирующих имен. В приведенном ранее примере мы использовали определенное по умолчанию пространство имен глобальных переменных и символ "квалифицирования имени" (“::”) для того, чтобы "дотянуться" из тела процедуры P2 до переменной Glob_Var. Глобальные переменные в Tcl без явного квалифицирования недоступны из процедур, что можно проверить, удалив из примера “::” перед Glob_Var (рис. 2 -- значение Glob_Var после вызова P2 не изменилось). Кроме предопределенного пространства имен, Tcl дает возможность программисту создавать собственные namespaces с помощью "инструмента-команды" с "неочевидным" именем namespace. Надо сказать, что команда namespace в Tcl, как и многие другие инструменты, на самом деле представляет собой настоящий "швейцарский нож" и позволяет осуществлять массу операций с пространствами имен. В общем формате этой команды за специфицирование конкретной операции отвечает первый аргумент:

namespace операция ...список_дополнительных_аргументов...

Как и в случае с proc, фигурные скобки, ограничивающие фрагмент строки “операция”, можно опустить.

Давайте модифицируем первый пример и "упрячем" внешнюю по отношению к функции переменную в отдельное пространство имен Module_1:

namespace eval Module_1
set Mod1_Var 0

proc P2 a b
set Module_1::Mod1_Var "$a+$b"

P2 (3-5) 4*2
puts Mod1_Var=$Module_1::Mod1_Var

В общем, все говорит само за себя -- теперь до переменной из Module_1 удастся из-за пределов этого пространства имен "дотянуться" только с помощью полного имени с явным квалификатором (лучший аналог здесь -- полный путь в файловой системе) в формате пространство_имен::имя_ переменной.

Аналогии с путем в файловой системе подкрепляются и тем, что пространства имен могут быть вложенными и тем самым образовывать иерархии. К вышесказанному остается добавить только основные принципы видимости имен Tcl:
  • все локальные имена -- локальны (будь то внутри процедуры или пространства имен);
  • все глобальные имена (определенные вне пользовательских процедур и пространств имен) доступны вне своей области только посредством квалифицирования имени;
  • по умолчанию в системе есть одно общее неименованное глобальное пространство имен для процедур и переменных;
  • имена процедур и переменных в Tcl могут совпадать -- это не вызывает конфликтов.
Об остальных нюансах детально говорится в сопровождающей документации Tcl.

Теперь, немного зная о процедурах и "модулях" (в принципе, пространства имен можно называть и так), перейдем к способам управления потоком вычислений. В Tcl есть все типовые инструменты этого класса -- команды ветвлений, циклов и т. д. Самая простая среди них -- естественно, "команда-инструмент" ветвления, в общем виде задаваемая строками в формате:

if проверяемое_выражение then код_1 else код_0

"Тикловый" if не сильно отличается от аналогов в других языках -- если результатом выполнения проверяемого выражения является "истина", то выполняется ветка кода код_1, в противном случае программа выбирает код_0. Но отличия в деталях все-таки есть, и они определяются одним важным нюансом, о котором мы говорили в прежних статьях: синтаксис параметров "команд-инструментов" определяется этими самыми командами. В случае с if как раз наблюдается проявление действия этого нюанса, которое, как может показаться, немного нарушает стройность языка. А речь идет, собственно, вот о чем. Коль интерпретатор Tcl ничего не знает о семантике, то для вычисления выражений в инструментальном наборе языка предусмотрена команда expr. И если где-либо в Tcl-программе вы напишете строку “$x == 0”, то ничего, кроме самой написанной строки, не получите. Но если эта же строка будет записана как проверяемое выражение команды if, все будет работать (на самом деле никакого отказа от целостности здесь нет -- фактически скрытый автоматический вызов expr предусмотрен в if просто для удобства программиста). А вот и "реальный" пример:

set Res 0
proc Taste a b
if $a > $b then set ::Res "a>b" else set ::Res "a<=b"

Taste 1 2
puts $Res
Taste 2 1
puts $Res

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

Еще один элементарный пример управляющей конструкции Tcl -- цикл с проверкой в начале, или команда while. Ее формат стандартен:

while булево_выражение тело

равно как и семантика -- программа повторяет выполнение кода тело до тех пор, пока булево выражение сохраняет истинное значение. Естественно, если при первом вызове while булево выражение ложно, то тело не выполнится ни разу. Эта команда наглядно демонстрирует и поясняет один исключительно важный момент, общий для всех инструментов Tcl. Но без примера здесь не обойтись. Итак, вот простейшая Tcl-процедура, которая 10 раз выводит неизбежные слова "Hello, World!":

proc Hello_World
set i 0 ;
while $i<10
puts "Hello, World!"
incr i



Из неизвестного в ней -- только команда incr, осуществляющая инкремент (увеличение на 1) значения переменной, имя которой передается в качестве параметра. Все прозрачно, понятно и действует именно так, как надо. А теперь давайте попробуем отказаться от подавляющей подстановки группировки при вызове команды while, т. е. запишем третью строку так:

while $i<10

Модифицированная программа начинает вести себя, мягко говоря, странно -- она входит в бесконечный цикл, при этом счетчик цикла, переменная i, честно изменяет свое значение. Причину такого поведения можно отыскать в принципах действия интерпретатора Tcl, который всего лишь один раз формирует параметры для "инструмента-команды" while, после чего диспетчер также всего лишь один раз передает эти параметры реализации команды. Сама цикличность -- дело сугубо команды while. И если не "задавить" подстановки фигурными скобками, то интерпретатор сделает то, что должен сделать, -- совершенно искренне и всего один раз заменит вычисляемое выражение "значение переменной i меньше 10" ($i<10), истинное или ложное в зависимости от значения i, на всегда истинное константное выражение "0 меньше 10"! Ведь перед выполнением команды while значение i равнялось нулю... и как бы теперь ни менялось значение i, команде никак не удастся изменить константное выражение, переданное ей интерпретатором. Именно из-за тонких нюансов, которые могут порождаться влиянием этапа подстановки интерпретатора на семантику (поведение) реализаций команд, настоятельно рекомендуется группировать аргументы всех команд посредством фигурных скобок!

С остальными управляющими конструкциями Tcl вы ознакомитесь самостоятельно -- благо, сопровождающая систему документация очень хороша и ничего особо сложного в этих конструкциях нет. Главное -- не забывать пример с while и подавляющей подстановки группировкой.

Мы же немного внимания уделим второму важнейшему классу инструментов -- командам файловых операций и операций ввода--вывода. Следует сразу отметить, что современные реализации Tcl поддерживают идеологию виртуальных файловых систем. На деле это означает, что один и тот же грамотно разработанный Tcl-скрипт, оперирующий файловой системой (ФС), без модификаций способен работать как непосредственно с файлами "реальной" ФС, так и со смонтированными виртуальными ФС, на которые отображаются, например, доступные по ftp- и http-протоколам удаленные ФС, содержимое zip-архивов и tar-файлов. Такой подход, по сути, является виртуализацией некоторых идей ОС Unix (как то монтируемых файловых систем и т. д.).

Перечень доступных инструментов для работы с ФС в Tcl, на первый взгляд, не обширен -- в него входят всего 15 команд. Но каждая из них весьма многофункциональна (Tcl буквально пропитана идеологическим наследием ОС Unix). Так, команда file, оперирующая именами и атрибутами файлов, позволяет выполнять более 30 операций! Воспользуемся только одним коротким осмысленным примером, чтобы продемонстрировать возможности менее чем 1/15 операций команды file:

proc newer file_1 file_2
if ![file exists $file_1] return 1
if ![file exists $file_2] return 1
expr [file mtime $file_1] > [file mtime $file_2]


Древняя, новая, будущая
Рис. 2
Древняя, новая, будущая
Рис. 3
Эта процедура сравнивает время последней модификации двух файлов, пути к которым передаются ей в качестве параметров. Начнем с главной особенности -- newer будет функционировать без модификаций на всех поддерживаемых Tcl платформах, у которых правила описания путевых имен файлов существенно отличаются (например, принятые в Unix “/каталог1/каталог2/...” или Windows-модель “символ_устройства:\каталог1\каталог2\....”). Теперь проведем построчный разбор "пока неизвестного": во второй строке используется вызов команды file с параметрами exist и путем файла -- в этом случае file проверяет наличие файла. Взятое в квадратные скобки выражение подменяется результатом его вычисления, инвертируется (оператор “!”), и, если файл не существует, процедура завершается с кодом возврата “1” (в реальных программах так лучше не делать -- осмысленное сообщение о причинах завершения процедуры просто необходимо). Третья строка полностью аналогична первой, за исключением того, что в ней проверяется наличие второго файла. Если поток исполнения достигает четвертой строки кода, это означает, что оба анализируемых файла существуют. В четвертой строке используются вызовы file с параметром mtime -- согласно документации, в этом случае file возвращает строку, содержащую время последней модификации файла. Здесь очень важна одна деталь -- формат представления времени модификации соответствует стандарту Posix (количество секунд от 1 января 1970 г.) и кросс-платформенно реализован. То есть под управлением разных ОС (Unix, Windows и т. д.) результат вызова "file mtime..." может быть одинаковым (в принципе, дата, от которой ведется отсчет, оставлена на откуп платформенной зависимости).

По большому счету, на этом пора и остановиться (естественно, не насовсем, "бесконечная история" не заканчивается). Tcl, как и любое инструментальное средство, без серьезной самостоятельной работы изучить невозможно.

В завершение целесообразно расширить перечень полезных как при изучении, так и при практическом программировании на Tcl программ еще одной интегрированной средой -- RamDebugger. Эта кросс-платформенная, сугубо "тикловая" разработка включает в себя очень удобный полнофункциональный отладчик, прекрасно документирована и фактически не требует инсталляции (естественно, если на вашей рабочей машине правильно установлена и нормально функционирует одна из бинарных версий Tcl/Tk).


Один психоделический шаблон применения namespace

Предположим, вы разрабатываете большую Tcl-программу, состоящую из множества "модулей" (пространств имен). И для всего этого множества существует перечень общих процедур, используемых каждым "модулем". Естественно, Tcl как серьезный язык в наборе опций команды namespace располагает операциями импорта и экспорта имен. Но давайте пока не думать об очевидных путях, а обратим внимание на нечто неочевидное -- попробуем сделать так, чтобы можно было "добираться", например, до значений локальных переменных другого "модуля", не используя импорт--экспорт, а только "полные пути" к этим переменным (квалифицированные имена). Вы думаете, в таком случае придется создавать отдельную реализацию каждой "общей" процедуры для каждого "обслуживаемого" namespace? Или всякий раз при передаче параметров вводить длинные-предлинные полностью квалифицированные имена? Вовсе нет. Вот психоделический пример-идиома, показывающий, как это можно сделать.

Древняя, новая, будущая
Рис. 4
Почти ничего, кроме "букета" из уже обсужденного в нашей "бесконечной истории", в этом примере нет, но несколько слов он все же заслуживает. Итак, здесь приведена реализация принципа доступа к локальным переменным одного пространства имен из другого без операций импорта--экспорта имен. Более того -- общая процедура возведения в квадрат X2 (она расположена в общем "модуле" Module_Common) принимает не значения, а... полные квалифицированные имена переменных! Впрочем, по-порядку. В строке 2 создается локальная в данном пространстве имен переменная X, ее значение (10) мы хотим прочитать и модифицировать извне. Со строки 8 начинается описание процедуры, посредством которой, собственно, и будет осуществляться такая модификация. Строка 3 не случайно выделена красным цветом -- это совершенно "тикловая" идиоматическая конструкция, формирующая для имени переменной X строку с полным квалифицированным именем. Команда namespace с опцией current (единственная из нерассмотренного в этой и предыдущих статьях) возвращает полный квалификатор для данного пространства имен (в нашем примере -- строку “::Module_1”). Раз эта команда заключена в квадратные скобки, значит, на этапе подстановки она заменяется результатом ее выполнения и в переменную fX записывается строка “::Module_1::X”. Именно эту строку мы передаем в качестве параметра функции возведения в квадрат X2 в строке 4 (заметьте, что для вызова данной функции мы указываем ее полностью квалифицированное имя). В теле X2 использована еще одна идиома -- трансформирующая имя в значение локальной переменной. Это сделано в строке 9, где на этапе подстановки сначала $X заменяется на “::Module_1::X”, а затем команда set ::Module_1::X -- на результат ее выполнения, который, очевидно, равен 10. И наконец, в строке 10 вычисляется произведение 10x10 и записывается в переменную с именем “::Module_1::” (еще одна идиома). В результате в переменной X "модуля" Module_1 будет ожидаемая строка 100, а мы построили способ написания процедур, для которых не требуется импорт--экспорт имен, -- иногда, в больших программах, они бывают весьма полезны.

Ready, set, buy! Посібник для початківців - як придбати Copilot для Microsoft 365

0 
 

Напечатать Отправить другу

Читайте также

 

Ukraine

 

  •  Home  •  Ринок  •  IТ-директор  •  CloudComputing  •  Hard  •  Soft  •  Мережі  •  Безпека  •  Наука  •  IoT