Зачем передавать функцию в качестве аргумента другой функции, если мы можем просто вызвать ее для реализации?

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

Метод, который должен быть передан в качестве аргумента или вызываться другим методом

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

при вызове функции из другой функции

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

при передаче функции в качестве аргумента другой функции

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

Можете ли вы дать нам некоторый контекст? Где используется этот код и с какой целью?

Rengers 23.01.2019 12:30

Этот код является просто примером. Я просматривал документацию Swift и наткнулся на эту концепцию «тип функции как параметр».

hakuna_matata 23.01.2019 14:59
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
2
88
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Например, без функции "postProcess" в качестве аргумента вам пришлось бы реализовывать древовидные функции: getAndAddData, getAndSubData и 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)
}
Ответ принят как подходящий

Причина использования функции в качестве аргумента заключается в том, что она делает вашу функцию более гибкой. Это позволяет абонент решать, что делает plus, передавая функцию или замыкание, которое объединяет два значения Int, чтобы вернуть значение Int.

Например, предположим, что вызывающая сторона хочет, чтобы plus перемножил значения:

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

или возьмите max двух значений (передав замыкание):

print(averageArg({ max($0, $1) }, 1, 100))
50

Лучшим примером этого является функция Swift sorted(by:). sorted принимает замыкание, которое определяет, что означает areInIncreasingOrder. Это позволяет вам сортировать массив в порядке возрастания и убывания, просто изменяя переданную функцию:

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

Вот пример использования функции:

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)
["Ed", "Jo", "Bob", "Bill", "Chuck", "Alexander"]

Автору sorted не нужно предоставлять разные версии sorted для каждого типа заказа, который может захотеть пользователь. Пользователь сам решает, что для него значит sorted.

Таким образом, преимущество заключается в следующем: можно передавать разные функции с разными функциями, если они имеют одинаковую структуру, определенную в параметре вызывающей функции, которая ограничивается, когда мы вызываем функцию по ее имени в другой функции.

hakuna_matata 23.01.2019 15:03

Хорошо. Спасибо. Это было полезно. :)

hakuna_matata 23.01.2019 15:18

Ваш пример - не очень убедительное использование передачи замыканий. Однако представьте, что реализация является переменной, т.е. вы не знаете, что должно произойти при определенных обстоятельствах, работа с замыканиями — это большое преимущество. Возьмите этот пример:

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)
  }
}

Это позволяет вызывающему объекту doSomething, но обрабатывать сбои функции особым образом, передавая замыкание.

Это становится действительно полезным в асинхронных ситуациях, когда результат вашей функции используется для каких-то действий, но вы не можете запустить выполнение в том же потоке (например, сетевые вызовы).

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

   if let completionClosure = completionClosure {
      completionClosure()
   }
}

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

Давайте сначала посмотрим, что означает _ plus: (Int, Int) -> Int. Этот фрагмент кода означает, что ваша функция averageArg принимает любые закрытие или функция, которые принимают два целочисленных параметра и выдают целое число в качестве вывода. Поскольку add(...) соответствует этому требованию (два аргумента integer и выдает integer в качестве вывода), вы можете передать его в качестве аргумента.

Но давайте посмотрим на пример, где такой параметр имеет смысл. Допустим, мы пишем интерфейсный код для приложения, которое показывает отзывы об отелях. В этом приложении нам нужно где-то написать функцию fetchReviews(_ serverResponse: ([Review]) -> Void), которая получает все отзывы об отелях с какого-то удаленного сервера:

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]())
    }
}

Поскольку может быть (иногда большая) задержка между тем, когда сервер отвечает и дает все обзоры, и между вызовом fetchReviews, для пользователя было бы нехорошо, если бы приложение просто зависло. Вместо этого вы можете сделать запрос к серверу в отдельном потоке, чтобы пользователь мог продолжать использовать приложение, а приложение не зависало (см. часть DispatchQueue.global).

Однако мы по-прежнему хотим отображать все отзывы пользователю после получения ответа от сервера. Добавив параметр _ serverResponse, мы можем уведомить любого, кто звонил fetchReviews, как только мы действительно получили ответ сервера. Делая это таким образом, пользователь может продолжать взаимодействовать с приложением, и как только обзоры станут доступны, мы сможем показать их пользователю!

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