Как стать автором
Обновить

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

10 Never'ов из 10!

То, что опционал это View – очевидный костыль. В ResultBuilder’ах же есть встроенный способ обработки условий buildIf…


Вот вам и «начать все с чистого листа»…

Очевидно, что это сделано для их собственного ViewBuilder'а, но он тоже резалт билдер. Хрен знает, что у них там не влезло и куда.

Но ведь ещё и каст к Optional<Never> сделан такой зачем-то.

Сведение кOptional<Never> не является особым поведением для View.
Optional<Never> может принимать только значение nil. Соответственно, .none любого типа есть Optional<Never>, а .some любого типа не Optional<Never>.

Я не говорила, что это особое поведение. Я недоумеваю, что могло привести к тому, что они объявили опционал вьюхой. Это раз.

Два - .some у опционала тоже может быть ещё одним опционалом и так далее. Но главный вопрос в том, что это дженерик тип. И что опционалы нельзя просто так друг к другу кастовать. Потому что даже .none несёт информацию, чей же конкретно он .none. Например .none у Optional<Int> не то же самое, что у Optional<String>. На этапе компиляции, конечно же. Если в рантайме, то, видимо, поэтому и возможен успешный каст любого нила к нилу от невер.

Да, правила различны во время компиляции и выполнения.
Во время выполнения забывается тип дженерика, если он не важен в контексте значения энума.
Но это не такой type erasure как в Java, потому что если тип таки значим, так не выйдет.

А в чём проблема с тем что Optional это View, если содержимое тоже View? Это обычное прокидывание свойств вверх по враперам, и улучшает полиморфизм.

Never тип - это особый тип: https://en.wikipedia.org/wiki/Bottom_type

В такие типы позволяется кастить что угодно, ибо они не населены (нельзя создать значение этого типа, соответственно, эта ветвь кода никогда не будет вызвана в рантайме).

Дальше, Optional<Never> - это тип имеет всего одно значение none (ах да, в Swift это то же что и nil).

Соответственно, через Never? суть выражается протокол (или неявный родительский тип), требующий быть nil.

nil, some(nil), и так далее, просто удовлетворяют этому протоколу.

Можно сказать что это хитрый и законный способ заабьюзить систему типов (автоприведение к боттому) для определения IsNil протокола. Это не особенность реализации компилятора, и рантайм тут совершенно ни при чём.

А в чём проблема с тем что Optional это View, если содержимое тоже View? Это обычное прокидывание свойств вверх по враперам, и улучшает полиморфизм.

Проблема в том, что это просто явный костыль. Кроме того, у вью должно быть быть бади, из-за чего Never тоже явно объявлен View. Но главное, конечно - зачем? Если я на уровне дженериков хочу явно различить опционал и другой любой тип, то данное решение меня лишает такой возможности. Субъективно, я не вижу улучшения полиморфизма так как вообще не понимаю конечной цели. Почему тогда опционал это не инт? Или не AnyObject?

В такие типы позволяется кастить что угодно, ибо они не населены (нельзя
создать значение этого типа, соответственно, эта ветвь кода никогда не
будет вызвана в рантайме).

Да, именно так. Он не имеет значений. Но кастить к нему нельзя ничего.

1 as? Never // == nil: warning
// Cast from 'Int' to unrelated type 'Never' always fails

Дальше, Optional<Never> - это тип имеет всего одно значение none (ах да, в Swift это то же что и nil).

Это спекуляция. У опционала два значения. Другое дело, что в этом случае одно из них невозможно создать и его никогда не будет в рантайме. Покажите, пожалуйста документацию этого факта, что все нил - это опционал невера. Не надо ехидничать, пожалуйста, я искренне не понимаю работы этого и просила скинуть в меня ссылку в статье.

из-за чего Never тоже явно объявлен View

Это нормально. Как я уже писал - боттом может быть чем угодно, и реализовывать что угодно, ведь его значение всё равно никогда нельзя создать. На то он и боттом, это соответствует его дизайну.

По факту, это должно автоматически работать, но видимо ещё не сделали:

https://github.com/apple/swift-evolution/blob/master/proposals/0102-noreturn-bottom-type.md#never-as-a-universal-bottom-subtype

Кроме того, у вью должно быть быть бади

А вот это уже не норм. Я как-то упустил из виду что там:

public typealias Body = Never

В таком виде оно действительно бесполезно. Я ожидал бы там увидеть что-то типа Wrapped.Body (не силён в синтаксисе Swift'а).

Почему влепили именно Never - сложно понять мотивацию. Уже ли не в void* полиморфизм они пытаются? >_<

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

У опционала два значения. Другое дело, что в этом случае одно из них невозможно создать и его никогда не будет в рантайме.

Optional - не тип, а конструктор типа, и у него значений быть не может. Optional<Int> - это уже тип, и у него 2 значения. Optional<Never> - это тоже тип, и у него всего одно значение.

Покажите, пожалуйста документацию этого факта, что все нил - это опционал невера.

Там действительно дело не в Neverах как я ожидал, просто семантика is довольно хитрая для Optionalов:

https://github.com/apple/swift/blob/main/docs/DynamicCasting.md#optionals

Nil Casting: if T and U are any two types, then Optional<T>.none is Optional<U> == true

Удачи в поисках "Motivation" раздела RFC для таких подводных камней =)

