Автор Тема: Как обозначить атрибут, значение которого вычисляется в момент создания объекта  (Прочитано 1274 раз)

Vadim

  • Full Member
  • ***
  • Сообщений: 150
  • Рейтинг читателей: 30
    • Просмотр профиля
Да. Но у него, на мой вкус, внятная позиция по тому, что это такое и как увязываются между собой {readOnly} и {frozen}. Если можно морозить, то можно и разморозить и т. п.
readonly и derived - это свойства "раз и навсегда", frozen (unfrozen) - только на время. Кроме того, если атрибут readonly, то делать его frozen (unfrozen) не имеет смысла - от этого ничего не изменится. И еще, если атрибут derived, то какой смысл давать ему defaultValue, разве что считать это дополнительным ограничением на те атрибуты, по которым вычисляется этот атрибут. Например описание атрибутов "длина, ширина, /площадь=6 {derive: длина*ширина}" означает: "Сразу после инициализации длина и ширина могут быть любыми числами, но их произведение должно равняться 6. Потом на длину и ширину не накладывается никаких ограничений, но площадь всегда будет равна их произведению.".
Есть другой вариант внятной позиции.
Эти авторы когда-то впечатлили меня фразой: "существуют три уровня понимания обучающимся нового предмета, которые характеризуются следующими признаками: 1. Возникает приятное чувство понимания; 2. Может повторить своими словами; 3. Видит ошибки. Данная книга написана на третьем уровне и адресована обучающимся, ориентированным на третий уровень" - взято отсюда. Про derived ничего не говорят, только про derive.
Совместное получается склеиванием раздельного, т. к. в стандарте оба свойства из метамодели независимы. Мы берём то, что сказано про readonly, и то, что сказано про derived и объединяем. Вот аналогия: теплое и сладкое -- это тёплое + сладкое. По намёкам из текущей версии стандарта выходит, что от соединения теплого и сладкого возникает дополнительный эффект "дрожжей" и градус в модели вдруг повышается.
Склеиваем и получаем: как получилось расчетное значение сразу после инициализации, так дальше и не меняется, хотя все время пересчитывается.
Если слэша нет, то о привязке к другому значению мы узнаём только из постусловия. Если слэш есть, то наше внимание дважды акцентируется на непростом атрибуте.
"=defaultValue" отражает постусловие конструктора в той части, в которой важно. И "init: Накладная::фиоКладовщика = Склад::фиоКладовщика" тоже. Просто в этих двух выражениях нет явной привязке ко времени, когда берётся присваиваемое значение. Эта привязка неявная, данная стандартом в пояснении к defaultValue. Постусловие, как мне кажется, нагляднее говорит о временнОй привязке. Выше писало про @pre, его можно [с некоторой тонкостью] использовать, что явно скажет о том, когда используется значение.
Рассмотрим класс "меняющийся прямоугольник" с атрибутами: длина, ширина, площадь, первоначальная площадь. Правильным описанием двух последних атрибутов будет: /площадь {derive: длина*ширина}, первоначальная площадь=площадь {readonly} (если считать, что "=defaultValue" отражает постусловие конструктора). Делать первоначальную площадь derive нельзя - тогда надо указывать правило вычисления. Или считать, что для атрибута с derived наличие "=defaultValue" (может в сочетании с readonly) служит правилом вычисления.


[прилетело НЛО и...]

  • Full Member
  • ***
  • Сообщений: 216
  • Рейтинг читателей: 26
    • Просмотр профиля
readonly и derived - это свойства "раз и навсегда", frozen (unfrozen) - только на время.
Согласно. Выше, говоря о связке readOnly с frozen, имело в виду, что readOnly=frozenForever.

