Фэндом

Smalltalk по-русски

Манипуляции с матрицами

239статей на
этой вики
Добавить новую страницу
Обсуждение0 Поделиться

(Оригинал. Автор - Alex Baran.)

У меня были тесты, в которых надо было создавать графы, потом проводить над этими графами некоторые манипуляции, а потом сверять результат. Сначала для создания ребра я писал нечто вроде v1 refTo: v2 weight: 100. Но даже при самых небольших графах писать приходилось много. Да и понять, какие ребра откуда выходят/куда входят ... по такой записи было тяжело.

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

etalonGraph
       | mb |
       mb := MatrixBuilder new.

       mb  |  v1 |  v2 |  v3  |  v4 |  v5  !
       v1  |  0  |  1  |  -3  |  2  |  -4  !
       v2  |  3  |  0  |  -4  |  1  |  -1  !
       v3  |  7  |  4  |  0   |  5  |  3   !
       v4  |  2  | -1  |  -5  |  0  |  -2  !
       v5  |  8  |  5  |  1   |  6  |  0   .

       ^mb matrix graph

Создавать такую матрицу просто - для выравнивания я использую табуляцию.

v1, v2, ... это вершины графа, т.е. объекты класса GraphNode. Суть в том что стобцом, строкой, и значением в матрице может быть любой объект. v1, v2, ... - также instance являются instance переменными теста

| , ! - это сообщения, посылаемые matrix builder-у. Например matrixBuilder | v1, аналогично вызову matrixBuilder addColumn: v1. Эти сообщения возвращают matrix builder, поэтому все последующие вызовы этих сообщений опять же идут к matrix builder-у. | - добавляет новый столбец или вбивает значение в матрицу. ! - обозначает конец строки, а также добавляет строку к матрице.

В конце теста граф, как правило, проверялся относительно другой, эталонной, матрицы.

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

testSmoke
       tb := TableBuilder newFor: applicationModel.

       tb |  #firstName  |  #lastName   |  #company    !
       1  |  'Иванов'    |  'Сергей'    |  'Company1'  !
       2  |  'Петров'    |  'Александр' |  'Company1'  .

       tb play.

tb play проигрывает наш тест. tb play знает applicationModel(некий аналог формы) и может автоматом заносить данные прямо в адаптеры, например

firstName value: 'Иванов'
lastName value: 'Сергей'

На самом деле внутри это будет выглядить так: (applicationModel perform: columnName) value: cellValue. Интересная особенность в том, что при просмотре ссылок на метод firstName мы увидим метод testSmoke. Также при переименовании метода firstName RefactoringBrowser? будет переименовывать #firstName внутри метода testSmoke. Но оказалось, что такой тест достаточно бесполезен. Дело в том, что это smoke тест. В нем нет никаких проверок. Тогда я подумал, что проверки надо делать именно в матрице - в еще одном столбце. Теперь я думаю использовать примерно такую матрицу:

testSmoke
       tb := TableBuilder newFor: applicationModel testCase: self.
       tb | #firstName | #lastName | #company   **  'error'               !
       1  | 'Иванов'   | 'Сергей'  | 'Company1' |   nil                   !
       2  | 'Иванов'   | 'Сергей'  | 'Company1' |   PossibleDoublingError .
       tb play.
    • - это новый метод (может звучать очень странно для тех, кто не знаком с Smalltalk-ом). Этот метод говорит нашему tableBuilder-у, что столбец 'error' содержит класс ошибки, которую необходимо ожидать при забивании строки. При tb play будет происходит нечто вроде:
self 
   should: [(self applicationModel perform: columnName) value: cellValue] 
   raise: errorClass

Опять к графам. На самом деле представлять графы только лишь в виде матрицы мне тоже было не очень удобно. В дебагере я постоянно запускал рисовалку графа и постоянно перетягивать вершины, чтобы получить человечесикй layout. Было бы неплохо если бы в тесте граф хранился как в виде матрицы, так и графически. Тогда я понял, что для этого надо не так много. Матрица есть. На ее основании можно построить граф. Не хватает только координат вершин.

graphAndPoints
       | gb |
       gb := GraphBuilder new.
       gb |  v1  |  v2  |  v3  |  v4   !
       v1 |  nil |  nil |  nil |  nil  !
       v2 |  nil |  nil |  10  |  10   !
       v3 |  nil |  10  |  nil |  nil  !
       v4 |  10  |  nil |  10  |  nil  .
       ^ gb graph -> (#() + (359@191) + (165@62) + (49@194) + (244@185))

359@191 - это координата вершина v1, 165@62 - вершины v2 и т.д. Я не указываю координаты вручную. Просто я добавил в рисовалку графов возможность сохранить эти координаты в методе. Я также могу попросить рисовалку выдать мне только строчку с координатами и вставить ее в ручную, например если я не хочу потерять изменения в методе. Я также могу нарисовать граф в рисовалке с нуля и попросить рисовалку создать метод. Опять же, я могу править этот метод. Фактически это два представления одной и той же модели. Так же, как спецификации GUI и сам GUI. Только это код, а не текст.

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

mb |  a  |  b |  (a \\ b) !
1  |  10 |  2 |  nil      !
2  |  10 |  3 |  nil      !
3  |  10 |  4 |  nil      .

matrix compute вернет уже вычисленную матрицу

mb |  a  |  b |  (a \\ b) !
1  |  10 |  2 |  0      !
2  |  10 |  3 |  1      !
3  |  10 |  4 |  2      .

Вместо a\\b может быть записанно любое выражение на Smalltalk-е. В этом выражении можно использовать любые переменные, которые видны из метода, где определенна матрица. Такая простая матрица большого интереса не представляет. К тому же она имеет недостаток. Она ничего не проверяет. Опять же ее можно использовать только в smoke тестах. Полезной могла бы оказалатся матрица, которая бы являлась одновременно тестом. Для этого надо сразу проставить вычисляемое поле. Матрица будет производить вычисления и сверять результат с значением в поле. Если результат расходится матрица сообщает об ошибке (либо Exception либо, если матрица имеет ссылку на testCase, она говорит об ошибке прямо testCase-у).

Отступление. В принципе, можно засовывать в матрицу любые объекты. Например можно засовывать блоки или вызовы сообщений. Можно например представить GUI действия как блоки, а в матрице комбинировать эти действия. Писать новые Builder-ы, которые будут с этим работать. Будет это удобно или нет - я пока не представляю. Здесь весь вопрос в удобстве написания, читаемости и понятности кода. Может что-то из этого и получится :). Вся суть даже не в матрицах как таковых, а в нахождении более простой формы. В Smalltalk-е легко изменить то, что в других языках является синтаксисом. В Self-е выполнять подобные манипуляции должно быть еще проще. В Self-е одно зарезервированное слово и отстутсвие классов :). Мечта - есть объекты и нет классов :-). Также интересно узнать другие, альтернативные способы записывания тестов.

Alex Baran

Обнаружено использование расширения AdBlock.


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

Викия не будет доступна для последующих модификаций. Если вы желаете продолжать работать со страницей, то, пожалуйста, отключите расширение для блокировки рекламы.

Также на Фэндоме

Случайная вики