Как стать автором
Поиск
Написать публикацию
Обновить

Комментарии 40

/me глядя на голосование: интересно, какие причины побуждают использовать 2.3.* в продакшне
Скучная офисная жизнь обрыдла, а на костюм для base-jumping’a денег не хватает?

Чем больше проблем, тем больше работы.
Чем больше работы, тем больше зарплаты.
… тогда в image окажется false, а не nil. А если проверять на nil?, то код будет выглядеть еще запутаннее.

Отдельно на nil проверять не нужно, кстати, он отработает аналогично false:

irb(main):001:0> !!nil
=> false
Проверять на nil может понадобиться, если кто-то может легитимно вернуть false. Например, если бы хранилось булево значение user.profile.confirmed, то при user && user.profile && user.profile.confirmed кто-то, получив false при user nil, может вызвать user.profile.resend_confirmation.
В Вашем примере, конечно, код «попахивает», но идею я понял и согласен.
«Проблема» безопасной навигации интересно решена в OCL.

Опциональные свойства имеют тип Bag (набор значений, коллекция). Если свойству не присвоено значение, то возвращается пустая коллекция. Иначе возвращается коллекция с одним значением (значением свойства).

При этом оператор "." применяется не ко всей коллекции, а к каждому элементу отдельно.

На языке OCL выражение «user.profile.thumbnails.large» будет интерпретировано так:

1) Получаем у user значение свойства profile.
1а) Если оно указано, то получаем коллекцию с этим значением,
1б) Иначе получаем пустую коллекцию.

2) Затем у каждого элемента этой коллекции обращаемся к свойству thumbnails и формируем из полученных значений новую коллекцию.
2а) Если на первом шаге получили коллекцию с одним профилем, то
2а1) Если у профиля есть thumbnail, то получаем коллекцию с иконкой этого профиля
2а2) Если у профиля thumbnail не указан, то получаем пустую коллекцию иконок
2б) Иначе (если на первом шаге получили пустую коллекцию), то и тут получаем пустую коллекцию

И т.д. Описал наверно очень туманно :) Но по-моему это идеальное решение, странно что в других языках такого нет. Нет, ещё в XPath есть, но в языках общего назначения ни разу не встречал.
Интересное поведение. В Groovy есть чуть-чуть похожий оператор '*.', он вместе с безопасной навигацией введен. Но и в нем это сильно отличается от описанного вами.
Да, очень похож! К тому же в OCL этот оператор, как и в Groovy, разворачивается в вызов операции collect.

Только, как я понял, он ведёт себя иначе для null-евых элементов списка. В OCL будет ошибка, а Groovy в результирующей коллекции сделает тоже null.

И ещё отличие, что в OCL входная и выходная коллекции оператора "." могут содержать разное количество элементов. А в Groovy, как я понимаю, они всегда равны по размеру.

Если кому-то интересно, то тут спецификация OCL (раздел 7.4.10), а тут статья на русском. В OCL всего два оператора для навигации: точка "." и стрелочка "->".

Если точка применяется к объекту, то интерпретируется как и в большинстве языков типа Ruby, Java.

Если точка применяется к коллекции, то она разворачивается в оператор collect (как и в Groovy): aSet->collect(name).

Стрелочка обычно применяется к коллекции, например, чтобы посчитать сумму значений: aSet->sum()

Но если применена к одиночному объекту типа «anObject->sum()», то объект неявно кастится во множество: anObject.oclAsSet()->sum()

Таким образом в OCL выражение «user.profile.thumbnails.large» развернется в «user.profile->collect(thumbnails)->collect(large)»

Удобно то, что всего два оператора для навигации. И с пустыми множествами или синглетонами работать удобней, чем с null.
Проблема безопасной навигации (или композиции функций) в том, что есть некое супер-значение, которое населяет все типы и ведет себя отлично от других граждан этого типа отказываясь отвечать на ожидаемые сообщения (если вы не в Objective-C или SmallTalk, где nil принимает любое сообщение и возвращает себя же). Поговаривают, что индустрия разработки программного обеспечения из-за этого потеряла как минимум миллиард долларов.