Кроме того, если атрибут readonly, то делать его frozen (unfrozen) не имеет смысла - от этого ничего не изменится. И еще, если атрибут derived, то какой смысл давать ему defaultValue, разве что считать это дополнительным ограничением на те атрибуты, по которым вычисляется этот атрибут.
Речь шла не о совместном применении readOnly и frozen, а об общности их смыслов.
Меня озарило, что авторы стандарта дали кучу примеров использования обсуждаемых нами тегов и слэшей. Они даны на диаграммах (и OCLях) описывающих метамодель UML. На 9.5.2 есть такое: Property::/isComposite:Boolean=false (также в 9.9.17.5). Авторы демонстрируют свой приём моделирования. Для каждого выводимого атрибута (полюса ассоциации) ими заводится одноимённая операция, возвращающая выведенное значение. Если OCL позволяет, то операции даётся тело (удивительно, что не derive!). См. Property::isComposite() в 9.9.17.7. Если убрать начальное значение в этом примере, смысл сохранится. Есть подозрение, что явное выписывание начального значения тут -- подсказка читателю диаграммы, чтобы ему не пришлось искать тело и в уме делать вывод. В схожей ситуации 12.3.2 (12.4.1.4, 12.4.1.6), Extension::/isRequired:Boolean вместо =false указано {readOnly}. Налицо "эффект дрожжей". Есть ещё одно подозрение, что завихрения относительно readOnly + derived рождались у авторов стандарта и обкатывались на метамодели UML. Т. е. это их реализаторская точка зрения, в которой они путают "не может меняться извне" с "не может меняться после инициализации". По каким-то причинам авторам эта путаница удобна. О том, как быть остальным, они не заботились. Щепотка "дрожжей" нашлась в описании Message::/messageKind {readOnly}. Для выводимого атрибута задана операция, тело которой состоит из возврата значения взятого у самого атрибута.
 
Например описание атрибутов "длина, ширина, /площадь=6 {derive: длина*ширина}" означает: "Сразу после инициализации длина и ширина могут быть любыми числами, но их произведение должно равняться 6. Потом на длину и ширину не накладывается никаких ограничений, но площадь всегда будет равна их произведению.".
Я бы такое описание прочло так: Длина и ширина не имеют значений по умолчанию. Площадь по умолчанию равна 6. В какие-то моменты времени значение площади вычисляется как произведение длины на ширину. Чтобы описать, в какие именно моменты, следует дать диаграмму состояний и/или дать реализации операций класса Прямоугольник.

Эти авторы когда-то впечатлили меня фразой: "существуют три уровня понимания обучающимся нового предмета, которые характеризуются следующими признаками: 1. Возникает приятное чувство понимания; 2. Может повторить своими словами; 3. Видит ошибки. Данная книга написана на третьем уровне и адресована обучающимся, ориентированным на третий уровень" - взято отсюда. Про derived ничего не говорят, только про derive.
Авторы захаживали/захаживают на эту планету, так что я остановлюсь на первом предложенном ими уровне.

Склеиваем и получаем: как получилось расчетное значение сразу после инициализации, так дальше и не меняется, хотя все время пересчитывается.
Я полагаю, что слэш не устанавливает явной директивы, что нужно всё время пересчитывать. Стандарт на этот счёт лишь замечает, что изменение значения nonReadOnly выводимого свойства должно подчиняться derive-ограничению.

Рассмотрим класс "меняющийся прямоугольник" с атрибутами: длина, ширина, площадь, первоначальная площадь. Правильным описанием двух последних атрибутов будет: /площадь {derive: длина*ширина}, первоначальная площадь=площадь {readonly} (если считать, что "=defaultValue" отражает постусловие конструктора). Делать первоначальную площадь derive нельзя - тогда надо указывать правило вычисления. Или считать, что для атрибута с derived наличие "=defaultValue" (может в сочетании с readonly) служит правилом вычисления.
С точки зрения написанного мной выше этот пример может быть разобран так:
Значение первоначальной площади зависит от других значений, значит этот атрибут можно пометить как выводимый.
Указание начального значения у первоначальной площади, которая является {readOnly}, играет ту же роль, что и правило вычисления. При инициализации объекта будет вычислено то, что справа от равенства, результат будет положен в  первоначальную площадь, далее он меняться не будет.
[...и улетело НЛО.]

Vadim

  • Full Member
  • ***
  • Сообщений: 150
  • Рейтинг читателей: 30
    • Просмотр профиля
