Программирование в Scilab Микаэль Боден Micha
Скачать 1.35 Mb.
|
e r r o r ( msg )Модуль основан на установке связи ключей и значений. • Каждый ключ соответствует полю списка параметров и сохраняется как строковая переменная. Список доступных ключей определён разработчиком функции. Нет огра- ничений на количество ключей. • Тип каждого значения зависит от особых требований алгоритма. На самом деле любой тип значения может быть сохранён в структуре данных, включая (но не ограничива- ясь) матрицу чисел типа double, строковые переменные, многочлены и т. д. Следовательно, эта структура данных чрезвычайно гибка, как мы и увидим далее. Для того, чтобы понять модуль parameters, мы можем выделить ситуации между двумя точками зрения: • точка зрения пользователя, • точка зрения разработчика. Следовательно, в дальнейшем обсуждении мы будем представлять первую, которая имеет место, когда мы хотим использовать модуль parameters для того, чтобы вызвать функцию. Затем мы обсудим чт´ о разработчик должен сделать для того, чтобы управлять необязатель- ными параметрами. Для того, чтобы сделать обсуждение как можно более применимым, мы возьмём при- мер алгоритма сортировки и изучим его в этом разделе. Мы рассматриваем функцию mergesort, которая определяет алгоритм сортировки, основанный на комбинации рекур- сивной сортировки и слияния. Функция mergesort связана со следующими вызывающими последовательностями y = m e r g e s o r t ( x ) y = m e r g e s o r t ( x , p a r a m s ) где x — матрица значений типа double, которые необходимо рассортировать, params — список параметров, и y — матрица рассортированных значений типа double. Действительная реали- зация функции mergesort будет определена позднее в этом разделе. Переменная params — это список параметров, который определяет два параметра: • direction, логическое значение, истина для возрастающего порядка и ложь — для убывающего порядка (по умолчанию direction = %t), • compfun, функция, которая определяет функцию сравнения (по умолчанию compfun = compfun_default). Функция compfun_default — функция сравнения по умолчанию, будет представлена позднее в этом разделе. Сначала мы рассмотрим точку зрения пользователя и представим пример использо- вания функций из модуля parameters. Мы хотим отсортировать матрицу чисел в порядке возрастания. В следующем примере, мы вызываем функцию init_param, которая создаёт пустой список параметров params. Затем, мы добавим ключ "direction" к списку пара- метров. Наконец, мы вызовем функцию mergesort с переменной params в качестве второго входного аргумента. 76 p a r a m s = a d d _ p a r a m ( params , " d i r e c t i o n " , %f ); x = [4 5 1 6 2 3] ’; y = m e r g e s o r t ( x , p a r a m s ); Теперь рассмотрим точку зрения разработчика и проанализируем команды, которые могут быть использованы в теле функции mergesort. В следующем примере мы вызы- ваем функцию get_param для того, чтобы получить значения, соответствующие ключу "direction". Мы передаём значение %t функции get_param, которое является значением, используемым по умолчанию в случае, когда этот ключ не определён. - - > d i r e c t i o n = g e t _ p a r a m ( params , " d i r e c t i o n " , %t ) d i r e c t i o n = F Поскольку ключ "direction" определён и является ложью, то мы просто получаем назад наше значение. Мы можем использовать расширенную вызывающую последовательность функции get_param с выходным аргументом err. Переменная err равна истине, если во время обработки аргумента произошла ошибка. - - >[ d i r e c t i o n , err ] = g e t _ p a r a m ( params , " d i r e c t i o n " , %t ) err = F d i r e c t i o n = F В нашем случае никакой ошибки не произошло, поэтому переменная err имеет значение ложь. Мы можем анализировать другие комбинации событий. Например, давайте рассмотрим ситуацию, когда пользователь определяет только пустой список параметров, как показано ниже. p a r a m s = i n i t _ p a r a m (); В этом случае, в теле функции mergesort прямой вызов функции get_param формирует предупреждение. - - > d i r e c t i o n = g e t _ p a r a m ( params , " d i r e c t i o n " , %t ) ВНИМАНИЕ: g e t _ p a r a m : параметр direction не определен d i r e c t i o n = T Предупреждение указывает, что ключ "direction" не определён, так что возвращается зна- чение по умолчанию %t. Если мы используем дополнительный выходной аргумент err, то предупреждений больше нет, но переменная err становится истиной. - - >[ d i r e c t i o n , err ] = g e t _ p a r a m ( params , " d i r e c t i o n " , %t ) err = T d i r e c t i o n = T Как и в последнем примере, давайте рассмотрим случай, когда пользователь опреде- ляет переменную params как пустую матрицу. p a r a m s = []; Как мы увидим позднее в этом разделе, этот случай может произойти когда мы хотим использовать значения по умолчанию. Следующий пример показывает, что ошибка форми- руется при вызове функции get_param. 77 [ d i r e c t i o n , err ] = g e t _ p a r a m ( params , " d i r e c t i o n " , %t ) [ compfun , err ] = g e t _ p a r a m ( params , " c o m p f u n " , c o m p f u n _ d e f a u l t ) y = m e r g e s o r t r e ( x , d i r e c t i o n , c o m p f u n ) e n d f u n c t i o n Следующая функция mergesortre реализует рекурсивный алгоритм слияния-сорти- ровки. Алгоритм делить матрицу x на две части x(1:m) и x(m+1:n), где m — целое число в середине между 1 и n. Как только отсортировали с помощью рекурсивного вызова, мы объединяем две упорядоченные части обратно в x. f u n c t i o n x = m e r g e s o r t r e ( x , d i r e c t i o n , c o m p f u n ) n = l e n g t h ( x ) i n d i c e s = 1 : n if ( n > 1 ) t h e n m = f l o o r ( n /2) p = n - m x1 = m e r g e s o r t r e ( x (1: m ) , d i r e c t i o n , c o m p f u n ) x2 = m e r g e s o r t r e ( x ( m +1: n ) , d i r e c t i o n , c o m p f u n ) x = m e r g e ( x1 , x2 , d i r e c t i o n , c o m p f u n ) end e n d f u n c t i o n Заметим, что функция mergesorte не вызывает саму себя. Вместо этого, функция mergesortre, которая на самом деле выполняет алгоритм рекурсивно, имеет фиксированное количество входных аргументов. Это позволяет уменьшить накладные расходы, вызванные обработкой необязательных переменных. Следующая функция merge объединяет свои два отсортированных входных аргумента x1 и x2 в свой отсортированный выходной аргумент x. Операция сравнения выполняется функцией compfun. В случае, когда аргумент direction — ложь, мы меняем знак пере- менной order, так, чтобы порядок стал «убывающим» вместо «возрастающего» порядка по умолчанию. f u n c t i o n x = m e r g e ( x1 , x2 , d i r e c t i o n , c o m p f u n ) n1 = l e n g t h ( x1 ) n2 = l e n g t h ( x2 ) n = n1 + n2 x = [] i = 1 i1 = 1 i2 = 1 for i = 1: n o r d e r = c o m p f u n ( x1 ( i1 ) , x2 ( i2 ) ) if ( d i r e c t i o n ) t h e n o r d e r = - o r d e r end if ( order <=0 ) t h e n x ( i ) = x1 ( i1 ) i1 = i1 +1 if ( i1 > m ) t h e n x ( i +1: n ) = x2 ( i2 : p ) b r e a k end e l s e x ( i ) = x2 ( i2 ) i2 = i2 +1 79 b r e a k end end end e n d f u n c t i o n Следующая функция compfun_default является функцией сравнения по умолчанию. Она возвращает order=-1 если x f u n c t i o n o r d e r = c o m p f u n _ d e f a u l t ( x , y ) if ( x < y ) t h e n o r d e r = -1 e l s e i f ( x == y ) t h e n o r d e r = 0 e l s e o r d e r = 1 end e n d f u n c t i o n Теперь проанализируем поведение функции mergesort вызывая её с особыми входными параметрами. В следующем примере мы сортируем матрицу scivarx, содержащую целый числа с плавающей запятой в порядке возрастания. - - > m e r g e s o r t ( x ) ’ ans = 1. 2. 3. 4. 5. 6. В следующем примере мы устанавливаем параметр direction в %f, так что порядок сорти- рованной матрицы убывающий. - - > p a r a m s = i n i t _ p a r a m (); - - > p a r a m s = a d d _ p a r a m ( params , " d i r e c t i o n " , %f ); - - > m e r g e s o r t ( x , p a r a m s ) ’ ans = 6. 5. 4. 3. 2. 1. Теперь мы хотим настроить функцию сравнения так, чтобы мы могли менять поря- док сортированной матрицы. В следующем примере мы определяем функцию сравнения mycompfun, которая позволяет разделять чётные и нечётные целые числа с плавающей за- пятой. f u n c t i o n o r d e r = m y c o m p f u n ( x , y ) if ( m o d u l o ( x ,2) == 0 & m o d u l o ( y ,2) == 1 ) t h e n // чётное < нечётного o r d e r = -1 e l s e i f ( m o d u l o ( x ,2) == 1 & m o d u l o ( y ,2) == 0 ) t h e n // чётное > нечётного o r d e r = 1 e l s e // 1 <3 or 2 <4 if ( x < y ) t h e n o r d e r = -1 e l s e i f ( x == y ) t h e n o r d e r = 0 e l s e o r d e r = 1 end 80 Мы можем настроить переменную params так, чтобы функция сортировки использовала на- шу настраиваемую функцию сравнения mycompfun вместо функции сравнения по умолчанию compfun_default. - - > p a r a m s = i n i t _ p a r a m (); - - > p a r a m s = a d d _ p a r a m ( params , " c o m p f u n " , m y c o m p f u n ); - - > m e r g e s o r t ( [4 5 1 6 2 3] ’ , p a r a m s ) ’ ans = 2. 4. 6. 1. 3. 5. Мы не настроили параметр direction, так что был использован порядок по умолчанию «возрастающий». Как мы можем видеть в выходном аргументе, чётные числа, как ожида- лось, идут перед нечётными. 4.4.3 Проблемы с модулем parameters У модуля parameters есть проблема, которая может заставить пользователя рассмат- ривать значения по умолчанию вместо ожидаемых. Модуль parameters не защищает поль- зователя от неожиданных полей, что может произойти, если мы сделаем ошибку в имени ключа при настройке параметра. В этом разделе мы покажем как проблема может появить- ся с точки зрения пользователя. С точки зрения разработчика мы представляем функцию check_param, которая позволяет защитить пользователя от использования ключа, который не существует. В следующем примере мы вызываем функцию mergesort с неверным ключом "compffun" вместо "compfun". - - > p a r a m s = i n i t _ p a r a m (); - - > p a r a m s = a d d _ p a r a m ( params , " c o m p f f u n " , m y c o m p f u n ); - - > m e r g e s o r t ( [4 5 1 6 2 3] ’ , p a r a m s ) ’ ans = 1. 2. 3. 4. 5. 6. Мы видим, что наш неправильный ключ "compffun" был проигнорирован, так что использо- валась функция сравнения по умолчанию: матрица была просто отсортирована в возраста- ющем порядке. Причина в том, что модуль parameters не проверяет, что ключ "compffun" не существует. Поэтому мы предлагаем использовать следующую функцию check_param, которая при- нимает в качестве входных аргументов список параметров params и матрицу строчных эле- ментов allkeys. Переменная allkeys хранит список всех ключей, которые доступны в спис- ке параметров. Алгоритм проверяет, что каждый ключе в params существует в allkeys. Вы- ходные аргументы — логическая noerr и строковая msg. Если нет ошибки, то переменная noerr — «истина» и строковая msg — пустая матрица. Если один ключ params не найден, мы устанавливаем noerr в значение «ложь» и формируем сообщение об ошибке. Это со- общение об ошибке используется для действительного формирования ошибки только если выходной аргумент msg не использовался в вызывающей последовательности. f u n c t i o n [ noerr , msg ] = c h e c k _ p a r a m ( params , a l l k e y s ) if ( t y p e o f ( p a r a m s ) < > " p l i s t " ) t h e n e r r o r ( s p r i n t f (.. g e t t e x t ( " %s : W r o n g t y p e for i n p u t a r g u m e n t # %d : %s e x p e c t e d .\ n " ) , .. " c h e c k _ p a r a m " , 1 , " p l i s t " )) 81 g e t t e x t ( " %s : W r o n g t y p e for i n p u t a r g u m e n t # %d : %s e x p e c t e d .\ n " ) , .. " c h e c k _ p a r a m " , 2 , " s t r i n g " )) end c u r r k e y s = g e t f i e l d (1 , p a r a m s ) n k e y s = s i z e ( c u r r k e ys , " * " ) n o e r r = %t msg = [] // Ключ №1 - это " p l i s t ". for i = 2 : n k e y s k = f i n d ( a l l k e y s == c u r r k e y s ( i )) if ( k == [] ) t h e n n o e r r = %f msg = m s p r i n t f (.. g e t t e x t ( " %s : U n e x p e c t e d key " " %s " " in p a r a m e t e r l i s t . " ) ,.. " c h e c k _ p a r a m " , c u r r k e y s ( i )) if ( lhs < 2 ) t h e n e r r o r ( msg ) end b r e a k end end e n d f u n c t i o n Следующая версия функции mergesort использует функцию check_param для того, чтобы проверить, что список доступных ключей является матрицей ["directioncompfun"]. f u n c t i o n y = m e r g e s o r t ( v a r a r g i n ) [ lhs , rhs ]= a r g n () if ( and ( rhs < >[1 2] ) ) t h e n e r r m s g = s p r i n t f (.. " %s : U n e x p e c t e d n u m b e r of a r g u m e n t s : " +.. " %d p r o v i d e d w h i l e %d to %d are e x p e c t e d . " ,.. " m e r g e s o r t " , rhs ,1 ,2); e r r o r ( e r r m s g ) end x = v a r a r g i n (1) if ( rhs <2 ) t h e n p a r a m s = [] e l s e p a r a m s = v a r a r g i n (2) end c h e c k _ p a r a m ( params ,[ " d i r e c t i o n " " c o m p f u n " ]) [ d i r e c t i o n , err ] = g e t _ p a r a m ( params , " d i r e c t i o n " , %t ) [ compfun , err ] = g e t _ p a r a m ( params , " c o m p f u n " , c o m p f u n _ d e f a u l t ) y = m e r g e s o r t r e ( x , d i r e c t i o n , c o m p f u n ) e n d f u n c t i o n В следующем примере мы используем, как раньше, неверный ключ "compffun" вместо "compfun". - - > p a r a m s = i n i t _ p a r a m (); - - > p a r a m s = a d d _ p a r a m ( params , " c o m p f f u n " , m y c o m p f u n ); - - > m e r g e s o r t ( [4 5 1 6 2 3] ’ , p a r a m s ) ! - - e r r o r 1 0 0 0 0 c h e c k _ p a r a m : U n e x p e c t e d key " c o m p f f u n " in p a r a m e t e r l i s t . 82 75 of f u n c t i o n c h e c k _ p a r a m c a l l e d by : at l i n e 16 of f u n c t i o n m e r g e s o r t c a l l e d by : m e r g e s o r t ( [4 5 1 6 2 3] ’ , p a r a m s ) С нашей исправленной функцией формируется ошибка, которая позволяет посмотреть нашу ошибку. 4.5 Область видимости переменных в стеке вызовов В этом разделе мы анализируем область видимости переменных и как это может вза- имодействовать с поведением функций. Поскольку этот момент может привести к неожи- данным ошибкам в программе, то мы предупреждаем об его использовании в функциях, которые могут быть разработаны лучшим образом. Затем мы представляем два различных случая, где область видимости переменных использовалась без должного внимания и как это может привести к труднообнаружимым ошибкам в программе. 4.5.1 Обзор области видимости переменных В этом разделе мы представляем область видимости переменных и как это может влиять на поведение функций. Предположим, что переменная, например a, определена в файле-сценарии и допустим, что та же переменная используется в функции, вызываемой прямо или косвенно. Что про- изойдёт в этом случае, зависит от чтения первой команды или написания переменной a в теле функции. • Если первой прочитана переменная a, то значение этой переменной используется на более высоком уровне. • Если первой написана переменная a, то местная переменная меняется (но переменная на более высоком уровне не меняется). Представим эту общую схему работы на нескольких примерах. В следующем примере мы представляем случай, когда переменная первой читается. Следующая функция f вызывает функцию g и вычисляет выражение, зависящее от входного аргумента x и переменной a. f u n c t i o n y = f ( x ) y = g ( x ) e n d f u n c t i o n f u n c t i o n y = g ( x ) y = a (1) + a (2 ) * x + a ( 3 ) * x ^2 e n d f u n c t i o n Заметим, что переменная a не является входным аргументом функции g. Заметим также, что в этом конкретном случае переменная a только читается, но не пишется. В следующем примере мы определим переменные a и x. Затем мы вызовем функцию f с входным аргументом x. Когда мы определим значение a, то мы будет на уровне вызова №0 в стеке вызовов, в то время как в теле функции g мы находимся на уровне вызова №2 в стеке вызовов. - - > a = [1 2 3]; - - > x = 2; - - > y = f ( x ) 83 = 17. - - > a a = 1. 2. 3. Мы видим, что функция f была вычислена правильно, используя в теле функции g, на уровне вызова №2, значение a, определённое на уровне вызова №0. Действительно, когда интерпретатор заходит в тело функции g, переменная a используется, но не определена. Это из-за того, что она не является ни входным аргументом, ни локально определённой в теле функции g. Следовательно, интерпретатор ищет a на более высоких уровнях в стеке вызовов. В теле функции f, на уровне №1 нет переменной a. Следовательно, интерпретатор ищет на более высоком уровне. На уровне №0 интерпретатор находит переменную a, кото- рая содержит [1 2 3]. Эта переменная используется для вычисления выражения y = a(1) + a(2)*x+a(3)*xˆ2, что, наконец, позволяет вернуть переменную y с правильным содержа- нием. Этот процесс представлен на рисунке 25 Уровень №0 a = [1 2 3]; Уровень №1 function y = f ( x ) y = g ( x ) Уровень №2 function y = g ( x ) y = a(1) + a(2)*x + a(3)*x^2 Использование переменной "а" Поиск переменной "а" на уровне №2... ничего. Поиск переменной "а" на уровне №1... ничего. Поиск переменной "а" на уровне №0... найдена! Рис. 25: Область видимости переменных. – Когда переменная используется, но не определена на нижнем уровне, то интерпретатор ищет ту же переменную на более высоком уровне стека вызовов. Будет использоваться первая переменная, которая будет найдена. На практике, нам следует изменить построение функции g для того, чтобы сделать использование переменной a более ясным. Следующий пример определяет модифицирован- ную версию функции g, где входной аргумент a делает явным тот факт, что мы используем переменную a. f u n c t i o n y = g f i x e d ( x , a ) y = a (1) + a (2 ) * x + a ( 3 ) * x ^2 e n d f u n c t i o n Функция f, соответственно, должна быть обновлена для того, чтобы указать функции gfixed аргумент, который нужен. Это поясняет следующий пример. 84 y = g f i x e d ( x , a ) e n d f u n c t i o n Клиентский исходный код также следует обновить, как в следующем примере. - - > a = [1 2 3]; - - > x = 2; - - > y = f f i x e d ( x , a ) y = 17. Функции ffixed и gfixed, с точки зрения разработки программного обеспечения, гораздо яснее, чем их предыдущие версии. Теперь представим случай, где переменная a написана первой. Следующая функция f2 вызывает функцию g2 и вычисляет выражение, зависящее от входного аргумента x и переменной a. f u n c t i o n y = f2 ( x ) y = g2 ( x ) e n d f u n c t i o n f u n c t i o n y = g2 ( x ) a = [4 5 6] y = a (1) + a ( 2) * x + a ( 3 ) * x ^2 e n d f u n c t i o n Заметим, что переменная a написана прежде её использования. Следующий пример использует те же данные, что и прежде. - - > a = [1 2 3]; - - > x = 2; - - > y = f2 ( x ) y = 38. - - > a a = 1. 2. 3. Заметим, что значение, которое возвращено функцией f2, равно 38, а не предыдущему 17. Это подтверждает, что использовалось значение локальной переменной a = [4 5 6], опре- делённой на уровне №2, а не переменная a = [1 2 3], определённая на уровне №0. Более того, заметим что переменная a, определённая на уровне №0 не изменилась после вызова f2: её значение по-прежнему a = [1 2 3]. Стек вызовов может быть очень глубоким, но всегда будет то же поведение: какой бы ни был уровень в стеке вызовов, если переменная первой прочитана и была определе- на на более высоком уровне, то переменная будет использоваться напрямую. Это свойство кажется некоторым пользователям удобным. Мы подчёркиваем, что это может привести к программным ошибкам, которые невидимы и их трудно обнаружить. Действительно, раз- работчик функции g может непреднамеренно ошибочно использовать переменную a вместо другой переменной. Это приводит к проблемам, которые представлены в следующем разде- ле. 4.5.2 Плохая функция: неоднозначный случай В следующих разделах мы представляем случаи, где неверные использования обла- сти видимости переменных ведут к программным ошибкам, которые трудно анализировать. 85 Предыдущая ошибка была, фактически милой. Действительно, она предупреждает нас о том, что что-то неправильное произошло, так что мы можем изменить наш файл-сценарий. В следующем случае набор команд пройдёт, но с неверным результатом, и это гораздо более опасная ситуация. Мы снова изменим тело алгоритма, предоставленного разработчиком. В этот раз мы устанавливаем переменную a равной произвольной матрице значений типа double, как в следующей функции myalgorithm3. // От разработчика f u n c t i o n y = m y a l g o r i t h m 3 ( x , f , h ) a = [4 5 6] y = ( f ( x + h ) - f ( x ))/ h e n d f u n c t i o n Тело функции несколько странное, поскольку мы не используем переменную a. но функция остаётся правильной. На самом деле это представляет более сложные случаи, где реализация функции myalgorithm3 использует полный, по возможности широкий, набор переменных. В этом случае вероятность использования некоторых имён переменных в качестве пользова- теля гораздо выше. В следующем примере мы вызываем функцию myalgorithm3 и сравниваем с ожидае- мым результатом. - - > a = [1 2 3]; - - > x = 2; - - > h = sq r t ( % e p s ); - - > y = m y a l g o r i t h m 3 ( x , m y f u n c t i o n , h ) y = 29. - - > e x p e c t e d = a (2) + 2 * a ( 3 ) * x e x p e c t e d = 14. Мы видим, что этот набор команд не формирует ошибки. Мы также видим, что ожидаемый результат абсолютно неверен. Как и прежде, переменная a была переопределена на уровне №1, в теле функции myalgorithm3. Следовательно, когда выполняется выражение a(1) + a(2)*x + a(3)*xˆ2, то используется значение [4 5 6] вместо матрицы, которую указал пользователь на более высоком уровне. В нашем конкретном случае легко отладить проблему, поскольку мы имеем точную формулу для производной. На практике мы, вероятно, не будем иметь точную формулу (вот почему мы используем численные производные. . . ), так что будет гораздо труднее об- наружить и, в случае обнаружения, исправить проблему. Использование переменной, определённой на более высоком уровне стека вызовов, мо- жет считаться плохой практикой программирования. В случае, когда разработчик функции этого не желал, это может вести к программным ошибкам, которые трудно обнаружить и может оказаться незамеченным долгое время, пока ошибки не исправят. Используя этот способ, область видимости переменной рассматривается как плохая практика программирования. По-прежнему, может так случиться, что функция требует бо- лее одного входного аргумента, которые надо вычислить. Эта конкретная ошибка приво- дится в разделе 4.6.4 , где мы представляем метод для указания дополнительных входных аргументов функции обратного вызова. 88 4.6.2 Неправильный индекс В этом разделе мы представляем ситуацию, где интерпретатор формирует ошибку «Неправильный индекс» из-за конфликта между именами функций пользователя и раз- работчика. В следующей функции myalgorithm, предоставленной разработчиком, мы устанавли- ваем локальную переменную f в 1. Затем мы вызываем обратно функцию userf с входным аргументом x и возвращаем выходной аргумент y. // На уровне разработчика f u n c t i o n y = m y a l g o r i t h m ( x , u s e r f ) f = 1 y = u s e r f ( x ) e n d f u n c t i o n Заметим, что переменная f установлена, но не используется. Функция по-прежнему годная, и это не меняет наш анализ. Следующая функция myfunction, предоставленная пользователем, вызывает функцию f с входным аргументом x и возвращает выходной аргумент y. // На уровне пользователя f u n c t i o n y = m y f u n c t i o n ( x ) y = f ( x ) e n d f u n c t i o n f u n c t i o n y = f ( x ) y = x ( 1 ) ^ 2 + x ( 2 ) ^ 2 e n d f u n c t i o n В следующем примере мы устанавливаем переменную x и вызываем функцию myalgorithm. Это формирует ошибку «Неправильный индекс» в теле функции myfunction. - - > x = [1 2]; - - > y = m y a l g o r i t h m ( x , m y f u n c t i o n ) ! - - e r r o r 21 Неправильный индекс. at l i n e 2 of f u n c t i o n m y f u n c t i o n c a l l e d by : at l i n e 3 of f u n c t i o n m y a l g o r i t h m c a l l e d by : y = m y a l g o r i t h m ( x , m y f u n c t i o n ) Объяснение следующее. На уровне №0 в стеке вызова, функция f определена так, как ожидает пользователь. В теле функции myalgorithm мы находимся на уровне №1 в стеке вызовов. Переменная f обновлена командой f=1: теперь f является матрицей значений ти- па double. Затем интерпретатор выполняет выражение y = userf(x). Поскольку userf — это myfunction, то интерпретатор вызывает функцию myfunction и входит на уровень №2 в стеке вызовов. На этом уровне интерпретатор выполняет выражение y = f ( x ). Затем интерпретатор ищет переменную f на уровне №2: нет такой переменной на этом уровне. Затем интерпретатор ищет переменную f на более высоком уровне. На уровне №1 интер- претатор находит переменную f, которая является матрицей значений типа double. В этом контексте выражение y = f ( x ) не имеет смысла: переменная x интерпретируется как ин- декс матрицы значений типа double f. Поскольку x=[1 2], то интерпретатор ищет ячейки 91 Следовательно есть только одна ячейка в f, поэтому формируется ошибка «Неправильный индекс». Как и в предыдущем разделе, пользователь не может предугадать имена переменных, выбранных разработчиком, а обратное верно для разработчика. В следующем разделе мы предлагаем методы решения этих проблем функций обрат- ного вызова. 4.6.3 Решения В этом разделе мы представляем решения для проблем функций обратного вызова, которые мы представили ранее. Решение проблем может быть сделано двумя разными спо- собами. • Изменить функцию разработчика так, чтобы было меньше шансов получить конфликт с именами пользовательских переменных. • Изменить функцию пользователя так, чтобы он мог использовать функцию разработ- чика. Очевидно, что мы можем также использовать и тот и другой. В общем, это задача разра- ботчика предоставлять функции, которые достаточно хорошо разработаны, так что пользо- ватель не должен озадачиваться странными программными ошибками. Поэтому мы пред- ставляем это обновление первым. Обновление пользовательской функции представлено во второй часть этого раздела. Следующая модифицированная функция представляет метод, который разработчик может использовать для написания функции myalgorithm. Мы можем видеть, что второй аргумент __myalgorithm_f__ имеет длинное и сложное имя. Это уменьшает вероятность получения конфликтов между именами переменных пользователя и разработчика. f u n c t i o n y = m y a l g o r i t h m ( x , _ _ m y a l g o r i t h m _ f _ _ ) y = _ _ m y a l g o r i t h m _ f _ _ ( x ) e n d f u n c t i o n Это обходной манёвр: по-прежнему есть вероятность, что пользователь получит ошибку. Действительно, если пользователь выберет имя переменной __myalgorithm_f__, то оста- нется конфликт между именами переменных пользователя и разработчика. Но это весьма маловероятно. Следующая модифицированная функция представляет метод, который пользователь может использовать для написания функции myfunction. Вместо определения функции f вне тела функции myfunction, мы определяем её внутри. В новой версии переменная f на этот раз определена локально, внутри myfunction. Это ограничивает видимость локальной функции f, к которой может получить доступ myfunction (и более низкие уровни), и она больше не появляется в глобальной области видимости. Следовательно, когда мы вызыва- ем f, то интерпретатор не ищет на более высоких уровнях стека вызовов: переменная f определена на текущем уровне. f u n c t i o n y = m y f u n c t i o n ( x ) f u n c t i o n y = f ( x ) y = x ( 1 ) ^ 2 + x ( 2 ) ^ 2 e n d f u n c t i o n y = f ( x ) e n d f u n c t i o n 92 В строках с №14 по №18 мы проверяем тип этой переменной и формируем ошибку, если переменная не является функцией. Затем в строках №19 и №20 мы вычисляем два значения функции fxph и fx. Заметьте, что мы используем выражение __myderivative_f__(2:$) для того, чтобы предоставить дополнительные аргументы вызывающей функции. Из-за особого способа, которым элементы выделяются из списка, это создаёт ряд входных аргументов, которые требуются заголовком функции, которую вызвали. На практике метод, который мы уже представили чрезвычайно гибок. Функция может предоставить более одного дополнительного аргумента. Фактически, число дополнитель- ных аргументов может быть уменьшено использованием сбора списка всех необходимых параметров. Действительно, списки могут быть вложены, что позволяет собрать требуемые параметры в единственную дополнительную переменную. 4.7 Мета-программирование: execstr и deff В этом разделе мы представляем функции, которые позволяют выполнить инструкции, которые определены как строки. Поскольку эти строки могут формироваться динамически, мы можем сделать программы, которые имеют уровень гибкости, который не может быть достигнут другими средствами. В первой части мы представляем функцию execstr и даём пример использования этой чрезвычайно мощной функции. Во второй части мы представля- ем функцию deff, которая позволяет динамически создавать функции, основанные на двух строках, содержащих заголовок и тело функции. В заключительной части мы представляем практическое использование функции execstr, где мы объединяем два модуля, которые не могут модифицироваться пользователем и которые не совпадают точно. Рисунок 26 представляет функции, которые позволяют динамически выполнять ин- струкции, основанные на строках. deff оперативное определение функции execstr выполнение инструкций в строке evstr выполнение строки Рис. 26: Функции мета-программирования. 4.7.1 Основное использование execstr В этом разделе мы представляем функцию execstr. Функция execstr берёт в качестве своего первого входного аргумента строку, которая содержит правильную инструкцию Scilab’а. Затем функция выполняет инструкцию как если бы это было частью оригинального файла-сценария на том же самом уровне стека вызовов. В следующем примере мы динамически формируем строку, содержащую инструкцию, которая отображает строку "foo". Сначала мы определяем переменную s, которая содер- жит строку "foo". Затем мы устанавливаем переменную instr, конкатенируя переменную s со строками disp(" и "). Это выполняется оператором +, который позволяет конкате- нировать свои операнды. Двойные кавычки " дублируются, что делает так, что выражение 95 - - > s = " foo " s = foo - - > i n s t r = " d i s p ( " " " + s + " " " ) " i n s t r = d i s p ( " foo " ) - - > e x e c s t r ( i n s t r ) foo Аргумент execstr может содержать любую правильную инструкцию Scilab’а. Напри- мер, мы можем динамически установить новую переменную с динамически созданным содер- жанием. В следующем примере мы создаём строку instr, содержащую инструкцию z=1+2. Затем мы выполняем это выражение и проверяем, что переменная z действительно установ- лена равной 3. - - > x = 1 x = 1. - - > y = 2 y = 2. - - > i n s t r = " z = " + s t r i n g ( x )+ " + " + s t r i n g ( y ) i n s t r = z = 1 + 2 - - > e x e c s t r ( i n s t r ) - - > z z = 3. В качестве другого примера использования execstr мы можем читать коллекцию фай- лов данных. Предположим, что файл datafile1.txt содержит строки 1 2 3 4 5 6 и файл datafile2.txt содержит строки 7 8 9 10 11 12 Следующий пример позволяет читать эти два файла последовательно. Функция read берёт строку, представляющую файл, содержащий матрицу элементов типа double и количество строк и столбцов в матрице. Набор команд устанавливает переменную data, которая содер- жит список матрицы, содержащей данные, прочитанные в двух файлах. d a t a = l i s t (); for k = 1 : 2 i n s t r = " t = r ea d ( " " d a t a f i l e " + s t r i n g ( k ) + " . txt " " ,2 ,3) " e x e c s t r ( i n s t r ) d a t a ( $ + 1 ) = t end Следующий пример показывает динамику предыдущего примера. 96 = t = r e a d ( " d a t a f i l e 1 . txt " ,2 ,3) d a t a = d a t a (1) 1. 2. 3. 4. 5. 6. i n s t r = t = r e a d ( " d a t a f i l e 2 . txt " ,2 ,3) d a t a = d a t a (1) 1. 2. 3. 4. 5. 6. d a t a (2) 7. 8. 9. 10. 11. 12. 4.7.2 Основное использование deff Функция deff позволяет динамически определять функцию, из двух строк, содержа- щих заголовок и тело функции. В следующем примере мы определим функцию myfun, которая принимает матрицу значений типа double x в качестве входного аргумента и возвращает выходной аргумент y. Сначала мы определяем заголовок функции в виде строки "y=myfun(x)" и устанавливаем переменную header. Затем мы определяем тело функции и, наконец, мы вызываем функцию deff, которая создаёт требуемую функцию. - - > h e a d e r = " y = m y f u n ( x ) " h e a d e r = y = m y f u n ( x ) - - > b o d y = [ - - > " y (1) = 2* x ( 1 ) + x (2) - x ( 3 ) ^ 2 " - - > " y (2) = 2* x (2) + x (3) " - - >] b o d y = ! y (1) = 2* x ( 1 ) + x (2) - x ( 3 ) ^ 2 ! ! ! ! y (2) = 2* x (2) + x (3) ! - - > d e f f ( header , b o d y ) Следующий пример показывает, что новая функция myfun может быть использована как любая другая функция. - - > x = [1 2 3] x = 1. 2. 3. - - > y = m y f u n ( x ) y = - 5. 7. Единственная разница заключается в типе функции, созданной с помощью deff, как пред- ставлено в разделе 4.1.1 . В следующем примере мы показываем, что функция, созданная с помощью deff, имеет тип 13, который соответствует компилированному макросу. - - > t y p e ( m y f u n ) 97 = 13. - - > t y p e o f ( m y f u n ) ans = f u n c t i o n Если мы добавим аргумент "n" к вызову функции deff, то это скажет интерпретатору создать вместо этого некомпилированный макрос, который производит функцию с типом 11. - - > d e f f ( header , body , " n " ) W a r n i n g : r e d e f i n i n g f u n c t i o n : m y f u n . Use f u n c p r o t (0) to a v o i d t h i s m e s s a g e - - > t y p e ( m y f u n ) ans = 11. - - > t y p e o f ( m y f u n ) ans = f u n c t i o n Мы подчёркиваем, что для того, чтобы создать новую функцию, могла бы исполь- зоваться функция execstr вместо deff. Например, следующий набор команд показывает способ определения новой функции с помощью execstr отдельным определением заголов- ка, тела и окончания функции. Эти строки затем конкатенируются в единую инструкцию, которая, наконец, исполняется для того чтобы определить новую функцию. h e a d e r = " f u n c t i o n y = m y f u n ( x ) " b o d y = [ " y (1) = 2* x ( 1 ) + x (2) - x ( 3 ) ^ 2 " " y (2) = 2* x (2) + x (3) " ] f o o t e r = " e n d f u n c t i o n " i n s t r = [ h e a d e r b o d y f o o t e r ] e x e c s t r ( i n s t r ) 4.7.3 Практический пример оптимизации В этом разделе мы представляем практический случай использования методов, кото- рые мы представили в предыдущих разделах. Этом примере мы используем функцию optim, которая является программой для по- иска решения числовой оптимизации. Мы представляем ситуацию, когда нам нужна функ- ция для определения функции адаптера (т. е. «клей»), которая соединяет optim с модулем, предоставляющим набор задач оптимизации. В первом методе, мы определяем функцию адаптера и передаём функцию обратного вызова с дополнительными аргументами в функ- цию optim. Во втором методе, мы используем функцию execstr для создания функции, чьё тело формируется динамически. Пример, который мы выбрали, может показаться сложным. Хотя на практике он пред- ставляет практическую задачу разработки программного обеспечения, где могут использо- ваться только нетривиальные решения. 98 |