Программирование в Scilab Микаэль Боден Micha
Скачать 1.35 Mb.
|
«Ringo». - - > s e t f i e l d (2 , " R i n g o " , p ) - - > p p = p (1) ! p e r s o n f i r s t n a m e n a m e b i r t h y e a r ! p (2) R i n g o p (3) S m i t h p (4) 1 9 9 7 . Может так случиться, что мы знаем значения полей во время создания типизирован- ного списка. Мы можем добавить эти значения ко входным аргументам функции tlist в строгом порядке. В следующем примере мы определим person p и установим значения полей во время создания типизированного списка. - - > p = t l i s t ( .. - - > [ " p e r s o n " , " f i r s t n a m e " , " n am e " , " b i r t h y e a r " ] , .. - - > " P au l " , .. - - > " S m i t h " , .. - - > 1 9 9 7 ) p = p (1) ! p e r s o n f i r s t n a m e n a m e b i r t h y e a r ! p (2) P a u l p (3) S m i t h p (4) 1 9 9 7 . Интересной чертой типизированного списка является то, что функция typeof возвра- щает текущий тип списка. В следующем примере мы проверяем что функция type возвра- щает 16, что соответствует списку. Но функция typeof возвращает строку «person». - - > t y p e ( p ) ans = 16. - - > t y p e o f ( p ) ans = p e r s o n Это позволяет динамически менять поведение функций для типизированных списков с ти- пом «person». Эта возможность связана с перегрузкой функций, темой, которая будет рас- смотрена в разделе 3.8 Теперь мы рассмотрим функции, которые позволяют динамически получать инфор- мацию о типовых списках. Для того, чтобы получить список полей «person», мы можем 35 использовать синтаксис p(1) и получить матрицу строк размером 1 × 4, как в следующем примере. - - > p (1) ans = ! p e r s o n f i r s t n a m e n a m e b i r t h y e a r ! Тот факт, что первый строковый элемент представляет тип, может быть полезен или мешать, в зависимости от ситуации. Если мы только хотим получить поля типизированного списка (и не распечатывать), то мы можем использовать функцию fieldnames. - - > f i e l d n a m e s ( p ) ans = ! f i r s t n a m e ! ! ! ! n a m e ! ! ! ! b i r t h y e a r ! Когда мы создаём типизированный список, мы можем определить его поля без установ- ки его значений. Действующее значение поля может быть на самом деле позже установлено динамически в файле-сценарии. В этом случае было бы полезно знать, определено ли поле уже ли нет. Следующий пример показывает как функция definedfields возвращает матрицу це- лых чисел с плавающей запятой, представляющую поля, которые уже определены. Мы на- чинаем с определения p человека без установки какого-либо значения любого из полей. Вот почему единственное определённое поле равно числу 1. Затем мы устанавливаем поле «firstname», которое соответствует индексу 2. - - > p = t l i s t ([ " p e r s o n " , " f i r s t n a m e " , " n a m e " , " b i r t h y e a r " ]) p = p (1) ! p e r s o n f i r s t n a m e n a m e b i r t h y e a r ! - - > d e f i n e d f i e l d s ( p ) ans = 1. - - > p . f i r s t n a m e = " P a u l " ; - - > d e f i n e d f i e l d s ( p ) ans = 1. 2. Как мы можем видеть, индекс 2 был добавлен к матрице с помощью определённых полей, возвращённых функцией definedfields. Функции, которые мы рассмотрели, позволяют программировать типизированные спис- ки очень динамичным образом. Теперь мы увидим как использовать функции definedfields для динамического вычисления определено ли поле, идентифицированное по его строке, или нет. Это позволит получить немного больше практики с типизированными списками. Вспомним, что мы можем создать типизированный список без действительного определения значений его полей. Эти поля могут быть определены позже, так что в конкретное время мы не знаем все ли поля определены или нет. Следовательно, может потребоваться функция isfielddef, которая бы вела себя как в следующем примере. - - > p = t l i s t ([ " p e r s o n " , " f i r s t n a m e " , " n a m e " , " b i r t h y e a r " ]); - - > i s f i e l d d e f ( p , " n a m e " ) ans = F 36 - - > p . n a m e = " S m i t h " ; - - > i s f i e l d d e f ( p , " n a m e " ) ans = T Целью упражнения 3.2 является динамическое определение: существует ли в типизи- рованном списке поле, связанное с заданным полем, определённым по его строке. 3.7 Имитация объектно-ориентированного программирования с по- мощью типизированных списков В этом разделе мы рассмотрим как типизированные списки могут использоваться для имитации объектно-ориентированного программирования (ООП). Это обсуждение частично было представлено в вики Scilab [ 7 ]. Мы представляем простой метод имитирования ООП с текущими функциями Scilab’а. Предлагаемый метод является классическим, когда мы хотим имитировать ООП на не-ООП языке, например, на Си или фортране. В первой части мы анализировали ограничения функ- ций, которые используют позиционные аргументы. Далее мы представляем метод имитации ООП в Scilab с помощью типизированных списков. В заметках, связанных с этим разделом, мы представляем похожие методы на дру- гих языках. Мы подчёркиваем тот факт, что объектно-ориентированное программирование использовалось и используется десятками не-ООП языков, таких как Си или фортран, на- пример, методами, которые очень похожи на тот, что мы собираемся представить. 3.7.1 Ограничения позиционных аргументов Перед тем как перейти к деталям, мы сначала представим причины того, почему ими- тация ООП в Scilab’е удобна и, иногда, необходима. В самом деле, метод, который мы защи- щаем, может позволить упростить многие функции, которые основаны на необязательных, позиционных аргументах. Факт, что выполнение, основанное на позиции входных и выход- ных аргументов функции, является жёстким ограничением в некоторых случаях, как мы вскоре увидим. Например, примитив optim является встроенной функцией, которая выполняет неогра- ниченную и частично ограниченную числовую оптимизацию. Эта функция имеет 20 аргу- ментов, некоторые из которых необязательные. Далее приводим заголовок функции, где квадратные скобки [...] обозначают необязательные параметры. [ f [ , x o p t [ , g r a d o p t [ , w o r k ] ] ] ] = .. o p t i m ( c o s t f [ , < contr >] , x0 [ , a l g o ] [ , df0 [ , mem ]] [ , w o r k ] .. [ , < stop >] [ , < params >] [ , imp = i f l a g ]) Эта усложнённая последовательность вызова делает практическое использование optim сложным (но работоспособным), особенно если мы хотим настроить его аргументы. Напри- мер, переменная является списком четырёх необязательных аргументов: ’ ti ’ , v a l t i , ’ td ’ , v a l t d Многие параметры алгоритма могут быть сконфигурированы, но, довольно удивитель- но, что многие не могут быть сконфигурированы пользователем функции optim. Например, в случае квази-Ньютоновского алгоритма без ограничений, процедуры фортрана позволяют сконфигурировать длину, представляющую оценку расстояния до оптимума. Этот параметр не может быть сконфигурирован на уровне Scilab и по умолчанию используется значение 37 0,1. Причина, которая кроется за этим выбором, очевидна: есть уже слишком много пара- метров для функции optim и добавление других необязательных параметров привело бы к невозможности пользования функцией. Более того, пользователи и разработчики хотят добавлять новые возможности в при- митив optim, но это может привести к нескольким трудностям. • Расширение текущего шлюза optim очень сложно из-за сложного управления 20-ю внутренними необязательными аргументами. Более того, поддержка и разработ- ка интерфейса в течение жизни проекта Scilab трудны, поскольку порядок аргумен- тов имеет значение. Например, мы можем осознавать, что один аргумент может быть ненужным (из-за того, например, что эта же самая информация может быть получена через другую переменную). В этом случае мы не можем удалить аргумент из после- довательности вызова, поскольку это сломает обратную совместимость всех файлов- сценариев, которые используют эту функцию. • Трудно расширить список выходных аргументов. Например, нас может заинтересо- вать целое число, представляющее статус оптимизации (например, достигнута ли схо- димость, достигнуто ли максимальное число итераций, максимальное число вызовов функций и т. д.). Мы могли бы быть также заинтересованы в числе итераций, чис- ле вызовов функций, конечном значении аппроксимации матрицы Гессе, и в другой информации. Ограниченное число выходных аргументов на самом деле ограничивает количество информации, которую пользователь может вытянуть из имитации. Более того, если мы захотим получить выходной аргумент №6, например, то мы должны вызвать функцию со всеми аргументами от №1 до №6. Это может породить много ненужных данных. • Мы могли бы захотеть установить необязательный входной аргумент №7, а не необяза- тельный аргумент №6, который отсутствует в данном интерфейсе. Это из-за того, что система обработки аргументов основана на порядке аргументов. В общем и целом, факт, что входные и выходные аргументы используются явно и на основе их последовательности, что очень неудобно когда число аргументов велико. Если окружение объектно-ориентированного программирования было бы доступно для Scilab’а, то управление аргументами было бы решено с меньшими трудностями. 3.7.2 Класс «person» в Scilab В этом разделе мы даём конкретный пример метода, основанного на разработке класса «person». Метод, который мы представляем в этом документе, для имитации объектно-ориенти- рованного программирования является классическим для других языков. На самом деле о общий для расширения необъектных языков в ООП-системе. Возможный метод: • мы создаём тип абстрактных данных (ТАД) со структурами основных данных языка, • мы имитируем методы с помощью функций, у которых первый элемент, по имени this, представляет текущий объект. Мы будем использовать этот метод и имитировать в качестве примера отдельный класс «person». Класс «person» сделан из следующих функций. 38 • Функция person_new, «конструктор», создаёт новый объект «person». • Функция person_free, «деструктор», разрушает существующий объект «person». • Функция person_configure и функция person_cget, позволяют сконфигурировать и опросить поля существующего объекта «person». • Функция person_display, «метод», который отображает текущий объект в консоли. В этих функциях текущий объект будет храниться в переменной this. Чтобы реализовать наш класс, мы используем типизированный список. Следующая функция person_new возвращает this нового объекта «person». Этот но- вый «person» определён по своим фамилии, имени, номеру телефона и адресу электронной почты. Мы выбираем для использования пустые значения строк для всех полей. f u n c t i o n t h i s = p e r s o n _ n e w () t h i s = t l i s t ([ " T P E R S O N " , " n a m e " , " f i r s t n a m e " , " p h o n e " , " e m a i l " ]) t h i s . n a m e = " " t h i s . f i r s t n a m e = " " t h i s . p h o n e = " " t h i s . e m a i l = " " e n d f u n c t i o n Следующая функция person_free разрушает существующий объект «person». f u n c t i o n t h i s = p e r s o n _ f r e e ( t h i s ) // Нет никаких действий. e n d f u n c t i o n Поскольку сейчас нет никаких действий, то тело функции person_free пустое. По-прежнему, в силу разумных причин и из-за того, что реальное тело функции может развиться позже во время разработки компонента, мы создаём эту функцию в любом случае. Мы подчёркиваем, что переменная this является как входным, так и выходным аргу- ментом функции person_free. В самом деле, текущий объект «person», в принципе, является модифицируемым через работу функции person_free. Функция person_configure позволяет сконфигурировать поле текущего объекта «person». Каждое поле идентифицируется строкой, «ключом», который соответствует значению. Сле- довательно, это просто отображение «ключ-значение». Функция устанавливает значение value, соответствующее заданному ключу key, и возвращает обновлённый объект this. Для класса «person» ключами являются "-name", "-firstname", "-phone" и "-email". f u n c t i o n t h i s = p e r s o n _ c o n f i g u r e ( this , key , v a l u e ) s e l e c t key c a s e " - na m e " t h e n t h i s . n a m e = v a l u e c a s e " - f i r s t n a m e " t h e n t h i s . f i r s t n a m e = v a l u e c a s e " - p h o n e " t h e n t h i s . p h o n e = v a l u e c a s e " - e m a i l " t h e n t h i s . e m a i l = v a l u e e l s e e r r m s g = s p r i n t f ( "Неизвестный ключ %s " , key ) e r r o r ( e r r m s g ) end e n d f u n c t i o n 39 Мы выбрали префиксацию каждого ключа знаком минус «-». В последовательностях вызова это позволит легко отличить ключ от значения. Мы подчёркиваем, что переменная this является как входным, так и выходным ар- гументом функции person_configure. Она похожа на функцию person_free, которую мы создали прежде. Аналогично, функция person_cget позволяет получить заданное поле текущего объ- екта «person». person_cget возвращает значение value, соответствующее заданному ключу key текущего объекта. f u n c t i o n v a l u e = p e r s o n _ c g e t ( this , key ) s e l e c t key c a s e " - n am e " t h e n v a l u e = t h i s . n a m e c a s e " - f i r s t n a m e " t h e n v a l u e = t h i s . f i r s t n a m e c a s e " - p h o n e " t h e n v a l u e = t h i s . p h o n e c a s e " - e m a i l " t h e n v a l u e = t h i s . e m a i l e l s e e r r m s g = s p r i n t f ( "Неизвестный ключ %s " , key ) e r r o r ( e r r m s g ) end e n d f u n c t i o n Точнее, функция person_cget позволяет получить значение конфигурируемого ключа. «c» в «cget» относится к первой букве «configure» (настроить). Если, вместо этого, мы захо- тим создать функцию, которая возвращает значение ненастраиваемого ключа, мы можем назвать её person_get. Теперь, когда наш класс установлен, мы можем создать новый «метод». Следующая функция person_display распечатать текущий объект this в консоли. f u n c t i o n p e r s o n _ d i s p l a y ( t h i s ) m p r i n t f ( " P e r s o n \ n " ) m p r i n t f ( " N a m e : %s \ n " , t h i s . n a m e ) m p r i n t f ( " F i r s t n a m e : %s \ n " , t hi s . f i r s t n a m e ) m p r i n t f ( " P h o n e : %s \ n " , t h i s . p h o n e ) m p r i n t f ( " E - m a i l : %s \ n " , t h i s . e m a i l ) e n d f u n c t i o n Теперь мы представим простое использование класса «person», который мы только что разработали. В следующем примере мы создаём новый объект «person» с помощью вызова функции person_new . Затем мы вызовем функцию person_configure несколько раз для того, чтобы сконфигурировать различные поля объекта «person». p1 = p e r s o n _ n e w (); p1 = p e r s o n _ c o n f i g u r e ( p1 , " - n am e " , " B a c k u s " ); p1 = p e r s o n _ c o n f i g u r e ( p1 , " - f i r s t n a m e " , " J o h n " ); p1 = p e r s o n _ c o n f i g u r e ( p1 , " - p h o n e " , " 0 1 . 2 3 . 4 5 . 6 7 . 8 9 " ); p1 = p e r s o n _ c o n f i g u r e ( p1 , " - e m a i l " , " j o h n . b a c k u s @ c o m p a n y . com " ); В следующем примере мы вызовем функцию person_display и распечатаем текущий «person». - - > p e r s o n _ d i s p l a y ( p1 ) P e r s o n N a m e : B a c k u s 40 F i r s t n a m e : J o h n P h o n e : 0 1 . 2 3 . 4 5 . 6 7 . 8 9 E - m a i l : j o h n . b a c k u s @ c o m p a n y . com Мы можем также запросить имя текущего «person» вызовом функции person_get. - - > n a m e = p e r s o n _ c g e t ( p1 , " - n a me " ) n a m e = B a c k u s Наконец, мы уничтожим текущий «person». p1 = p e r s o n _ f r e e ( p1 ); 3.7.3 Расширение класса В этом разделе мы обсудим способы расширения класса, основанные на имитации ме- тода, который мы представили. Наша цель — возможность управлять более сложными ком- понентами с б´ ольшим количеством полей, б´ ольшим количеством методов или б´ ольшим ко- личеством классов. Сперва мы подчеркнём, что управление опциями класса безопасно. На самом деле система, которую мы только что разработали, просто сопоставляет значение ключу. Следо- вательно, список ключей, определяется один раз для всех, пользователь не может конфи- гурировать или получать значение ключа, который не существует. Если мы попытаемся, то функции person_configure или person_cget сформируют ошибку. Новый ключ в класс добавляется напрямую. Сначала мы должны обновить функцию person_new, добавив новое поле к типизированному списку. Мы можем решить, какое зна- чение по умолчанию использовать для нового поля. Заметим, что существующие файлы- сценарии, использующие класс «person», будут работать. Если требуется, то файл-сценарий может быть обновлён для того, чтобы конфигурировать новый ключ значением не по умол- чанию. Мы можем решить различать публичные поля, которые могут быть сконфигурированы пользователем или классом, и частные поля, которые не могут. Для того, чтобы добавить новое частное поле в класс «person», скажем «bankaccount» (банковский счёт), мы изменя- ем функцию person_new и добавляем соответствующую строку в типизированный список. Следовательно, нам не нужно делать его доступным ни в функции person_configure, ни в функции person_cget, пользователь класса не сможет получить доступ к нему. Мы можем быть немного гибче, позволяя пользователю класса получать значения по- лей без возможности изменения значения. В этом случае нам следует создать отдельную функцию person_get (заметим отсутствие буквы «c»). Это позволяет отделить конфигури- руемые опции от неконфигурируемых опций. Мы можем создать даже более сложные структуры данных с помощью вложения клас- сов. Это легко, поскольку типизированные списки могут содержать типизированный список, который может содержать типизированный список, и т. д. до любого требуемого уровня вло- жения. Следовательно, мы можем имитировать ограниченное наследование, идея, которая является одним из главных вопросов в ООП. К примеру, допустим, что мы хотим создать класс «company». Эта «company» опре- деляется по её имени, адресу, назначению и списком всех людей («person»), работающих в ней. Следующая функция company_new предлагает возможную реализацию. |