Я пытаюсь выяснить, может ли Swift, например C# и другие языки, поддерживать неявные преобразования между типами, когда известно, что эти типы полностью совместимы и без потерь. Протоколы ExpressibleByXxxLiteral
кажутся близкими, но я не думаю, что они на самом деле то, что мне нужно. Подробнее об этом в конце.
Короче говоря, считайте это удобным для пользователя прославленным типалиасом. для счетчиков в SceneKit.
Примечание: поскольку типы SceneKit, такие как SCNVector3
, основаны на разных типах в зависимости от платформы, я создал SCNFloat
typealias, который стандартизирует мой API, поэтому он просто работает независимо от платформы, и мне не нужно проводить эти проверки компилятора во всем моем коде, поскольку это происходит один раз в определении typealias. для пользователя все проще, особенно при добавлении таких вещей, как значения по умолчанию к их собственным функциям, я также реализую ExpressibleByIntegerLiteral
и ExpressibleByFloatLiteral
, вот так ...
extension DistanceUnit : ExpressibleByIntegerLiteral {
init(integerLiteral value: Int) {
self = .meters(SCNFloat(value))
}
}
extension DistanceUnit : ExpressibleByFloatLiteral {
init(floatLiteral value: Float) {
self = .meters(SCNFloat(value))
}
}
Вернемся к проблеме ...
Ненавижу, что внутри моего API я всегда должен делать value.meters
при передаче DistanceUnit
. Я уже знаю, что это перечисление - это прославленный типалиас для метров, выраженный через SCNFloat
, так почему мне всегда нужно вводить его явно?
Я пытаюсь выяснить, возможно ли как-то украсить этот тип, чтобы его можно было передать напрямую в любой API, который ожидает SCNFloat
, как вы можете в таких языках, как C#.
The C# Way
Теперь в C# , это легко сделать с помощью операторов неявного преобразования. Вот пример неявного преобразования между SCNFloat
и DistanceUnit
(технически я добавляю здесь небольшой псевдокод Swift, чтобы проиллюстрировать, что C# не имеет перечислений со связанными значениями, но вы поняли) ...
public static implicit operator DistanceUnit(SCNFloat value) => .meters(value);
public static implicit operator SCNFloat(DistanceUnit units) => units.meters;
Первая строка выше говорит: «Любой API, который принимает DistanceUnit
, теперь может принимать SCNFloat
напрямую. При передаче SCNFloat
компилятор автоматически вставляет это значение в случай перечисления .meters(x)
, затем это значение перечисления в конечном итоге передается получателю, ожидающему DistanceUnit
. '
И наоборот, второе В строке говорится: «Любой API, который принимает SCNFloat
, теперь может принимать DistanceUnit
напрямую. При передаче DistanceUnit
компилятор автоматически извлечет свойство .meters
, которое уже является типом SCNFloat
, и просто передаст это значение получателю, ожидающему SCNFloat
. '
Simple , предсказуемо и полностью без потерь.
Примечание. Выше я определил преобразования в обоих направлениях - от SCNFloat
до DistanceUnit
и наоборот. Однако вам не нужно указывать оба, если это не имеет смысла.
Например, то, что ожидает Float
, может легко принять преобразованный Int
, потому что такие преобразования будут без потерь, но что-то, ожидающее Int
, не обязательно может принимать Float
без возможной потери некоторых данных.
В этих случаях вы можете либо определить его как односторонний, либо вы все равно можете определить их как двусторонний , но вы бы отметили направление с потерями explicit
вместо implicit
. Это заставляет пользователя явно приводить один тип к другому, предупреждая их, что это потенциально операция с потерями, но оставляя им самим сделать этот вызов. Вы никогда не используете implicit
, если данные не могут быть гарантированы на 100% без потерь.
Снова в Свифтвилле ...
Протоколы ExpressibleByXxxLiteral
на первый взгляд кажутся , как будто они полдела, но на самом деле я думаю, что это отвлекающий маневр, потому что он фактически не выполняет преобразование во время выполнения. Подсказка находится в названии ... 'literal', что означает что-то буквально набранное в коде, а не с переменными Xxx
. Игровые площадки, похоже, также поддерживают эту теорию. Это наводит меня на мысль, что, хотя это удобно для нескольких простых типизированных литералов, на самом деле это не решение того, что мне нужно.
Так есть ли у Swift какие-либо такие функции / возможности, когда вы знать тип полностью совместим с другим типом, будь то однонаправленный или двунаправленный, как здесь?