Ограничения на связанный тип Swift

#swift

#swift

Вопрос:

Не позволяет ли Swift накладывать ограничения связанного типа на дженерики.

 typealias Reducer = (State, Action) -> State

class RootReducer {
    
    var reducers: [Reducer] = [Reducer]()
    
    func addReducer<T: Reducer>(_ reducer: T) {
        self.reducers.append(reducer)
    }
}
  

Я получаю следующее:

введите описание изображения здесь

Обновить:

Вместо использования имени в виде строки, как я могу передать тип и сохранить тип в качестве ключа к словарю.

 typealias Reducer = (State, Action) -> State

class RootReducer {
    
    var reducers: [String :Reducer] = [:]
    
    func addReducer(name: String, reducer: @escaping Reducer) {
        reducers[name] = reducer
    }
}
  

Комментарии:

1. Ну, это именно то, что указано в ошибке. Reducer Это не протокол и не класс. Вы не можете создать подкласс типа замыкания или функции.

2. Чего вы на самом деле пытаетесь достичь, используя дженерики?

3. Reducer — это псевдоним типа. Итак, в основном я говорю, что когда вы передаете reducer в качестве аргумента, он должен принадлежать редуктору typealias.

4. @johndoe вам не обязательно использовать для этого дженерики.

5. Что вы подразумеваете под использованием типа в качестве ключа? Какой тип? Очевидно, вы каким-то образом пытаетесь реализовать Redux в Swift. Обратите внимание, что в Redux редуктор не является типом, потому что каждый редуктор принимает разные параметры и дает разные результаты. Единственное, что вы знаете о редукторе, это то, что это функция с двумя параметрами.

Ответ №1:

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

В Swift нет динамических объектов такого же типа, как в Javascript, и вы не можете напрямую переписать из него функциональность. Например, в Swift нет ничего похожего на Object.values .

Ваш код, очевидно, вдохновлен Redux, поэтому давайте посмотрим, как все работает в Redux и как преобразовать его в Swift.

Давайте начнем с Action .

 protocol Action {
   var type: String { get }
}

struct ActionImpl<PayloadType>: Action {
   let type: String
   let payload: PayloadType
}
  

Это всего лишь простая идея, но она должна работать в большинстве случаев использования. Обратите внимание, что в Redux действие — это в основном объект, который может содержать что угодно. Мы могли бы использовать payload: Any в Swift, но это было бы некрасиво.

Теперь давайте определим состояние. Состояние не является словарем. Состояние является сложным иерархическим типом, например:

 struct AppState1: Equatable {
    var value1: Int = 0 // default value
    var value2: String?
}

struct AppState2: Equatable {
    var value3: Double?
}

struct RootState: Equatable {
    var appState1 = AppState1()
    var appState2 = AppState2()
}
  

Теперь, при таком состоянии, функции редуктора должны иметь следующие типы:

 (AppState1, Action) -> AppState1
(AppState2, Action) -> AppState2
  

и корневой редуктор:

 (RootState, Action) -> State
  

Как вы можете видеть, это не единственный тип, который можно обобщить с помощью typealias .
Это значительно проще реализовать в Javascript, потому что Javascript не делает большой разницы между объектом и словарем, а также между строкой и ключом. А также все функции в Javascript имеют один и тот же тип…
Тем не менее, в Swift мы можем создавать типобезопасные редукторы аналогичным образом:

 let appState1Reducer: (AppState1, Action) -> AppState1 = { state, action in
   switch action {
   case let action as ActionImpl<Int> where action.type == "changeValue1":
        var newState = state
        newState.value1 = action.payload
        return newState
   case let action as ActionImpl<String> where action.type == "changeValue2":
        var newState = state
        newState.value2 = action.payload
        return newState
   default:
      return state
   }
}

let appState2Reducer: (AppState2, Action) -> AppState2 = { state, action in
   switch action {
   case let action as ActionImpl<Double> where action.type == "changeValue3":
        var newState = state
        newState.value3 = action.payload
        return newState
   default:
      return state
   }
}

let rootReducer: (RootState, Action) -> RootState = { state, action in
    var newState = state
    newState.appState1 = appState1Reducer(state.appState1, action)
    newState.appState2 = appState2Reducer(state.appState2, action)
    return newState
}

let state = RootState()
print(state)
let action = ActionImpl(type: "changeValue2", payload: "New value!")
let newState = rootReducer(state, action)
print(newState)
  

Корневой редуктор не намного сложнее, чем composeReducers помощник в Redux, и это все еще функция, какой она должна быть.