Почему имя первого параметра функции в Swift не требуется при вызове?

Я изучаю Swift, и мне кажется странным, почему при вызове функции первое имя параметра не требуется.

func say(greeting: String, toName: String) {
    print("\greeting), \(toName)!")
}

say("Goodbye", toName: "Hollywood") // <-- why is there no "greeting" required?

Ответ 1

Как говорили другие, это проблема стиля, возникающая с помощью Objective-C.

Чтобы понять, зачем кому-то нужен этот стиль, рассмотрите модификацию вашего примера:

func say(greeting: String) {
    print("\(greeting)")
}

который вы бы назвали так:

say("Hello!")

Когда вы смотрите на имена, которые используете, возможно, какая-то информация отсутствует. С помощью функции say() вы можете разумно подумать, что это функция, позволяющая вам вообще что-то сказать. Но когда вы смотрите на имя параметра, довольно ясно, что это функция для выражения приветствия, а не для высказывания чего-либо.

Итак, Objective-C предпочитает писать так:

func sayGreeting(greeting: String) {
    print("\(greeting)")
}

который вы бы назвали так:

sayGreeting("Hello!")

и теперь ясно, что вы говорите приветствие. Другими словами, само имя функции более четко описывает то, что вы делаете. По этой причине sayGreeting("Hello!") предпочтительнее say(greeting: "Hello!"), потому что ключевая вещь, которую выполняет функция, должна описываться ее именем, а не относиться к имени параметра и иметь второстепенное значение.

Но это рассуждение действительно работает только для первого аргумента. Предположим, вы хотите добавить имя, как вы это сделали. На языке, таком как C, где у вас вообще нет внешних имен параметров, вы можете написать:

void sayGreetingToName(char * greeting, char * person) { ...

и назовите его так:

sayGreetingToName("Hello", "Dave");

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

func sayGreetingToName(greeting: String, name: String? = nil) {
    if let name = name {
        print("\(greeting), \(name)!")
    }
    else {
        print("\(greeting)!")
    }
}

называя его как:

sayGreetingToName("Hello", "Dave")

будет выглядеть в основном в порядке, но:

sayGreetingToName("Hello")

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

Итак, если вы пишете:

func sayGreeting(greeting: String, toName: String? = nil) {
    if let name = toName {
        print("\(greeting), \(name)!")
    }
    else {
        print("\(greeting)!")
    }
}

вы можете называть его двумя способами:

sayGreeting("Hello")
sayGreeting("Hello", toName: "Dave")

и все выглядит совершенно ясно.

Итак, идея такого стиля письма заключается в том, что само имя функции должно содержать любую информацию, содержащую имя первого параметра, но не имеет смысла расширять ее до последующих параметров. Таким образом, по умолчанию первое не имеет внешнего имени, но остальные делают. Функция состоит в том, чтобы все время приветствовать приветствие, поэтому оно должно быть присуще имени функции (и, следовательно, не дублируется, настаивая на внешнем имени первого параметра), но может или не может быть сказано к определенному имени, чтобы информация не содержалась в имени функции.

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

sayGreeting("Hello", toName: "Dave")

Произнесите приветствие "Hello", чтобы (человек с именем) "Dave"

Это довольно приятный стиль, как только вы привыкнете к нему.

Ответ 2

Крис Лэттнер рассказывает об этом именно в Что нового в Swift 2 Talk:

Назад во времена Swift 1.x это поведение применяется только к методам. Функции, написанные вне классов, структур или перечислений, не требуют, чтобы какой-либо параметр назывался в вызове функции (если вы явно не принудительно его использовали с использованием имени внешнего параметра в определении функции).

Из-за соглашений из Objective-C методы часто назывались таким образом, что первый параметр уже был частью имени метода.
Примеры: indexOf(_:) вместо index(of:) или charactersAtIndex(_:) вместо charactersAt(index:).
В Objective-C это будет написано как indexOf: и charactersAtIndex:. Нет никаких фигурных скобок для разделения имени функции из параметров функции. Таким образом, параметры были в основном частью имени функции.

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

tl; dr Поведение - это остаток от Objective-C

Ответ 3

Это поведение по умолчанию для функций в swift, опуская внешнее имя для первого параметра функции.

По умолчанию первый параметр опускает свое внешнее имя, а второй и последующие параметры используют свое местное имя в качестве внешнее имя.

Из Руководство по языку - Функции.

Обратите внимание, что вы можете добавить внешнее имя и к первому параметру функции, если хотите:

func foo(extBar bar: String, bar2: String) {
    print(bar+bar2)
}

foo(extBar: "Hello", bar2: "World")

Аналогично, вы можете указать параметры функции 2 (и т.д.), чтобы опустить свои внешние имена, добавив _ до внутреннего имени параметра в подпись функции.

func foo2(bar: String, _ bar2: String) {
    print(bar+bar2)
}

foo2("Hello", "World")

Обратите внимание, однако, что для инициализаторов внешние имена являются обязательными для всех параметров функции, включая первый.

Как и параметры функции и метода, параметры инициализации могут имеют как локальное имя для использования внутри тела инициализаторов, так и внешнее имя для использования при вызове инициализатора.

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

Из Руководство по языку - Инициализация.

В качестве примера рассмотрим

struct Foo {
    var bar : Int

    init(extBar: Int) {
        bar = extBar
    }
}

var a = Foo(extBar: 1)

Также в этом случае вы можете явно указать конструктору, чтобы параметры опускали свои внешние имена

struct Foo2 {
    var bar : Int

    init(_ intBar: Int) {
        bar = intBar
    }
}

var a = Foo2(1)

Ответ 4

Как уже упоминалось @dfri, это было решено именно так.

Обратите внимание, что вы можете легко настроить это поведение, явно указав имена параметров при вызове и _, чтобы опустить их:

func say (greeting greeting: String, _ toName: String){
    print("\(greeting), \(toName)!")
}

приводит к тому, что вам нужно позвонить через

say(greeting: "Goodbye", "Hollywood")

В качестве альтернативы

func say (greeting: String, _ toName: String){
    print("\(greeting), \(toName)!")
}

say("Goodbye", "Hollywood")

или

func say (greeting greeting: String, toName: String){
    print("\(greeting), \(toName)!")
}

say (greeting: "Goodbye", toName: "Hollywood")