Вернуть общий тип в его дискретный тип

Я выполняю кучу вычислений, используя Decimal и Double. Без дженериков было бы много повторяющегося кода. Есть места, где мне в конечном итоге понадобится десятичная дробь в более родном формате, но я не могу понять, как это сделать в Swift.

Гипотетический пример: в Decimal нет log2(), но из-за приемлемой погрешности я хочу преобразовать его в Double и использовать таким образом.

Первоначально я думал, что просто создам общую функцию, которая будет работать как для Double, так и для Decimal.

// Dtype is either Decimal or Double
typealias Dtype =  SignedNumeric

func mylog2<T: Dtype>(_ x: T) -> Double
{
    let x_double: Double = Double(truncating: x as NSNumber) //'T' is not convertible to 'NSNumber'
    return log2(x_double)
}

Но я получаю сообщение об ошибке, поэтому решил просто разделить их на отдельные функции. Подумав об этом, я пришел к выводу, что T не может раскрыть свой настоящий Тип. Затем я решил разделить функцию на отдельные типы и попытаться использовать ее таким образом:

func mylog2(_ x: Decimal) -> Double
{
    let x_double: Double = Double(truncating: x as NSNumber)
    return log2(x_double)
}

func mylog2(_ x: Double) -> Double
{
    return log2(x)
}

Но тогда я не знаю, как заставить T использовать функцию mylog2() дискретными типами:

func myExample<T: Dtype>(_ a: T, _ b: T) -> Double
{
    let c: T = (a + b) * (b - a)
    //bunch of other maths funcs
    return mylog2(c) //No exact matches in call to global function 'mylog2'
}

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

Вы действительно хотите вернуть Double из myLog2? Это, вероятно, вызовет настоящую проблему. Я думаю, вам захочется вернуть Self, чтобы Decimal оставался Decimal (даже если вы выполняете вычисления в Double). Тем не менее, я бы очень хорошо подумал о поддержке как десятичной, так и двоичной плавающей запятой в одном и том же коде. Обычно они решают очень разные проблемы, поэтому я всегда с подозрением отношусь, когда люди пытаются их слишком сильно объединить; какова конечная цель? Swift плохо поддерживает Decimal (это пережиток ObjC, а не основной тип Swift).

Rob Napier 07.07.2024 15:56

Я знаю, что это всего лишь пример, но когда вы обнаруживаете, что берете двоичный журнал десятичной дроби, создается ощущение, что вы взяли не те инструменты. Есть много способов сделать то, что вы описываете, в Swift, но все они требуют большого количества кода. Вы просто перемещаете код, потому что в конечном итоге он должен существовать. Большинство языков, которые поддерживают что-то вроде myExample, делают это из-за сбоя во время выполнения, если вы передаете что-то неожиданное (что, если я передаю Float, а у вас нет mylog2 для этого?). Swift предпочитает ошибки компилятора сбоям во время выполнения.

Rob Napier 07.07.2024 15:59

С точки зрения математики, большинство языков включают неявное приведение. C автоматически расширяет различные типы, чтобы математика «работала». Это может привести к тонким ошибкам, поэтому Swift намеренно решил не делать этого. 1 + 1.0 недействителен в Swift по своей природе. Другие языки, такие как JavaScript, предпочитают просто конвертировать все в Double. Это также приводит к появлению странных ошибок для больших чисел и оказывает серьезное влияние на производительность. Swift выбрал способ, который сложнее кодировать, но который легче обеспечить правильность и производительность. Это был компромисс. Свифт имеет тенденцию быть педантичным в отношении вещей; это хорошо и плохо в разное время.

Rob Napier 07.07.2024 16:04

Привет @Rob Napier, я ценю вопрос! Да, я имею в виду перейти от Log2() -> Double. Причина этого в том, что в этот момент я превращаю числа в визуальное представление (т. е. цветовой код), и все эти конечные данные больше не нужны. Вероятно, мне следовало бы начать с более убедительного примера «func / () -> Self» (функция оператора разделения), который бы возвращал результат, как вы и ожидали, сохраняя непрерывный тип данных. С этим нужно обращаться так же, как с моим myLog2(), потому что не существует общего протокола, который сочетал бы оператор деления с Decimal и Double.

nilgirian 07.07.2024 17:27
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
4
63
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Просто объявите свой собственный протокол, который требует реализации метода log2. Соответствуйте Double и Decimal этому протоколу.

protocol Log2 {
    func log2() -> Double
}

extension Decimal: Log2 {
    func log2() -> Double {
        Darwin.log2(Double(truncating: self as NSNumber))
    }
}

extension Double: Log2 {
    func log2() -> Double {
        Darwin.log2(self)
    }
}

Затем вы можете ограничить параметр универсального типа значением Log2 & SignedNumeric.

typealias Dtype = SignedNumeric & Log2

func myExample<T: Dtype>(_ a: T, _ b: T) -> Double
{
    let c: T = (a + b) * (b - a)
    //bunch of other maths funcs
    return c.log2()
}

Альтернативно создайте Dtype собственный протокол, заменив Log2.

protocol Dtype: SignedNumeric {
    func log2() -> Double

    // other functions you need on both Double and Decimal goes here...
}

И, вероятно, стоит сделать Dtype собственный протокол, а не типалиас, поскольку я уверен, что их будет больше, чем этот.

Rob Napier 07.07.2024 16:01

Огромное спасибо @Sweeper. Я не знал, что такие вещи можно делать по протоколу. Это определенно открывает для меня новые двери. Идеальный!

nilgirian 07.07.2024 17:29

На ваш вопрос «как думать в Swift» это действительно сердце Swift. Все дело в определении протоколов, которые соответствуют потребностям ваших алгоритмов, написании общих алгоритмов на основе этих протоколов, а затем согласовании типов с этими протоколами, часто задним числом (т. е. не как часть определения), чтобы указать, какие типы могут участвовать. Это совсем другой подход, чем во многих языках, использующих наследование. Вам не нужно заранее решать, какие типы вы можете поддерживать. Вы просто решаете, какое поведение типов вам нужно, а затем соединяете их вместе. Желаю удачи в этом.

Rob Napier 07.07.2024 17:58

Другие вопросы по теме