Меня озарило, что авторы стандарта дали кучу примеров использования обсуждаемых нами тегов и слэшей. Они даны на диаграммах (и OCLях) описывающих метамодель UML. На 9.5.2 есть такое: Property::/isComposite:Boolean=false (также в 9.9.17.5). Авторы демонстрируют свой приём моделирования. Для каждого выводимого атрибута (полюса ассоциации) ими заводится одноимённая операция, возвращающая выведенное значение. Если OCL позволяет, то операции даётся тело (удивительно, что не derive!). См. Property::isComposite() в 9.9.17.7. Если убрать начальное значение в этом примере, смысл сохранится. Есть подозрение, что явное выписывание начального значения тут -- подсказка читателю диаграммы, чтобы ему не пришлось искать тело и в уме делать вывод. В схожей ситуации 12.3.2 (12.4.1.4, 12.4.1.6), Extension::/isRequired:Boolean вместо =false указано {readOnly}. Налицо "эффект дрожжей". Есть ещё одно подозрение, что завихрения относительно readOnly + derived рождались у авторов стандарта и обкатывались на метамодели UML. Т. е. это их реализаторская точка зрения, в которой они путают "не может меняться извне" с "не может меняться после инициализации". По каким-то причинам авторам эта путаница удобна. О том, как быть остальным, они не заботились. Щепотка "дрожжей" нашлась в описании Message::/messageKind {readOnly}. Для выводимого атрибута задана операция, тело которой состоит из возврата значения взятого у самого атрибута.
Наиболее часто встречается ошибка из 12., она же самая грубая. Основная причина
Цитировать
путают "не может меняться извне" с "не может меняться после инициализации"
Ситуация из 9. чревата тогда, когда правило начального значения и правило производного значения приводят к разным результатам. Init это только на момент инициализации, а derive - и на момент инициализации, и на всю последующую жизнь! (readonly только на оставшуюся жизнь, но не на момент инициализации)
Я полагаю, что слэш не устанавливает явной директивы, что нужно всё время пересчитывать. Стандарт на этот счёт лишь замечает, что изменение значения nonReadOnly выводимого свойства должно подчиняться derive-ограничению.
Производный атрибут может использоваться в invariant-ограничениях, а там неактуальным не место!

У тех же Новикова и Иванова про derive:
Цитировать
Буквально означает "может быть вычислен по". Зависимость с данным стереотипом применяется не только к классам, но и к другим элементам модели: атрибутам, ассоциациям и т.д. Суть состоит в том, зависимый элемент может быть восстановлен по информации, содержащейся в независимом элементе. Таким образом, данная зависимость показывает, что зависимый элемент, вообще говоря, излишен и введен в модель из соображений удобства, наглядности и т.д.
Если убрать зависимый элемент и заменить все его использования на его derive-определение, ничего измениться не должно. А это возможно, если зависимый элемент постоянно будет актуальным.

[прилетело НЛО и...]

  • Full Member
  • ***
  • Сообщений: 216
  • Рейтинг читателей: 26
    • Просмотр профиля
Ситуация из 9. чревата тогда, когда правило начального значения и правило производного значения приводят к разным результатам. Init это только на момент инициализации, а derive - и на момент инициализации, и на всю последующую жизнь! (readonly только на оставшуюся жизнь, но не на момент инициализации)
...
Производный атрибут может использоваться в invariant-ограничениях, а там неактуальным не место!
С Вами согласны авторы стандарта OCL 2.4 (см. 7.3.3, 7.3.7, 12.6, 12.9). Я спорить не буду, лишь замечу, что обороты вроде "всё время" употребимы для деклараций/спецификаций. С точки зрения реализации может быть достаточно [а может-- и нет], чтобы выводимый атрибут имел корректное значение всякий раз, когда оно затребовано. Такой ситуации отвечает описание ограничения как тела операции, а не как правила вывода. Быть может, то, что авторы UML 2.5 в метамодели UML описывают body, указывает на это обстоятельство. В этом можно увидеть различие между umlьным derive и oclьным (про oclьный явно сказано в тексте его стандарта, что он "всё время"). Забавно, что umlьные инварианты соблюдаются не всегда, если верить мелкому замечанию внутри 6.3.3. (мол, между вызовами операций объекта всё должно быть чики-пуки, а во время выполнения метода на инварианты смотрят сквозь пальцы).

У тех же Новикова и Иванова про derive:... Если убрать зависимый элемент и заменить все его использования на его derive-определение, ничего измениться не должно. А это возможно, если зависимый элемент постоянно будет актуальным.
Авторы "UML3" переводят пояснение, данное в стандарте к стереотипу <<derive>> из стандартного профиля, который можно применять к связям реализации и манифестации. Полагаю, что к этому случаю написанное и относится.
[...и улетело НЛО.]