Наряду с тем, что добавили в Ruby, иной популярный способ избавиться от злополучного гражданина — явно указывать, что значение может отсутствовать. Обычно такой тип называет Option, или Maybe. На мой взгляд, это решение лучше потому, что программист точно знает, что в этом месте может ничего не быть и остаток цепочки не выполнится. К сожалению, добавить Option в язык ретроспективно не очень просто, а в язык с динамической типизацией вообще не представляется возможным.

Недостаток второго метода в том, что теперь возвращаемое значение предыдущей функции не совпадает с принимаемым значением (или self в OO языках) последующей и их нельзя просто так взять и связать точкой. Но эта проблема решена с помощью композиции в некой категории Клейсли, при чем, она предлагает более общее решение, которое помогает не только при вероятном отсутствии значения, но и в других ситуациях. Например, неопределенности, которая часто встречается при parsing'е того же программного кода. Интересная концепция, кстати, рекомендую почитать на досуге.
Конечно, я знаю о монадах. В Haskell есть интересное обобщение монад — стрелочки :) Тип Bag, который в OCL используется для опциональных значений — это фактически и есть некий аналог Maybe, но со своими отличиями.

В OCL логика очень простая. Обычно в разных ЯП если свойство может принимать несколько значений, то для него используется какой-нибудь тип-коллекция (множество, список, массив, ...). А в OCL вообще для всех свойств, множественность которых отличается от 1, используются коллекции. В т.ч. для свойств со множественность 0..1.

На мой взгляд, это гораздо более естественный подход, чем использование null или каких-то дополнительных типов Maybe. Если свойство опциональное, значит его значения нужно представлять в виде коллекции, которая может быть либо пустой, либо синглетоном.

Если программист видит, что у свойства тип Bag, то он понимает, что значение опциональное.

Добавляем к этому два оператора для навигации "." и "->". Первый применяется к одиночным значениям, второй — к коллекциям.

И доопределяем точку и стрелочку для коллекций и единичных объектов соответственно:
1) Если точка используется для коллекции, то применяется к каждому её элементу.
2) Если стрелочка используется для единичного значения, то оно неявно преобразуется в синглетон.

Всё, получаем удобный язык без всяких Maybe, null и т.п. На самом деле, null в OCL есть, но у него немного другая роль, чем в обычных ЯП.

Чем это лучше… Например, при использовании Maybe приходится использовать явно bind, return. А в OCL это не нужно, выражения выглядят гораздо короче, понятней и естественней.
Не совсем List :) Если совсем строго, то в OCL 4 вида коллекций: Bag, Set, OrderedSet, Sequence. Которые отличаются упорядоченностью и уникальностью элементов. Bag — это не упорядоченная коллекция не уникальных элементов. List ближе к Sequence.

Я и говорю, что Bag — это «некий аналог Maybe, но со своими отличиями», который вместе с операторами "." и "->" позволяет добиваться примерно того же, для чего предназначен Maybe, но проще.
Все равно как монада, Bag ближе к List чем к Maybe.
Я это как-то пропустил, так как на практике сравнения хэшей, кроме matcher в rspec, нигде больше не встречал.

А вопрос, действительно, получается интересный. Сначала вводился только метод проверки на включение субхэша, а потом оказалось, что это хорошо укладывается в частично упорядоченные множества.
Кстати, тогда логично еще добавить <=> в Set.
По-моему, как раз очень плохо укладывается. Я там об этом в багтрекере довольно подробно написал. Плюс, как мне в комментариях к обсуждению в ЖЖ объяснили, главная проблема — несогласованность операций `<=` и `==`.
Там не укладывается из-за того, что вместо для несравниваемых элементов отдают false вместо nil. Если отдавать nil, то все будет в порядке.

