Упрощение быстрого перечисления - PullRequest
1 голос
/ 12 апреля 2020

Поэкспериментировав с карри в Swift, я придумал код ниже. Я хочу посмотреть, возможно ли упростить это перечисление Operate. В настоящее время мне нужно выполнить инициализацию следующим образом:

let multiply = Operate.Multiply.op

Я бы предпочел, чтобы каждый случай имел ассоциированное значение, которое напрямую возвращает закрытие без необходимости выполнять этот хакерский блок switch , Возможно ли это?

Вот код, который вы можете запустить на игровой площадке Swift:

import Foundation

enum Operate {
    case Plus
    case Minus
    case Multiply
    case unsafeDivide

    var op: (Double) -> (Double) -> Double {
        get {
            switch self {
            case .Plus:
                return { n in
                    return { n + $0}
                }
            case .Minus:
                return { n in
                    return { n - $0}
                }
            case .Multiply:
                return { n in
                    return { n * $0}
                }
            case .unsafeDivide:
                return { n in
                    return { n / $0 }
                }
            }
        }
    }
}

let multiply = Operate.Multiply.op
let plus = Operate.Plus.op
let unsafeDivide = Operate.unsafeDivide.op
// 3 + (16 * 2) -> 35
plus(3)(multiply(16)(2))

Бонус: как я могу обрабатывать ошибки с unsafeDivide «быстро», что есть, предотвратите это:

let unsafeDivide = Operate.unsafeDivide.op
unsafeDivide(2)(0)

1 Ответ

2 голосов
/ 12 апреля 2020

То, что вы, похоже, делаете, это карри . Вы удаляете много дублированного кода, извлекая функцию curry:

func curry<A,B,C>(_ f: @escaping (A, B) -> C) -> (A) -> (B) -> C {
    return { a in { b in f(a, b) } }
}

// ...

var op: (Double) -> (Double) -> Double {
    switch self {
    case .plus: // please follow Swift naming conventions, enum cases start with a lowercase
        return curry(+)
    case .minus:
        return curry(-)
    case .multiply:
        return curry(*)
    case .unsafeDivide:
        return curry(/)
    }
}

Это уже выглядит намного лучше. Похоже, вам не нравятся операторы switch, поэтому вот как вы это сделаете со словарем:

var op: (Double) -> (Double) -> Double {
    let dict: [Operate: (Double, Double) -> Double] =
        [.plus: (+), .minus: (-), .multiply: (*), .unsafeDivide: (/)]
    return curry(dict[self]!)
}

Фактически, вы можете использовать новую функцию callAsFunction в Swift 5.2 опустить даже слово op на стороне вызывающего абонента:

func callAsFunction(_ a: Double) -> (Double) -> Double {
    op(a)
}

Это позволяет вам:

Operator.multiply(2)(3)

Использование связанных значений равно по-другому:

enum Operate {
    case plus(Double)
    case minus(Double)
    case multiply(Double)
    case unsafeDivide(Double)

    func callAsFunction(_ b: Double) -> Double {
        switch self {
        case .plus(let a):
            return a + b
        case .minus(let a):
            return a - b
        case .multiply(let a):
            return a * b
        case .unsafeDivide(let a):
            return a / b
        }
    }
}

Но мне лично это не нравится, поскольку наличие связанных значений означает, что вы не можете просто использовать == для сравнения значений перечисления, среди других ограничений.


Запрет деления на 0 во время компиляции невозможен, поскольку передаваемые вами значения могут не быть константами времени компиляции. Если вы просто хотите проверить постоянные времени компиляции, вам может понадобиться статический анализатор кода c, такой как SwiftLint. Во время выполнения деление Double 0 в любом случае четко определено стандартом IEEE. Это не будет sh или что-нибудь еще.

...