Vadim

  • Full Member
  • ***
  • Сообщений: 150
  • Рейтинг читателей: 30
    • Просмотр профиля
С Вами согласны авторы стандарта OCL 2.4 (см. 7.3.3, 7.3.7, 12.6, 12.9).
Я с ними не согласен  :) (правда по другому поводу).
Из 7.3.7: "The derivation constraint must be satisfied at any time, hence the derivation includes the initialization. Both are allowed on the same property but they must not be contradictory. For each property there should be at most one initialization constraint and at most one derivation constraint.". Что привносит возможность одновременно задать и derive, и init? Проверить (гарантировать), что в момент инициализации выполняется дополнительное ограничение равенства одного и другого? А если понадобится проверить (гарантировать), что в момент инициализации выполняется какое-либо другое условие, вообще не связанное со значением описываемого property - придется всё равно использовать другой механизм (какой?, но это уже отдельный вопрос: как указать constraint, действующий не как invariant - всегда, а только в момент инициализации? Единственное, что пришло в голову - предусловие в конструктор. А если конструкторов несколько - в каждом прописывать одно и то же?)? Поэтому возможность одновременного указания derive и init считаю лишней - "For each property there should be at most one initialization OR derivation constraint." (если что-то не так с английским - простите).
Я спорить не буду, лишь замечу, что обороты вроде "всё время" употребимы для деклараций/спецификаций. С точки зрения реализации может быть достаточно [а может-- и нет], чтобы выводимый атрибут имел корректное значение всякий раз, когда оно затребовано. Такой ситуации отвечает описание ограничения как тела операции, а не как правила вывода. Быть может, то, что авторы UML 2.5 в метамодели UML описывают body, указывает на это обстоятельство. В этом можно увидеть различие между umlьным derive и oclьным (про oclьный явно сказано в тексте его стандарта, что он "всё время").
По-моему между "все время" и "когда затребовано" нет никакой разницы: даже про значение обыкновенного (не выводимого) атрибута известно только в тот момент, когда "затребовано" (ну и получено) это значение.
Забавно, что umlьные инварианты соблюдаются не всегда, если верить мелкому замечанию внутри 6.3.3. (мол, между вызовами операций объекта всё должно быть чики-пуки, а во время выполнения метода на инварианты смотрят сквозь пальцы).
Упоминается это мелкое замечание: " Note, however, that the postcondition is not required to hold during the
transient execution of the method behavior, but only at the stable point of the completion of execution of that
behavior. A class may also have invariant conditions that must be true before and after the execution of the
operation but may be violated during the course of the execution of the operation method." или что-то другое?

[прилетело НЛО и...]

  • Full Member
  • ***
  • Сообщений: 216
  • Рейтинг читателей: 26
    • Просмотр профиля
Что привносит возможность одновременно задать и derive, и init?
Да. Я по-прежнему думаю, что автор модели может потрафить читателю и явно выписать начальное значение выводимого атрибута (правда, затратив дополнительные усилия, чтобы они соответствовали друг другу). Например, площадь "хитрого" прямоугольника может быть единичной при неопределённом значении длины и/или ширины, а при заданных размерах равняться их произведению. Derive-правило в этом случает будет немного длинной oclькой, зато единицу можно указать не только в нём, но и явно на ДК.
 
Поэтому возможность одновременного указания derive и init считаю лишней - "For each property there should be at most one initialization OR derivation constraint." (если что-то не так с английским - простите).
Можно ей не пользоваться, но вдруг кем-то [авторами метамодели UML в одном единственном месте] востребовано.)

По-моему между "все время" и "когда затребовано" нет никакой разницы: даже про значение обыкновенного (не выводимого) атрибута известно только в тот момент, когда "затребовано" (ну и получено) это значение.
Можно вообразить себе, что какая-то модель запрещает реализацию с "ленивыми" сеттерами атрибутов. Т. е. не допускает стратегию, при которой изменения только обещаются к выполнению и откладываются до самого последнего момента, когда затребовано изменённое значение. В такой "неленивой" модели следует потребовать выполнимости "всё время". В "ленивой" модели достаточно ограничения "когда затребовано".
OCLьщики не ленивы. UMLьщики -- лентяи.))

Упоминается это мелкое замечание: " Note, however, that ...?
Да, оно.
[...и улетело НЛО.]

