Ты знаешь, я подумал, что наши решения в принципе эквивалентны, с точностью до разницы между концепциями is-a и can
Т.е. по сути, на псевдокоде их сущность будет выглядеть так:
//мое, на C++
if (stream is-a file)
stream.seek();
else if (stream is-a socket)
stream.read();
//твое, на Ruby
if (stream can seek)
stream.seek();
else if (stream can read)
stream.read();
"Синтаксический сахар" компилятора делает мое решение выглядящим слегка изящнее.
В целом — все аналогично.
Здравствуйте, Sinclair, Вы писали:
VD>>CanSeek — это свойство. И дело тут даже не в базовости потока, а втом, что когда их разрабатывали дженериков еще небло. А так можно было бы в принципе сделать иерархию потоков. S>Вот с этого места я вообще не понимаю.
Не ясно что именно не понимашь. Если про иерархию, то ниже я описал свое видение.
VD>>Однко нафига тут дженерики не ясно. S>А вот с этого — уже понимаю
Ну, это главное.
S>А ну как кто-то меня интом параметризовать вздумает? Где смысл, где логика?
Ну, от этого можно защититься констрэйном (только они для этого должны быть).
S>Ну, я все едино даункасты не люблю.
Дык что значит люблю не люблю? Не пиши код динамически разбирающийся в типе потока. Пиши код универсальный.
S> Но сама идея, имхо, в целом правильная. Тогда можно строить алгоритмы, которые честно декларируют свои требования в виде типов параметров, а не в виде документации.
Именно. А в примере eao197 уже была логика распознования типа. Вот для нее прийдется исползовать приведение типов, но все же это будет безопасный и понятный код. Причем без всяких выкрутасов и кодогенераций:
IRandomInputStream randomStream = stream as IRandomInputStream;
if (randomStream == null)
проматываем стрим прочитыванием...
else
randomStream.Seek(...);
Такой код уже можно считать рантайм оптимизацеией. Он будет корректно работать с любым типом InputStream-а. Просто с IRandomInputStream он будет работать быстрее. Причем пользователю функции не прийдется думать о комромисе, так как он просто отдаст нужный поток, а алгоритм сам поймет можно ли работать умнее.
В примере eao197 по всей видимости подразумевалась кодогенерация. При этом пришлось бы или явно приводить тип перед вызовом фунции или оптимизация просто не сработала бы так как использовалась бы статитческая информация о типах.
S>...можно было бы делать S>
Именно. Статическая типизация в действии. К сожалению не разумное проектирование интерфейсов бичь очень многих программистов. На ту же Сцинтилу без слез не взглянешь. Причем объем кода у них больше, а количество классов меньше. Парадокс, однако.
S>Однако, как я подозреваю, микрософтеры сделали стрим таким потому, что считают свойства CanXXX не неотъемлемо приданными на уровне класса, а изменчивыми за время жизни одного и того же стрима. Это — единственное оправдание подобной архитектуры.
Думаю, что они это сделали по глупости. Точно так же как использовали базовый класс вместо интерфейса.
... << RSDN@Home 1.2.0 alpha rev. 618>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Кодёнок, Вы писали:
Кё>Кстати, динамическая типизация тут не обязательна. В С++ возможно объявить шаблонную функцию, которая будет рассчитывать на наличие определенных методов у своих аргументов — и это будет то же самое (только эти требования нигде в коде явно не прописаны, что плохо).
Немного оффтоп, но к слову считаю что это "упущение" является одним из принципиальных ляпов при введении шаблонов в C++.
Ведь что стоило ввести описание типа и его свойств доступных данному шаблону ? Кокоенить ключевое слово похожее на виртуал типа "templated".
Данвно , года 4 назад я был ярым сторонником шаблонов в C++ , видел их потенциал и т.д. Но этот ляп явился одним из решающих , который позволяет мне относиться к шаблонам с большой опаской (есть и другие недостатки).
Здравствуйте, VladD2, Вы писали:
VD>Отличия с Явой тут нет. Явские дженерики тоже рантаймная сущьность. Просто они обрабатываются за счет полиморфизма.
Влад что ты имеешь ввиду под "рантаймная сущьность"? Явские дженерики с рантаймом никак не связаны. Вообще.
S>>И тут уж ты не ошибешься.
VD>Именно. Статическая типизация в действии. К сожалению не разумное проектирование интерфейсов бичь очень многих программистов.
А другой бичь, имхо, в попытке привести разные типы к одинаковым интерфейсам.
Как раз мне кажется, что duck typing, это разновидность обобщенного программирования, при котором устранение несоотвествия между типами устраняется более простым способом.
И нужно написать функцию, которая может взять N байт из объекта любого из этих типов. Эти классы не имеют общего интерфейса, да и вряд ли вообще должны.
Варианты с наследованием или со стратегиями требуют создания отдельных классов или иерархий классов, т.е. порождения новых сущностей только для того, чтобы привести уже существующие сущности к одному знаменателю. А подход на основе duck typing не требует этого:
def get_n_bytes( device, bytes )
if device.respond_to? :read then return device.read bytes
elsif device.respond_to? :get then return device.get bytes
elsif device.respond_to? :peek then return device.peek bytes
else
fail "unknown beast: #{device.class.name}"
end
end
Хорош такой подход или нет, но он точно позволяет отказаться от введения новых типов.
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
minorlogic wrote:
> Немного оффтоп, но к слову считаю что это "упущение" является одним из > принципиальных ляпов при введении шаблонов в C++. > Ведь что стоило ввести описание типа и его свойств доступных данному > шаблону ? Кокоенить ключевое слово похожее на виртуал типа "templated".
Нечто такое собираются добавить в следующий Стандарт С++ под названием
"Concepts".
> Данвно , года 4 назад я был ярым сторонником шаблонов в C++ , видел их > потенциал и т.д. Но этот ляп явился одним из решающих , который > позволяет мне относиться к шаблонам с большой опаской (есть и другие > недостатки).
Нынешние шаблоны были спроектированы в 93 году Так что вполне
понятно, что они не получились абсолютно идеальными.
Здравствуйте, Cyberax, Вы писали:
C>Нечто такое собираются добавить в следующий Стандарт С++ под названием C>"Concepts".
Хорошо бы , но к сожалению в целях совместимости , как всегда оставят прежние шаблоны .
C>Нынешние шаблоны были спроектированы в 93 году Так что вполне C>понятно, что они не получились абсолютно идеальными.
Да уж , тогда явно разработчики мало задумывались о таких вещах как кривая обучения , читабельность , отладка и т.д. Явно внимание было сфокусированно на эффективности "code reuse" и т.д.
Здравствуйте, VladD2, Вы писали:
VD>Ну, вот С++ со своими шаблонами сегодня живет по принципу "like-a". Что приводит к забавнейшему поведению компилятора. На такой вот простой код: VD>
VD>Он реагирует выдачей следующего сообщения об ошибке: VD>
e:\vs\vs2005\vc\include\xutility(1059) : error C2665: 'std::_Debug_range2' : none of the 2 overloads could convert all the argument types
...
VD> ]
VD>Причем указывает он при этом не на строку с ошибкой, а на строку 1059 в файле e:\vs\vs2005\vc\include\xutility
VD>Между тем проблема лекго решается путем введения интерфейса.
Еще лучше она решается введением concept-ов. Причем для этого даже не нужно ждать C++0x (уж извини за длинный код, оставил только самое нужное):
template< class T >
struct sort_concept_test
{
static void constraint( T a )
{
++a;
T b = a;
b = a;
*b = *a;
bool l = *a < *b;
}
sort_concept_test() { void (*p)(T) = constraint; }
};
template< class T >
void my_sort( T a, T b )
{
sort_concept_test< T >();
std::sort( a, b );
}
int
main()
{
int i, j;
my_sort( i, j );
}
Получаем:
sort_2.cpp
sort_2.cpp(13) : error C2100: illegal indirection
sort_2.cpp(7) : while compiling class-template member function 'void sort_concept_test<T>::constraint(T)'
with
[
T=int
]
sort_2.cpp(23) : see reference to class template instantiation 'sort_concept_test<T>' being compiled
with
[
T=int
]
sort_2.cpp(32) : see reference to function template instantiation 'void my_sort<int>(T,T)' being compiled
with
[
T=int
]
sort_2.cpp(13) : error C2100: illegal indirection
sort_2.cpp(14) : error C2100: illegal indirection
sort_2.cpp(14) : error C2100: illegal indirection
Если же заменяем вызов my_sort:
std::vector< int > i;
my_sort( i.begin(), i.end() );
То все компилится как надо.
Вот почему в STL такие concept-ы сейчас не используются -- это другой вопрос. В Boost-е уже давно готовая библиотека есть: Boost.ConceptCheck.
E>>Да, но особенности Lisp-а и Smalltalk-а не позволили им набрать большого количества сторонников. А вот Python и Ruby популярность набирают.
VD>Вся история программирования — это борьба с подходами вроде утиной типизации. А ты призывашь обратно в каменный век.
История развивается по спирали. Не зря же сейчас интерес к динамическим языкам возврастает.
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, minorlogic, Вы писали:
M>Здравствуйте, Cyberax, Вы писали:
C>>Нечто такое собираются добавить в следующий Стандарт С++ под названием C>>"Concepts". M>Хорошо бы , но к сожалению в целях совместимости , как всегда оставят прежние шаблоны .
ANS>Smalltalk считается объектно-ориентированным, а должен был бы ориентированным на обмен сообщениями.
Тут надо добавить для ясности.
Термин объектно-ориентированный больше подходит для C++,ObjectPascal,Java,С# и т.п. чем для Smalltalk, Self,... Языки типа C++ не являются объектными, т.е. далеко не все (а точнее почти ничего) с чем приходится иметь дело не является first-class-object. Тем не менне эти языки имеют массу кострукций, которые ориентированны на построение объектных программ.
В Smalltalk все (ну, почти все) является first-class-object. В частности first-class-object являются класс, методы, сам факт посылки сообщения, стек, тело методов и т.д. По-этому такой язык стоит называть объектным, а термин объектно-ориентированный плохо описывает ключевые особенности. Alan Kay imho предложил назвать Smalltalk "ориентированным на обмен сообщениями" что бы подчеркнуть эти особоенности. Стоит так же отметить, что Smalltalk именно ориентирован на обмен сообщениями, точно так же как C++ ориентирован на объекты. Не все, что по объектной концепции, должно бы, в Smalltalk является, сообщением. Например, возврат (из метода), не является посылкой собщения вызывающему объектку. Так же выброс исключения это тоже не сообщение.
Здравствуйте, VladD2, Вы писали:
D>>Более того, типизация несколько мешает ОО. Чистое объектное видение мира подразумевает, что любой объект потенциально может отреагировать на любое сообщение. Или другими словами есть только объекты и объекты не различаются ничем, кроме своего поведения. Например, к каждому человеку можно подойти с вопросом "Сколько ты будешь делать?" с параметром ТЗ. На что можно получить в ответ "N дней", "Чего???" или по зубам . Но коллапса с миром не случит в любом случае.
VD>Вот здесь уже противоречие. С одной стороны "объекты не различаются ничем, кроме своего поведения", с другой походить ты хочешь к "каждому человеку", а не, к примеру, к столбу. Ведь у столба ты вряд ли сможешь спросить что либо, и уж точно не сможешь дать ему по зубам. Так что класс объекта определяет его интерфейс. И если мы заранее знаем класс объекта, то можем не далать бессмысленных действий.
Спросить могу, другое дело, что в ответ ничего не будет, но всеравно могу, и ударить могу тоже. Конечно введение типов помогает узнать, что с объектном делать, можно, а что нельзя. Но так же типы мешают применять некоторые решения, например в DolphinSmalltalk есть класс DeafObject. Объектны этого класса реагируют на все сообщения и возвращают себя же. Применяется как замена кода типа:
if (object.hasValue()) {
value = object.getValue();
// Работаем c value.
}
на
value = object.getValue();
// Работаем c value. Если value это экземпляр DeafObject, то вся работа с value будет проигнорированна.
Только, пожалуйста, не надо комментировать этот паттерн, я в курсе, без него можно обойтись, а применять стоит очень осторожно.
Другое применение: логгер вызванных методов — иногда очень удобно в тестах.
VD>Например, если мы заблудились в лесу, то мы не будем подходить к каждому дереву и спрашивать "как выйти к станции?", за то мы будем подходить с подобным вопросом к каждому всречному человеку.
При наличии света в лесу, деревья шлют тебе сообщения, что они деревья. Дальше включается логика, которая позволяет не делать глупостей С другой стороны, при плохом освещении, может случиться, что ты пойдешь спрашиват к столбу (перепутаешь), и не пойдешь спрашивать у человека (не увидишь).
Применить то можно , но во первых совсем не изящно. А во вторых можно вообще програмировать красиво , без ошибок и т.д. Хороший язык должен поощрять хороший стиль и запрещать плохой.
Ну и сам посмотри трезвым взглядом сколько кода надо писать , который не несет ФУНКЦИОНАЛЬНОСИ. C++ мощный язык , тем что позволяет писать такие вещи , или например сборщики мусора. Но недостатков у него тоже хватает ... даже через чур ..
Здравствуйте, eao197, Вы писали:
E>Есть такая штука: Duck typing. В языках, вроде Smalltalk, Python и Ruby она активно используется.
E>Еще есть несколько интересных обсуждений: Следующий язык программирования
Рассмотрим типичную ситуацию. Есть проект, живет он давно и понаделано за это время не мало. Стоит задача прикрутить фичу. И так получается, что фича может быть удобно сделана полагаясь на уже существующие абстрастные методы определенные в разных классах. К примеру, задача может выглядить так: есть список разнородных объектов, на реализовать еще одно полезное действие, применимое к некоторым из них (типа в IDE есть список из классов\методов\филдов\... хотим сделать переименование). И все было бы просто, если бы эта новая фича гладко ложилась на существующую архитектуру. Ан-нет — не ложится, нужны новые интерфейсы и местами новые методы.
Что делаем без уток:
1. Заводим интерфейс(ы), который должны бы поддерживать все элементы списка.
2. Пишем, что соотвествующие классы его поддерживают.
3. Пишем фичу. По мере написания добавляем в интерфейс необходимые методы и имплементим их в классах, где раньше они были не нужны, а теперь понадобились.
4. Переодически запускаем (возможно приложение, возможно тесты) и смотрим, что получается иногда глазами, а иногда дебагером. Из увиденного делаем выводу, выясняем тонкие подробности и возвращаемся к пункту 3.
Иногда натыкаемся на некоторые особенности некоторых реализаций, которые проваливают, казалось бы прямое красивое решение. Иногда фиксим и выпрямляем, а иногда не получается. Когда не получается приходится искать другое решение, заводить другие методы.
Теперь как с утками:
Первые два пункта смело пропускаем, новую фичу без типизации.
На 3 пункте имеем упрощение жизни, за счет не добавления методов в классы, в которые их довавить тяжело. Все компилится и без них. Так же в нашем распоряжении появляется возможность быстро пробовать решения и быстро обламываться, если они не работают. При этом можно не отвлекаться на вставку заглушек во все классы (даже если IDE эти заглушки вставит более-менее сама мысль может немного потеряться). И, что важнее, не забываем эти заглушки убирать, когда оказалось, что они не нужны (делать будем по-другому).
Вот так прыгаем между 3 и 4, пока не получаем, работающую или почти работающую фичу.
Здесь уток выкидываем, а точнее переходим к пункту 1, а потом 2 — вписываем типы. А компилятор проверяет, что мы не пропусти ни один класс и везде все необходимые методы есть.
В общем еще одина изложение довольно старой идеи — дизайн удобнее и легче делать, когда уже есть код на этом дизайне построенный, чем гадать, что понадобится, а что нет.
Более того, если охота погадать заранее, то пожалуйста, пунты 1 и 2 выполняем ровно на столько на сколько гадать кажется эффективным и полезным.
E>
E>за синтаксис не ручвюсь, но смысл думаю понятен.
E>Но это все из другой оперы. Это все для скриптов. А в нормальных ОО-программах типизация должна быть типзацией, а не утиными историями.
Получаем удобство утиных историй на стадии разработки-исследования, а потом, когда маршрут между граблями уже проложен, делаем из них "нормальную ОО-программу". При этом почти не делаем работу которую заведомо собираемся выкидывать (даже если граблей не оказалось).
E>Так вот мне интересно, не является ли Duck typing следующей парадигмой после ООП? Ведь в duck typing нет отношения "is-a", а есть что-то типа "like-a" ("can", "respond_to"). Т.е., наследования нет, а полиморфизм, благодоря динамической типизации, есть.
Такое использование утиных историй поможет решить проблемы ООП совмещенного со строгой типизацией.
E>Может быть ООП уже исчерпала себя (как в свое время структурное программирование) и сейчас мы наблюдаем за возникновением новой парадигмы?
Скорее утиные истории поддерживают более чистое объектное мышление, чем статическая типизация. Вот объект, я хочу послать ему это сообщение, а какого он должен быть типа решим немного позже.
Здравствуйте, Dyoma, Вы писали:
D> Например, возврат (из метода), не является посылкой собщения вызывающему объектку.
Угу.
D>Так же выброс исключения это тоже не сообщение.
Ага. Не одно сообщение, а целая куча
Если серьёзно, подозреваю, что зависит реализации (изначально ведь никаких исключений не было). В VisualWorks исключения реализованы средствами языка.
<...> D>Вот так прыгаем между 3 и 4, пока не получаем, работающую или почти работающую фичу. D>Здесь уток выкидываем, а точнее переходим к пункту 1, а потом 2 — вписываем типы. А компилятор проверяет, что мы не пропусти ни один класс и везде все необходимые методы есть.
Имхо, была когда-то такая заповедь у программистов: никогда не переделывай работающий код
В этом свете мне не понятно, зачем после успешно завершенных шагов 3 и 4 переделывать уже работающий код для выполнения шагов 1 и 2?
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, eao197, Вы писали:
D>>Вот так прыгаем между 3 и 4, пока не получаем, работающую или почти работающую фичу. D>>Здесь уток выкидываем, а точнее переходим к пункту 1, а потом 2 — вписываем типы. А компилятор проверяет, что мы не пропусти ни один класс и везде все необходимые методы есть.
E>Имхо, была когда-то такая заповедь у программистов: никогда не переделывай работающий код E>В этом свете мне не понятно, зачем после успешно завершенных шагов 3 и 4 переделывать уже работающий код для выполнения шагов 1 и 2? E>
1. Не переделывать, а верифицировать компилятором (системой типов). Деятельность схожая с запуском написанного кода, дабы увидеть своими глазами, что оно таки работает
2. Списать типы не обязательно в самом конце, это можно сделать как только появится твердая уверенность, что дизайн устаканился и все грабли уже найдены и обходные пути известны. Т.е. когда появится ощущение, что дальнейший отказ от типов не даст выигрыша.
3. Добавить декларацию типов в качестве комментариев (документации)
Здравствуйте, Blazkowicz, Вы писали:
B>Влад что ты имеешь ввиду под "рантаймная сущьность"? Явские дженерики с рантаймом никак не связаны. Вообще.
То что код не становится специализированным в процессе компиляции.
Собственно Явовские дженерики это на половину обман, так как в рантайме все живет на динамическом полиморфизме. Но с другой стороны это не припятствует тому, чтобы дженерики помещались в библиотеки. В конце концов свою задачу они решают. Человек не обязан клепать кучу специализированных классов или явно пользоваться приведениями типов.
... << RSDN@Home 1.2.0 alpha rev. 618>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, eao197, Вы писали:
E>Еще лучше она решается введением concept-ов. Причем для этого даже не нужно ждать C++0x
Хреновенько. Я бы даже сказал чуть-чуть замазывается, а не решается.
E>(уж извини за длинный код, оставил только самое нужное):
Да, в этот раз как раз более-менее коротко. Всегда бы так. E>Получаем:
А получаем мы ту же задниц, вид с боку. Чуть лучше чем было, но все равно задница.
О чем говорит это сообщение об ошибке?
E>[q] E>sort_2.cpp E>sort_2.cpp(13) : error C2100: illegal indirection E> sort_2.cpp(7) : while compiling class-template member function 'void sort_concept_test<T>::constraint(T)'
E>Вот почему в STL такие concept-ы сейчас не используются -- это другой вопрос. В Boost-е уже давно готовая библиотека есть: Boost.ConceptCheck.
Ну, в STL только интерфейс описан. А реализация может быть любой. В том числе и с концептами. Думаю, когда появятся полноценные коцепты, то их сразу же вставят в большинство реализаций.
Только все равно концепты не приучат народ использовать их во всех шаблонах и описанная ситуация еще не раз повторится.
VD>>Вся история программирования — это борьба с подходами вроде утиной типизации. А ты призывашь обратно в каменный век.
E>История развивается по спирали. Не зря же сейчас интерес к динамическим языкам возврастает.
Именно, что по спирали! Но если история развивается по груку, то это не развитие. Вот идея концептов, коие на самом деле являются ни чем иным как утиными интерфейсами — это действительно развите по спирали. Если к ней еще прикрутить некую идею мапинга типа на утиный интерфейс, то будет вообще круто. Если не ясно о чем я говорю, то поясню. Предположим у нас есть некий тип который нельзя напрямую ассоциировать с утиным интерфейсом, ну, например, у него имя метода не совпадает или у метода лишний параметр. Чтобы этот тип можно было использовать там где допустим утиный интерфейс прийдется применить классический ОО-паттерн — обертку (врапер). Но каждый раз явно создавать эту обертку некрасиво, да и не эффективно. Вместо этого можно было бы дабавить некий синтаксис который позволил бы указать компилятору что при попытке привести тип к утиному интерфейсу нужно (а может и к полноценному если поддержка таковых в языке есть) нужно использовать эту обертку. При этом можно будет задать еще и некотороую информацию о некоторых тонкостях маппинга (а может это будет и не нужно).
Однако то что есть в С++ сегодны (да и в Руби) — это попрание всех принципов безопасного и понятного программирования. Так что вводя в язык концепты и интерфейсы нужно ко пвсему прочему удалять из него "возможности" неявногой утиности. В общем, "Маг.Дак маст дай".
... << RSDN@Home 1.2.0 alpha rev. 618>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Можешь. Вот только тогда уже можно задуматься о твоей адекватности. Тех кто часто такими вещами занимается, обычно кладут в соотвествующие лечебные заведения.
В жизни от неадекватного поведения спасает собственный разум и самоконтроль. В программировании компилятор и рантайм. Это же удобно. Представь как сожно будет программировать если контроль исчезнет? Это же будет хуже ассемблера.
D>Только, пожалуйста, не надо комментировать этот паттерн, я в курсе, без него можно обойтись, а применять стоит очень осторожно.
Ну, ты сам все сказал. Мне добавить нечего.
D>Другое применение: логгер вызванных методов — иногда очень удобно в тестах.
А что логер? Смолток не единственный язык где "все является объектом". В C# у меня тоже все является объектом и если мне понадобится, то я смогу написать универсальный код. Например, Console.WriteLine() прекрасно отображает информацию о любом объекте. Правда тут скорее используется полиморфизм, но все же. Главное, что передаю я ему все приводя к типу object. Ну, и как последний аргумент — рефлексия. С ее помощью любой логе и т.п. пишется как нефиг делать. Заметь все эти идеи позаимствованы у Смолтока, но при этом они прекрасно работают в статически типизированном языке. И отлично, надо сказать, работают!
D>При наличии света в лесу, деревья шлют тебе сообщения, что они деревья.
Это надо с дубу рухнуть, чтобы разговаривать с деревьями.
Мы анализируем деревья с помощью данных нам органов чувств (больше зрением). Далее мы принимаем решение о типе объекта на который мы смотрим, и принимаем решение, что объект данного типа на нужный нам запрос не ответит.
D> Дальше включается логика, которая позволяет не делать глупостей
У вас, у любителей Смотока, наблядается некий сдвиг по фазе. Видимо это от неконтролируемости. Шучу.
D> С другой стороны, при плохом освещении, может случиться, что ты пойдешь спрашиват к столбу (перепутаешь), и не пойдешь спрашивать у человека (не увидишь).
Погоди ка. Он же тебе сообщения шлет! Зачем же тебе к нему ходить? Или все же я прав?
... << RSDN@Home 1.2.0 alpha rev. 618>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.