Это спекуляция.

Не надо ехидничать, пожалуйста

Цели издеваться над вами у меня нет. Я вообще мимокрокодил и Swift не знаю, но с Neverами бодался много в других языках. Уповая на то, что разработчики Swift'а более менее следуют "общим практикам", лишь подкидываю идеи чем бы этого могло быть. Конечно же всё это спекуляции. Я и не претендовал на истину.

Удачи в поисках "Motivation" раздела RFC для таких подводных камней =)

Вроде они и сами недовольны таким решением:

https://github.com/apple/swift-evolution/blob/master/proposals/0083-remove-bridging-from-dynamic-casts.md#removing-special-case-optional-and-container-handling-from-dynamic-casts

Optional - не тип, а конструктор типа, и у него значений быть не может. Optional<Int> - это уже тип, и у него 2 значения. Optional<Never> - это тоже тип, и у него всего одно значение.

В свифте - это тип. Енам. Дженерик. С двумя значениями. ¯\_(ツ)_/¯

Нет, это не тип.

Если Вы имеете в виду Optional<T> - это да, тип, но этот тип конструируется параметризацией Optional дженериком T (тайп параметром).

Сам по себе Optional, без подстановки туда чего-угодно, не может быть типом, соответственно, нельзя его использовать в сигнатурах типов не указав параметр.

Аналогично и с Array. Пока не заполнены все "дырки", неважно чем, конкретными типами или тайп параметрами - у нас нет типа, есть только конструктор типа.

Внимательно перечитала первый абзац коментария. Проблемы в существовании опционалов каких либо типов - нет. Проблема в том, что опционалу дается дополнительное поведение. Зачем?

Ну то есть я и так могу сделать Optional<T2> (изначально решение как раз на это и полагалось), но зачем делать так, чтобы Optional сам был вью и мог стать этим самым T2 - я не понимю.

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

но зачем делать так, чтобы Optional сам был вью и мог стать этим самым T2 - я не понимю.

К примеру, если брать широкими мазками, то если логикой какой-то generic third-party View не предусмотрено Optional поле, а вы хотите отобразить в ней ток 2 из 3 передаваемых снаружи вьюх, то третью вы можете передать как nil, и получить желаемое поведение, не переписывая компонент и не матеря других разрабов. E - extensibility.

То, что возможно подобная задумка (опять спекуляция ^_^), плохо легла конкретно на ваш кейс - вопрос отдельный. И вообще, так ли оно работает на самом деле в Swift?

assert(UltraView(title: Text("чук рулит")) is UltraView1<Text, EmptyView>)
assert(Helper.ultraView(title: "чук рулит") is UltraView1<Text, Optional<Text>>)

И соответственно

var description: Optional<Text>? = nil
assert(description == nil)

description = Optional.some(nil)
assert(description != nil)



Вывод: методHelper.ultraViewисспользует дженерики правильно, но ожидания - не правильные

Это почему же неправильные ожидания? :-)

Это шутка такая? Раздули вы ребята из мухи слона конечно. Я может заблуждаюсь (нет мака под рукой для проверки), но пока я вижу вместо проблемы, очень глупую ошибку и необходимость добавить в инициализатор опционал (в самый первый пример кода). Полагаю это решит проблему.

Вот сюда добавить.

...///код

// базовый конструктор
init(title: T1, description: T2?) {
	self.title = title
	self.description = description
}

...///код

Я обязательно проверю свое умозаключение, но если кто проверит быстрее, отпишите, будьте любезны.

P/s. Под глупой ошибкой я имею ввиду саму ошибку, а не то что автор глупый. Примерно как символ пропустить и сидеть искать.

Проверил. Поведение соответствует ожиданию (с исправленным инициализатором).

Этот пример синтетический. Исходный был сложнее: либо обязательные несколько параметров инициализатора, либо вообще нет. То есть "опционально" - не про тип, а про само присутствие элементов (вью) или их отсутствие в принципе.

В любом случае, то, что Optional - это вью, неочевидно.

Так что проблема скорее не в том, чтобы передать опционал, а в том, чтобы его не передавать вообще.

Проверила. Поведение не соответствует ожиданию:

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

Так Вы в итоге, используя код

let v: Optional<Optional<Text>> = .some(nil)

UltraView(
	title: Text("чук рулит"),
	description: v)

передаете не nil, а nil завернутый в опционал (Optional(nil)), так что код работает именно так как задумано т.к. nil не равен Optional(nil).

Что Вас удивляет касательно Optional? Идем в документацию - ссылка

прямым текстом указано, что Optional соответствует View.