Vadim

  • Full Member
  • ***
  • Сообщений: 150
  • Рейтинг читателей: 30
    • Просмотр профиля
Да. Я по-прежнему думаю, что автор модели может потрафить читателю и явно выписать начальное значение выводимого атрибута (правда, затратив дополнительные усилия, чтобы они соответствовали друг другу).
Если изменится правило вычисления выводимого атрибута или начальное значение того, из чего выводится выводимый атрибут, то возможно изменится и выписанное начальное значение - автору надо не забыть исправить.
Например, площадь "хитрого" прямоугольника может быть единичной при неопределённом значении длины и/или ширины, а при заданных размерах равняться их произведению. Derive-правило в этом случает будет немного длинной oclькой, зато единицу можно указать не только в нём, но и явно на ДК.
context Прямоугольник::площадь derive: if длина->isEmpty() or ширина->isEmpty() then 1 else длина*ширина endif
context Прямоугольник::площадь init: 1
Подразумевается, что при инициализации длина и ширина получают "пустое" значение?
Можно ей не пользоваться, но вдруг кем-то [авторами метамодели UML в одном единственном месте] востребовано.)
Это единственное - Property::isComposite?

[прилетело НЛО и...]

  • Full Member
  • ***
  • Сообщений: 216
  • Рейтинг читателей: 26
    • Просмотр профиля
Если изменится правило вычисления выводимого атрибута или начальное значение того, из чего выводится выводимый атрибут, то возможно изменится и выписанное начальное значение - автору надо не забыть исправить.
Ну, да.

длина->isEmpty() or ширина->isEmpty()
меняем на длина=null or ширина=null
или лучше на длина.oclIsUndefined() or ширина.oclIsUndefined()

Подразумевается, что при инициализации длина и ширина получают "пустое" значение.
Смешно, нигде явно не сказано, что писать в oclьке, чтобы проверить, есть ли значение в слоте (атрибуте). Сами авторы стандарта UML при описании метамодели сравнивают с null в таких случаях. Дурной пример заразителен, так что я думаю, надо идти след в след за ними. Строго говоря, не инициализированный слот не получает никакого значения. Чтобы по умолчанию был null надо явно его указывать, как дефолтное значение [намёк на это есть в стандарте OCL]. Так что речь нужно вести не о том, что лежит в слоте, если туда ничего не положили и не указали дефолтное значение, а о том, как выглядит ocl-проверка.
 
Это единственное - Property::isComposite?
Да. Искало без фанатизма, но, кажется, больше такого нет.
[...и улетело НЛО.]

Vadim

  • Full Member
  • ***
  • Сообщений: 150
  • Рейтинг читателей: 30
    • Просмотр профиля
длина->isEmpty() or ширина->isEmpty()
меняем на длина=null or ширина=null
или лучше на длина.oclIsUndefined() or ширина.oclIsUndefined()
В 11.7.1 Collection (OCL) есть:
Note: null->isEmpty() returns 'true' in virtue of the implicit casting from null to Bag{}
Смешно, нигде явно не сказано, что писать в oclьке, чтобы проверить, есть ли значение в слоте (атрибуте). Сами авторы стандарта UML при описании метамодели сравнивают с null в таких случаях. Дурной пример заразителен, так что я думаю, надо идти след в след за ними.
Авторы OCL чаще используют oclIsUndefined(), но в одном месте (11.5.4) - null.
Строго говоря, не инициализированный слот не получает никакого значения. Чтобы по умолчанию был null надо явно его указывать, как дефолтное значение [намёк на это есть в стандарте OCL]. Так что речь нужно вести не о том, что лежит в слоте, если туда ничего не положили и не указали дефолтное значение, а о том, как выглядит ocl-проверка.
Для меня это слишком: отдельно null, отдельно "никакое значение". Можно ссылку на намёк?
Да. Искало без фанатизма, но, кажется, больше такого нет.
Я тоже искал по-простому: у каких derived есть ещё и init. Так можно пропустить случай, если между атрибутами redefinition, один из них derived, а другой - с init.

А про это что-нибудь:
как указать constraint, действующий не как invariant - всегда, а только в момент инициализации? Единственное, что пришло в голову - предусловие в конструктор. А если конструкторов несколько - в каждом прописывать одно и то же?

