1
votes

Pourquoi passer une fonction comme argument à une autre fonction, si nous pouvons simplement l'appeler pour l'implémentation?

Je n'arrive pas à comprendre la raison à quoi sert d'ajouter cette fonctionnalité.

Méthode qui doit être passée en argument ou appelée par une autre méthode

func averageArg(_ plus: (Int, Int) -> Int, _ a: Int, _ b : Int) -> Int {
    return plus(a, b) / 2
}

lors de l'appel d'une fonction depuis une autre fonction

func average(_ a : Int, _ b : Int) -> Int{
    return add(a, b) / 2
}

lors de la transmission d'une fonction comme argument à une autre fonction

func add(_ a : Int, _ b : Int) -> Int {
    return a + b 
}


2 commentaires

Pouvez-vous nous donner un peu de contexte? Où ce code est-il utilisé et dans quel but?


Ce code n'est qu'un exemple. Je parcourais la documentation de Swift et suis tombé sur ce concept de «type de fonction en tant que paramètre».


4 Réponses :


0
votes

Par exemple, sans la fonction "postProcess" comme argument, vous devrez implémenter des fonctions d'arborescence: getAndAddData, getAndSubData et getAndMulData.

func add(_ a:Int, _ b:Int) -> Int {
    return a + b
}

func sub(_ a:Int, _  b:Int) -> Int{
    return a - b
}

func mul(_ a:Int, _  b:Int) -> Int{
    return a * b
}

func getAndProcessData(_ postProcess:(Int, Int) -> Int, _ a:Int, _ b:Int) -> Int {

    let a2 = ExampleClass.get(a)
    let b2 = ExampleClass.get(b)
    return postProcess(a2, b2)
}

func exampleFunc(a:Int, b:Int) {

    let getAndAdd = self.getAndProcessData(self.add(_:_:), a, b)
    let getAndSub = self.getAndProcessData(self.sub(_:_:), a, b)
    let getAndMul = self.getAndProcessData(self.mul(_:_:), a, b)
}


0 commentaires

0
votes

La raison de prendre une fonction comme argument est qu'elle rend votre fonction plus flexible. Il permet à appelant de décider ce que fait plus en passant une fonction ou une fermeture qui combine deux valeurs Int pour renvoyer un code Int > valeur.

Par exemple, supposons que l'appelant souhaite que plus multiplie les valeurs:

["Ed", "Jo", "Bob", "Bill", "Chuck", "Alexander"]
func shortNamesFirst(_ name1: String, _ name2: String) -> Bool {
    if name1.count < name2.count {
        return true
    } else if name1.count > name2.count {
        return false
    } else {
        // when names are the same length, sort
        // them alphabetically
        return name1 < name2
    }
}

["Chuck", "Bob", "Bill", "Jo", "Ed", "Alexander"].sorted(by: shortNamesFirst)

ou prenez le max des deux valeurs (en passant une fermeture):

[4, 3, 2, 1]
[1, 3, 2, 4].sorted(by: >)

Un meilleur exemple de ceci est la fonction Swift trié (par :) . sorted prend une fermeture qui détermine ce que signifie areInIncreasingOrder . Cela vous permet de trier un tableau par ordre croissant et décroissant en changeant simplement la fonction passée:

[1, 2, 3, 4]
[1, 3, 2, 4].sorted(by: <)
50
print(averageArg({ max($0, $1) }, 1, 100))

Voici un exemple utilisant une fonction:

15
print(averageArg(*, 5, 6))

L'auteur de sorted n'a pas à fournir une version différente de sorted pour chaque type de commande qu'un utilisateur peut souhaiter. L'utilisateur décide de ce que signifie pour lui trié .


2 commentaires

Donc, l'avantage est donc: Différentes fonctions avec des fonctionnalités différentes pourraient être passées tant qu'elle a la même structure définie dans le paramètre de la fonction d'appel, qui est restreinte lorsque nous appelons une fonction par son nom dans une autre fonction.


D'accord. Merci. Cela a été utile. :)



0
votes

Votre exemple n'est pas une utilisation très convaincante du passage des fermetures. Cependant, imaginez que la mise en œuvre est variable, c'est à dire. vous ne savez pas ce qui devrait arriver dans certaines circonstances, la manipulation des fermetures est un grand avantage. Prenons cet exemple:

func doSomethingAsynchronously(completion completionClosure: (() -> Void)? = nil) {
   // Do asynchronous stuff

   if let completionClosure = completionClosure {
      completionClosure()
   }
}

Cela permet à un appelant de doSomething mais de gérer les échecs de la fonction de manière personnalisée en passant une fermeture.

Cela devient vraiment utile dans les situations asynchrones, où le résultat de votre fonction est utilisé pour faire quelque chose mais vous ne pouvez pas exécuter l'exécution dans le même thread (par exemple, les appels réseau).

func doSomething(with: AnyObject, whenFails failureClosure: ((Error) -> Void)? = nil) {
  var error: Error?

  // do stuff, maybe setting the error to something...

  if let error = error,
     let failureClosure = failureClosure {
         failureClosure(error)
  }
}


0 commentaires

0
votes

C'est une fonctionnalité de swift qui n'est pas souvent utilisée, mais qui peut parfois s'avérer extrêmement utile. Dans votre exemple, cela n'aurait en effet pas de sens, mais trouvons un scénario dans lequel cela a réellement du sens.

Voyons d'abord ce que signifie _ plus: (Int, Int) -> Int . Ce morceau de code signifie que votre fonction averageArg accepte toute fermeture ou fonction qui prend deux paramètres entiers et fournit un entier en sortie. Comme add (...) est conforme à cette exigence (deux arguments integer et il donne un integer en sortie), vous pouvez le transmettre comme argument.

Mais regardons un exemple où un tel paramètre aurait du sens. Disons que nous écrivons un code frontal pour une application qui affiche des avis sur les hôtels. Dans cette application, nous devons écrire quelque part une fonction fetchReviews (_ serverResponse: ([Review]) -> Void) qui obtient tous les avis sur les hôtels d'un serveur distant:

struct Review {
    let author: String
    let rating: Int
}

func fetchReviews(_ completionHandler: @escaping ([Review]) -> Void) {
    // Lets do a request to the server on a separate thread, so that the user can keep
    // interacting with the app. The app doesn't freeze.
    // Making qos .userInitiated will ensure that user interaction will be more important than
    // our backend request.
    DispatchQueue.global(qos: .userInitiated).async {
        // Do some request to the server....

        //...

        // Lets pretend we have received a response, and we are turning the response into an array of reviews
        completionHandler([Review]())
    }
}

Comme il peut y avoir un délai (parfois long) entre le moment où le serveur répond et donne tous les avis, et entre le moment où fetchReviews a été appelé, ce ne serait pas bien pour l'utilisateur si l'application se fige simplement. Au lieu de cela, vous pouvez faire la demande au serveur sur un thread distinct afin que l'utilisateur puisse continuer à utiliser l'application et que l'application ne se fige pas (voir la partie DispatchQueue.global ).

Cependant, nous voulons toujours afficher tous les avis à l'utilisateur une fois que nous recevons une réponse du serveur. En ajoutant le paramètre _ serverResponse , nous pouvons avertir quiconque a appelé fetchReviews dès que nous avons effectivement reçu la réponse du serveur. En procédant ainsi, l'utilisateur peut continuer à interagir avec l'application, et une fois que les avis sont disponibles, nous pouvons les montrer à l'utilisateur!


0 commentaires