У меня есть структура Vector:

struct Vector{
var X : Measurement<Dimension>
var Y : Measurement<Dimension>
var Z : Measurement<Dimension>
...

И я создаю новый объект, например:

let test = Measurement(value: 1.5, unit: UnitLength.inches) as Measurement<Dimension>
var lVector = Vector(x: Measurement(value: 10, unit: UnitLength.meters), y: test, z: Measurement(value: 10, unit: UnitLength.meters))

Все работает нормально. Но если попытаться использовать переменную из другого класса, я получил ошибку: «Невозможно преобразовать значение типа «Измерение» в тип «Измерение» при принуждении»

final class SettingsManager{
  ...
 var test  = Measurement(value: 1.5, unit: UnitLength.inches)
  ...  }
class Calculator {
 ...
 let test = SettingsManager.shared.test as Measurement<Dimension>
 var lVector = Vector(x: Measurement(value: 10, unit: UnitLength.meters), y: test, z: Measurement(value: 10, unit: UnitLength.meters))

Я пробовал "как!" и получил

Cast from 'Measurement<UnitLength>' 
to unrelated type 'Measurement<Dimension>' 
always fails

Та же самая переменная, объявленная в этом классе, отлично работает, как я показал выше. Что я сделал не так?

1
Dima 2 Фев 2022 в 15:14
Верьте компилятору. Дженерики не являются ковариантными. Нет общих супертипов и подтипов. Эти два типа действительно не связаны.
 – 
matt
2 Фев 2022 в 15:36
Все это прекрасно и правильно, но не объясняет, почему let test = Measurement(value: 1.5, unit: UnitLength.inches) as Measurement<Dimension> работает. Думаю, я бы расценил это как ошибку в языке.
 – 
JeremyP
2 Фев 2022 в 15:47
Это комментарий, а не ответ. Ответ Sweeper - это ответ.
 – 
matt
2 Фев 2022 в 15:49
Во всяком случае, это объясняет это просто отлично. Measurement<UnitLength> не является Measurement<Dimension>, но UnitLength, безусловно, является измерением. Ты видишь?
 – 
matt
2 Фев 2022 в 15:52
Я точно понимаю, почему это не работает, когда оно не работает, но те же рассуждения должны применяться, когда вы создаете объект и немедленно пытаетесь преобразовать его в то же самое выражение. Существует дополнительное скрытое правило, связанное с выводом типов, которое немного удивляет, когда вы впервые сталкиваетесь с ним.
 – 
JeremyP
2 Фев 2022 в 16:04

3 ответа

Measurement<UnitLength> не является разновидностью Measurement<Dimension>. Это несвязанные типы. Работает только здесь:

let test = Measurement(value: 1.5, unit: UnitLength.inches) as Measurement<Dimension>
var lVector = Vector(x: Measurement(value: 10, unit: UnitLength.meters), y: test, z: Measurement(value: 10, unit: UnitLength.meters))

Потому что вызовы Measurement(...) здесь фактически создают Measurement<Dimension>, поскольку это то, что ожидается на месте вызова. as Measurement<Dimension> сообщает, что вам нужен Measurement<Dimension>, а тип параметра x и z также Measurement<Dimension>. Алгоритм определения типа достаточно умен, чтобы понять, что вы также должны иметь в виду Measurement<Dimension>(...).

Measurement<Dimension>.init принимает Dimension в качестве второго параметра, а UnitLength является его подтипом, поэтому здесь нет проблем.


С другой стороны, в SettingsManager вы объявили test следующим образом:

var test = Measurement(value: 1.5, unit: UnitLength.inches)

Нигде здесь вы не упомянули Measurement<Dimension>, поэтому алгоритм вывода типа просто использует второй параметр, чтобы сделать вывод, что вы должны иметь в виду Measurement<UnitLength>, и поэтому тип test равен Measurement<UnitLength>.

Если вы просто добавите as Measurement<Dimension>

var test = Measurement(value: 1.5, unit: UnitLength.inches) as Measurement<Dimension>

Он должен работать.

Однако не будет ли разумнее, если все три компонента вектора будут иметь один и тот же тип единиц измерения?

struct Vector<T: Dimension>{
    var X : Measurement<T>
    var Y : Measurement<T>
    var Z : Measurement<T>
}
3
Sweeper 2 Фев 2022 в 15:36

Это просто чтобы положить конец вопросу о том, почему это законно:

let test = Measurement(value: 1.5, unit: UnitLength.inches) as Measurement<Dimension>
var lVector = Vector(x: Measurement(value: 10, unit: UnitLength.meters), y: test, z: Measurement(value: 10, unit: UnitLength.meters))

Это даже не должно быть вопросом; это просто нормальный принцип замены. Возьмем более простой способ выразить это. Что, если вы не бросаете?

let test = Measurement(value: 1.5, unit: UnitLength.inches)

Тогда test выводится как Measurement<UnitLength>. Теперь бросим:

let test: Measurement<Dimension> = Measurement(value: 1.5, unit: UnitLength.inches)

Это законно. Почему? Это нет, потому что Measurement<UnitLength> является подтипом Measurement<Dimension>. Это не так! Это потому, что UnitLength является подтипом Dimension. Это как если бы вы сказали

let test = Measurement(value: 1.5, unit: UnitLength.inches as Dimension)

Ты видишь? На самом деле вы не преобразовали Measurement<UnitLength> в Measurement<Dimension>; это невозможно. Вы разыгрываете UnitLength до Dimension, что вполне возможно. Это ничем не отличается от того, если бы вы сказали

let test2: Dimension = UnitLength.inches

Вы всегда можете заменить подтип там, где ожидается супертип. Сложность исходного вопроса заключается в том, что Measurement<UnitLength> не является подтипом Measurement<Dimension> (поскольку дженерики Swift не являются автоматически ковариантными по отношению к их параметризованному типу).

1
matt 2 Фев 2022 в 17:51

Использовать

var test : Measurement<Dimension>  = Measurement(value: 1.5, unit: UnitLength.inches)
0
matt 2 Фев 2022 в 15:33