[прилетело НЛО и...]

  • Full Member
  • ***
  • Сообщений: 216
  • Рейтинг читателей: 26
    • Просмотр профиля
В 11.7.1 Collection (OCL) есть:
Note: null->isEmpty() returns 'true' in virtue of the implicit casting from null to Bag{}
Я, наверно, ошибаюсь, считая, что тип длины определяется статически по её описанию, а не динамически по её значению.

Авторы OCL чаще используют oclIsUndefined(), но в одном месте (11.5.4) - null.
oclIsUndefined() лучше тем, что сработает при неправильно вычисленной длине (например, полученной делением на 0).

Для меня это слишком: отдельно null, отдельно "никакое значение". Можно ссылку на намёк?
A.2.1.1 Пример с email'ами.

А про это что-нибудь: ...
Угу. Полагаю, что общую часть можно описать один раз через def, а затем использовать в предусловиях разных конструкторов.
[...и улетело НЛО.]

Galogen

  • Member of CAR
  • Hero Member
  • *****
  • Сообщений: 6061
  • Рейтинг читателей: 192
  • Аксакал
    • Просмотр профиля
    • Профиль в Моем Круге
Вам надо время от времени итожить свою дискуссию, чтобы сохранить ее понимание для окружающих :)

[прилетело НЛО и...]

  • Full Member
  • ***
  • Сообщений: 216
  • Рейтинг читателей: 26
    • Просмотр профиля
Строго говоря, не инициализированный слот не получает никакого значения.
Имелось в виду, что не получает никакого значения при инициализации экземпляра.) По стандарту при мощности 1..1 значение быть должно. Можно предположить, что перед инициализацией, т. е. при создании "голого" экземпляра в атрибуты кладутся какие-то значения, как того требуют мощности. Но что это за значения, можно лишь гадать (и сравнивать с null, как это делают авторы метамодели:)).
[...и улетело НЛО.]

[прилетело НЛО и...]

  • Full Member
  • ***
  • Сообщений: 216
  • Рейтинг читателей: 26
    • Просмотр профиля
Эти авторы когда-то впечатлили меня фразой: "существуют три уровня понимания обучающимся нового предмета, которые характеризуются следующими признаками: 1. Возникает приятное чувство понимания; 2. Может повторить своими словами; 3. Видит ошибки.
Навеяло. У Коберна 4 уровня понимания: http://alistair.cockburn.us/Shu+Ha+Ri+Kokoro
[...и улетело НЛО.]

Vadim

  • Full Member
  • ***
  • Сообщений: 150
  • Рейтинг читателей: 30
    • Просмотр профиля
Навеяло. У Коберна 4 уровня понимания: http://alistair.cockburn.us/Shu+Ha+Ri+Kokoro
Неважно сколько уровней, важно, что только последний имеет реальную ценность.
Я, наверно, ошибаюсь, считая, что тип длины определяется статически по её описанию, а не динамически по её значению.
Множественность длины [0..1], поэтому тип длины такой, что включает и null-значение.
A.2.1.1 Пример с email'ами.
Все равно не уловил. :'(
Угу. Полагаю, что общую часть можно описать один раз через def, а затем использовать в предусловиях разных конструкторов.
Это способ лучшего описания конструкторов, а хотелось обойтись без конструкторов.
Вам надо время от времени итожить свою дискуссию, чтобы сохранить ее понимание для окружающих :)
Пока не готов.

[прилетело НЛО и...]

  • Full Member
  • ***
  • Сообщений: 216
  • Рейтинг читателей: 26
    • Просмотр профиля
Множественность длины [0..1], поэтому тип длины такой, что включает и null-значение.
При такой мощности мне придраться не к чему.

Все равно не уловил.
Там указывается, что null можно использовать не только как пустое значение, но и как неопределённое значение. Если в Client::email положили null (например, конструктором/init'ом), то это может явно указывать, что емэйл неизвестен. Из  того, что авторы стандарта OCL пишут "may, for example, be assigned to an attribute", можно сделать вывод, что null не возникает сам по себе как неопределённое значение атрибута. Авторы метамодели UML полагают, что возникает, но не оговаривают это в своём стандарте.

хотелось обойтись без конструкторов
Попадались попытки расширения языка в сторону темпоральности, но я внимательно в эту сторону не смотрело.

[...и улетело НЛО.]