2017-09-16 3 views
7

J'expérimente une architecture basée sur des messages dans Swift. J'essaie de faire quelque chose de similaire à l'architecture d'Elm, par exemple. Voici comment mon code ressemble:Messages génériques dans une architecture basée sur des messages

enum SideEffect<Message> { 

    case sendRequest((String) -> Message) 
} 

protocol Component { 

    associatedtype Message 

    mutating func send(msg: Message) -> [SideEffect<Message>] 
} 

struct State: Component { 

    var something: String? 

    enum Message { 

     case downloadSomething 
     case receiveResponse(String) 
    } 

    mutating func send(msg: Message) -> [SideEffect<Message>] { 
     switch msg { 
      case .downloadSomething: 
       return [.sendRequest(Message.receiveResponse)] 
      case .receiveResponse(let response): 
       something = response 
       return [] 
     } 
    } 
} 

Ainsi, l'état est modélisé par State et vous pouvez le changer en envoyant Message s. S'il y a des effets secondaires à calculer, ils sont retournés sous la forme d'un message SideEffect et seront pris en charge par quelqu'un d'autre. Chaque message SideEffect prend un argument "callback", un Message à envoyer lorsque l'effet secondaire est terminé. Cela fonctionne très bien. Maintenant, et si je veux avoir un message d'effet secondaire générique? Je voudrais avoir quelque chose comme ceci:

struct Request<ReturnType> { … } 

et ont un effet secondaire lié à charger la demande et retourner une valeur de type ReturnType:

enum SideEffect<Message> { 
    case sendRequest(Request<T>, (T) -> Message) 
} 

Mais ce (évidemment) ne compile pas , car le case devrait être générique sur T. Je ne peux pas faire l'ensemble SideEffect générique sur T, car il y a d'autres effets secondaires qui n'ont rien à voir avec T.

Puis-je créer en quelque sorte un message SideEffect avec un Request<T> qui enverrait plus tard un Message avec ? (Je pense que je veux quelque chose comme this feature discussed on swift-evolution.)

+0

Que diriez-vous que nous faisons un protocole 'Returnable' et faire les' ReturnType' sont conformes à ce protocole ? Ensuite, nous pouvons également étendre d'autres types, comme 'String', pour se conformer à ce protocole. – sCha

+0

Vous voudrez taper "T", en général cela peut être fait avec des fermetures (par exemple vous faites une fermeture qui exécute la requête et passe ensuite le résultat à votre fonction qui donne alors un message, cachant ainsi le type de ' T' du monde extérieur). Je ne suis pas du tout familier avec l'architecture d'Elm et je ne suis donc pas sûr de savoir comment vous attendez que 'Request' soit implémenté, mais [quelque chose comme ça] (http://swift.sandbox.bluemix.net/#/ repl/59e15aad6cbea87f72c470cc) être viable? – Hamish

+0

Hamish à la rescousse à nouveau! Je pense que c'est exactement ce dont j'avais besoin. Je battais autour de l'effacement de type, mais apparemment je ne me suis pas habitué au concept alors je n'ai pas trouvé la solution. Merci beaucoup! J'ai marqué la question pour que votre commentaire soit converti en une réponse. – zoul

Répondre

1

Vous aurez besoin de taper T - généralement cela peut être fait avec des fermetures, car ils peuvent référencer le contexte du site où ils sont créés, sans exposer ce contexte au monde extérieur.

Par exemple, avec une Request<T> maquette (en supposant qu'il est une opération async):

struct Request<T> { 

    var mock: T 

    func doRequest(_ completion: @escaping (T) -> Void) { 
     // ... 
     completion(mock) 
    } 
} 

Nous pouvons construire une RequestSideEffect<Message> qui détient une fermeture qui prend un rappel (Message) -> Void donné et effectue alors une requête sur un capturé Request<T> exemple, la transmission du résultat à travers un (T) -> Message, dont le résultat peut ensuite être renvoyé à la fonction de rappel (maintenant ainsi le type de variable T « contenue » dans la fermeture):

Maintenant, votre SideEffect<Message> peut ressembler à ceci:

enum SideEffect<Message> { 
    case sendRequest(RequestSideEffect<Message>) 
} 

Et vous pouvez mettre en œuvre State comme ceci:

protocol Component { 
    associatedtype Message 
    mutating func send(msg: Message) -> [SideEffect<Message>] 
} 

struct State: Component { 

    var something: String 

    enum Message { 
     case downloadSomething 
     case receiveResponse(String) 
    } 

    mutating func send(msg: Message) -> [SideEffect<Message>] { 
     switch msg { 
     case .downloadSomething: 
      let sideEffect = RequestSideEffect(
       request: Request(mock: "foo"), nextMessage: Message.receiveResponse 
      ) 
      return [.sendRequest(sideEffect)] 
     case .receiveResponse(let response): 
      something = response 
      return [] 
     } 
    } 
} 

var s = State(something: "hello") 
let sideEffects = s.send(msg: .downloadSomething) 

for case .sendRequest(let sideEffect) in sideEffects { 
    sideEffect.doRequest { 
     _ = s.send(msg: $0) // no side effects expected 
     print(s) // State(something: "foo") 
    } 
}