Я согласен, что сама возможность такой ситуации является проблемой, но это не проблема языка, а проблема проектирования. Решение очевидное, использовать другой подход в построении.

P/s

Я пытался понять, зачем использовать код из примера, но так и не придумал ситуацию, где может понадобиться подобная реализация.

Да, передаю опционал в опционале. Примерно так и работает свифтюай. Вас, я так понимаю, ни капельки не смутило, что опционал опционала влез? Хотя условие было, что это должно быть вью.

В этом же и есть суть проблемы.

Когда пишется сложная вью с условными ветвлениями и всякими модификаторами, то вот такие вложенные опционалы возникают как нечего делать.

Ссылка на документацию была лишней. Я это видела и знаю. И закономерный вопрос - а нахрена?

Примерно так и работает свифтюай

Спасибо, я достаточно осведомлен о том как работает SwiftUI.

Вас, я так понимаю, ни капельки не смутило, что опционал опционала влез? Хотя условие было, что это должно быть вью.

А чем меня, простите, это должно смутить? Какое такое условие? Вы определяете входящий тип и обязываете его соответствовать протоколу View. Optional ему соответствует. В документации об этом указано. И что же не так? Вполне ожидаемое поведение.

В этом же и есть суть проблемы.

Да какой проблемы? Серьезно. Я либо Вас не понял, либо просто что-либо еще не понимаю. Изначально (согласно публикации, которую мы сейчас комментируем), была проблема в том, что из-за Helper'а, появился якобы баг, хотя по факту, просто в инициализаторе забыли поставить «?».

Ну Вы сами логически подумайте, вот просто по порядку.

  1. Есть инициализатор, который принимает два неопциональных значения и инициализатор, который принимает одно значение, задавая второе самостоятельно.

  2. Есть вариант с прямой передачей и там все отрабатывает корректно т.к. в ручную передается один либо два представления т.е. используются оба инициализатора.

  3. Появляется Helper, который всегда использует первый инициализатор, просто передавая ему nil, в случаее отсутствия значения (опустим, что Helper иначе можно было построить или добавить еще один инициализатор, готовый к такому сюжету).

  4. Инициализатор построен на дженериках и принимает любой тип, который соответствуют View, следовательно пропускает опционал, при этом в инициализаторе не указано, что параметр опционального типа т.е. nil там точно не будет.

  5. В представлении идет проверка на nil

Беря во внимание вышеизложенное, мы получаем опциональное свойство в представлении, проверку на эту опциональность (отображение, цвет) и инициализатор, который никогда не присвоит nil свойству. Ну очевидно же. Просто правим инициализатор.

С этим вроде разобрались. Идем дальше. Вы негодуете из-за того, что .some(nil) не проходит проверку на nil (опустим, что Optional это перечисление, где в some должен быть тип, а под опционал предусмотренно .none, и пихать в .some nil, уже неправильно). Я Вам пишу, что .some(nil) не соответствует nil т.к. это вообще разные вещи и nil не равно Optional(nil). Что же не так? Я правда силюсь понять, что конкретно в этой ситуации идет не по плану. Смею предположить, что негодование связано с тем, что нельзя проверить на nil Optional(nil).

Так можно, кто сказал что нельзя. В интеренете решения нет? Ну так это ни о чем и не говорит, от Apple прямых упоминаний об этом нет, а там далеко не дураки сидят, значит решение есть. Новый вопрос - это решит «проблему»? Ну давайте тогда подумаем логически, как можно это организовать.

App, принимает в body протокол View (о структуре не слова, именно протокол), какой протокол поддерживают все классы? AnyObject. Причем тут классы и протокол AnyObject? Мы не работаем со структурами во SwiftUI, а работаем с протоколом View, а протоколу View могут соответствовать как структуры так и классы, следовательно возможно приведение к AnyObject. Логично? Что даст на выходе null при преобразовании в AnyObject? nil, Optional(nil) и т.д. Как мы можем работать с null в Swift? С помощью NSNull. Вот Вам и готовое решение.

extension Optional where Wrapped: View {
    var isNil: Bool {
        if ((self as AnyObject) as? NSNull) == nil {
            return false
        } else {
            return true
        }
    }
}

...//код

// базовый конструктор
init(title: T1, description: T2?) {
	self.title = title
	self.description = description.isNil ? nil : description
}

...//код

Я проверил, работает ожидаемо

К слову, я не утверждаю, что это какое-то там хорошее решение, просто первое, что мне пришло в голову и склоняюсь к тому, что просто проектировать надо иначе. Говорю не с пустого места т.к. написал не одну сотню представлений и нигде таких проблем у меня не возникало, опять же это не означает, что я вот делаю правильно, а все остальные неправильно. Просто есть документация Apple, есть правила, примеры и возможности. Надо брать все это во внимание.

И как я уже писал ранее, проблемы все же нет, все вот это, притянуто за уши.

Зарегистрируйтесь на Хабре , чтобы оставить комментарий

Публикации

Истории