С '==' в ЖЖ не видно, откуда проблема. Есть утверждение, что '==' оказалась более узкая, чем антисимметрия, но чем обосновано это утверждение, не написано.
Это будет лучше, но даже это не может исправить ситуацию. ИМХО, введение операторов (встроенных, не внешних компараторов как в c++) для несравнимых объектов — большая ошибка проектирования. И ее надо выкорчевывать, в том числе, избавляясь от этого оператора в методе Class.
Меня, как программиста, не желающего заморачиваться — иначе я сидел бы не на руби — строгим, математически корректным прописыванием всех возможных веток кода для сравнения (коих оказывается больше трех), сильно смущает, что !(a<b || a>b) не эквивалентно a==b. В ЖЖшном треде ezz666 расписал это сильно более обстоятельно, опираясь на честное определение отношения порядка.
> сильно смущает, что !(a<b || a>b) не эквивалентно a==b
Это не должно смущать. В ЖЖ, насколько я вижу, у вас произошла путаница из-за того, что вы захотели qsort, и для этого потребовали strict weak ordering. И, вероятно, смешали strict weak ordering, strict partial ordering и weak partial ordering.

Но вы предъявили избыточное требование к тому, что это требование и не должно выполнять. В partially ordered set равенство определяется антисимметрией, то есть (a <= b && b <= a) -> a == b. И при таком условии и топологическая сортировка работает, и выполняются все операции сравнения, там где есть отношение порядка. qsort и не должен тут работать.
partially ordered set является нормальной строгой математикой и не должно никого смущать. Классы являются отличным образцом и выкорчевав оператор <=> вы без всякой пользы убъете возможность строить граф наследования классов.

У вас есть обоснование того, что это большая ошибка проектирования?
Нет, подождите. Я не хочу qsort, это лишь частный пример.
Я хочу чтобы встроенные операторы был годны для всего спектра применений (и qsort — лишь один из случаев), абсолютно консистенты и согласовывались сo здравым смыслом. Для этого оператор должен быть определен лишь на полностью упорядоченных множествах. Изыски вроде poset-ов — не типовая задача, не нужно из-за них захламлять ненужными сложностями типовое использование операторов сравнения.
Ради сортировки не-линейно упорядоченных множеств можно сделать специальный метод, в описании которого прописать четкие требования к оператору сравнения и описать правила работы с несравнимыми элементами.

Обоснование, почему это ошибка проектирования? Легко!
Если вы хотите сделать граф наследования, вполне можно определить методы `#subclass?`, `#superclass?`, `#unrelated?`, а не усложнять функциональность оператора `<=>`. Ни малейших проблем.
Но разработчики руби решили обобщить оператор <=> на этот случай, и теперь вам приходится всякий раз рассматривать отдельную ветку a<=>b == nil. Даже если вы предполагаете, что все ваши элементы сравнимы. Но вы работаете в динамически-типизированном языке и семантика оператора <=> по факту допускает эту (лишнюю для большей части применений) ветку. А значит, вы должны ее обработать и либо заморочиться работой с poset-ами, либо, по-меньшей мере, бросить исключение.

О том, как такие обобщения влияют на код, посмотрите статью про сравнение Float-ов. С ними проблема следующая: у них есть значения +-Inf и NAN. Из-за этого написать корректный компаратор становится весьма сложной задачей. Но работа с бесконечностями и неопределенностями, может, и заслуживает такого обобщения, усложняющего код (и то не факт, что подход был выбран правильно). А вот сравнение классов/хэшей оператором вместо метода — точно не заслуживает.
Я думаю, что вы видите проблему на ровном месте, там, где ее нет. strict weak order является подклассом partial order. Вы работаете с более узким классом strict weak order и из-за этого требуете, чтобы оператор не работал для более широкого класса. А, например, у меня последние пол-года половина задач связана с зависимостями и топологической сортировкой, и для меня это рутина, а не изыски.

