Есть ли у Swift какой-либо механизм для неявного преобразования типов, когда он гарантированно работает без потерь? - PullRequest
1 голос
/ 17 июня 2020

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

А пока рассмотрим перечисление DistanceUnit ниже. Это было создано мной, чтобы пользователи моего API для ARKit, основанного на счетчиках, могли указывать значения в любых единицах, которые они выбирают. Если они хотят, чтобы что-то было перемещено на два дюйма в их расширенной сцене, им не нужно выполнять вычисления вручную. Они просто используют moveBy(x: .inches(2)), и перечисление автоматически преобразует его через открытое свойство meters и передает это API Apple, которые их ожидают.

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

enum DistanceUnit {

    case meters(SCNFloat)
    case centimeters(SCNFloat)
    case millimeters(SCNFloat)
    case feet(SCNFloat)
    case inches(SCNFloat)
    case points(SCNFloat)

    var meters:SCNFloat {

        switch self {
            case let .meters(meters)  : return meters
            case let .centimeters(cm) : return cm     * 0.01
            case let .millimeters(mm) : return mm     * 0.001
            case let .feet(feet)      : return feet   * 0.3048
            case let .inches(inches)  : return inches * 0.0254
            case let .points(points)  : return (points / 72) * Self.inch.meters
        }
    }

    // Presets
    static let meter      : DistanceUnit = .meters(1)
    static let centimeter : DistanceUnit = .centimeters(1)
    static let millimeter : DistanceUnit = .millimeters(1)
    static let foot       : DistanceUnit = .feet(1)
    static let inch       : DistanceUnit = .inches(1)
    static let point      : DistanceUnit = .points(1)
}

Примечание: поскольку типы 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 какие-либо такие функции / возможности, когда вы знать тип полностью совместим с другим типом, будь то однонаправленный или двунаправленный, как здесь?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...