Вам с qsort наличие <=> у хэша ничем не помешает. Он выдает на nil такое же исключение, как и на сравнение числа со строкой. Если вы сейчас не проверяете на nil, то у вас будут вылезать ошибки и при смешивании типов в одном списке:

 > 'a' <=> 2
 => nil


Если же вы полагаетесь на то, что у вас в массиве ничего кроме чисел не будет, то каким образом вам помешает <=> у хэша, ведь у вас в массиве ничего кроме чисел нет?

Возможно, для того, чтобы сделать более удобным и строгим для вас язык, стоило бы наоборот, ввести что-то типа <===>, который бы выдавал исключение вместо nil.
Да, аргумент про то, что в массиве могут быть объекты разных типов справедлив. Убедили, что хуже не станет. Спасибо!
Приложения работают под Nginx + Apache + mod_passenger
А зачем тут апач?
Для удобства и упрощения конфигурации.
Чем настройка апача проще настройки нгинкса? По-моему, только лишнее звено, вносящее дополнительные задержки, учитывая, что пассажир и с nginx неплохо интегрируется.
В первую очередь это удобнее клиентам. Сайт с привязкой к кластеру, DNS и CDN создается через веб-панель в один клик и не нужно по зарываться в документацию и конфигурировать отдельности все эти компоненты. Apache также нужен для настройки сложной структуры через .htaccess, организации хранения документов через WebDAV и для обслуживания других платформ, например, под mod_php — phpmyadmin и awstat. В связке Redmine + LDAP + SVN без Apache не обойтись. Поэтому для простоты и для минимизации расхода ресурсов по дефолту используется apache.

Если клиенту ничего этого не нужно, он отключает их конфигурации apache и запускает так, как ему нужно и passenger, и unicorn, и puma. По скорости будет все то же самое, что и с apache, но чуть меньше будет расход памяти.

Плагином включать passenger в nginx в нашем случае нельзя, так как мы изолируем клиентов друг о друга и по правам доступа (безопасность) и по ресурсам (нагрузка соседей).
Насчёт прав доступа — пассажир в nginx по дефолту запускает воркеры под тем юзером, которому принадлежит config.ru. Вот насчёт ресурсов хз, не интересовался.
Просто под юзером — это малая часть задачи. Чтобы все было хорошо, нужно еще для каждого пользователя правильно выставлять контекст выполнения — доступные файловые системы, переменные окружения, лимиты памяти/стека/процессора, приоритеты выполнения.
Еще немаловажная причина не складывать разных пользователей в nginx с интегрированным passenger — разным пользователям в эксплуатации могут требоваться разные версии passenger. Одному нужна более новая версия с исправленным багом, с которым он сталкивается, другому наоборот, никакие лишние изменения не нужны.

Поэтому лучше как в нашем случае — passenger от nginx отдельно.
> image = user && user.profile && user.profile.thumbnails && user.profile.thumbnails.large

Это они так изобретают Maybe?
Это стандартная замена Maybe для большинства динамически типизированных языков.
То есть в рубях эта «стандартная замена» появилась только к версии 2.3?
До этого была видимо не очень стандартная…
Нет, в версии 2.3 появился оператор &.

А этот способ был всегда.
Ну видимо через несколько релизов они еще и >>= сделают. Будет очередной невыразительный захардкоженый оператор типа $%.
Еще появилась непонятная возможность обходить Enumerable при помощи хеша, превращенного в прок
hash = {a: 1, b: 2, c: 3}
arr = [:a, :b, :d]
arr.map(&hash); # => [1, 2, nil]

и еще Numeric#positive? Numeric#negative?
Уоу. А можете кинуть ссылку на обход Enumerable? Интересно, для чего это исходно задумывалось.
Есть подозрение, что это аналог метода extract!(*keys) из Rails.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий