Почему Go не допускает объявления вложенных функций (функций внутри функций)?

87

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

Лямбды работают должным образом:

func main() {
    inc := func(x int) int { return x+1; }
}

Однако следующее объявление внутри объявления не допускается:

func main() {
    func inc(x int) int { return x+1; }
}

По какой причине нельзя использовать вложенные функции?

Corazza
источник
хм, я не знаю, func main() { func (x int) int { return x+1; }(3) }
собирались
@YasirG. но это ведь тоже лямбда, не так ли? Я не понимаю вашего комментария ...
corazza
Какие функции будут разрешены во втором примере в синтаксисе, которые не поддерживаются в первом случае?
Not_a_Golfer
@yannbane, это лямбда-выражение, я не думаю, что вы можете объявить функцию внутри другой функции, такой как JS. Поэтому я бы сказал, что вам лучше всего использовать лямбды.
ymg
@Not_a_Golfer: одна из возможностей - реализовать его так, как это делает JavaScript, по сути, назначение функции переменной сильно отличается от объявления функции, потому что поток управления влияет на такие переменные, в то время как функции в JavaScript не затрагиваются. Это означает, что вы можете вызвать inc()второй пример до фактического объявления. Но! Я ищу причины, я мало знаю о Go, но хотел бы узнать, в чем заключалась логика этого правила.
corazza

Ответы:

54

Я думаю, что есть 3 причины, по которым эта очевидная функция не разрешена

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

Это всего лишь мое мнение - я не видел официального заявления разработчиков языка.

Ник Крейг-Вуд
источник
2
Паскаль (по крайней мере, это воплощение Delphi) понял их правильно и просто: вложенные функции ведут себя так же, как обычные, но также имеют доступ к переменным в области видимости их включающих функций. Я не думаю, что это сложно реализовать. С другой стороны, написав много кода Delphi, я не уверен, что мне очень нужны вложенные функции: иногда они кажутся изящными, но имеют тенденцию взрывать включающую функцию, делая ее трудно читаемой. Также доступ к аргументам их родителей может затруднить чтение программы, поскольку доступ к этим переменным осуществляется неявно (не передается как формальные параметры).
kostix
1
локальные функции отлично подходят как промежуточный этап рефакторинга на пути к извлечению методов. В С # они сделали их более ценными, когда ввели статические локальные функции, которым не разрешено захватывать переменные из включающей функции, поэтому вы вынуждены передавать что-либо в качестве параметра. Статические локальные функции делают пункт 3 несущественным. Пункт 2, с моей точки зрения, тоже не имеет значения.
Cosmin Sontu
47

Конечно, есть. Вам просто нужно присвоить их переменной:

func main() {
    inc := func(x int) int { return x+1; }
}
Мэтт Уильямсон
источник
4
Стоит отметить, что вы не можете вызывать такие функции (inc) рекурсивно.
Mohsin Kale,
26

Часто задаваемые вопросы (FAQ)

Почему в Go нет функции X?

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

Если вас беспокоит, что в Go отсутствует функция X, простите нас и изучите возможности, которые есть в Go. Вы можете обнаружить, что они интересными способами компенсируют отсутствие сертификатов X.

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

Питер
источник
19
Честно говоря, я не думаю, что кто-то продемонстрировал какую-либо особую сложность, которую может вызвать использование вложенных функций. Кроме того, хотя я согласен с процитированной философией, я не уверен, что разумно называть вложенные функции «функцией», а не только ссылаться на их упущение как на функцию. Знаете ли вы о каких-либо сложностях, которые могут возникнуть при использовании вложенных функций? Я предполагаю, что они будут просто синтаксическим сахаром для лямбд (я не могу придумать другого разумного поведения).
joshlf
Поскольку go скомпилирован, единственный способ сделать это AFAIK - создать другой синтаксис для определения лямбда-выражений. И я действительно не вижу для этого варианта использования. у вас не может быть статической функции внутри статической функции, созданной в реальном времени - что, если мы не введем конкретный путь кода, который определяет функцию?
Not_a_Golfer
Просто передайте лямбда-интерфейс {} и введите assert. Например. f_lambda: = lambda (func () rval {}) или что бы то ни было в прототипе. Синтаксис func decl не поддерживает это, но язык полностью поддерживает.
BadZen
8

В Go разрешены вложенные функции. Вам просто нужно назначить их локальным переменным во внешней функции и вызывать их с использованием этих переменных.

Пример:

func outerFunction(iterations int, s1, s2 string) int {
    someState := 0
    innerFunction := func(param string) int {
        // Could have another nested function here!
        totalLength := 0
        // Note that the iterations parameter is available
        // in the inner function (closure)
        for i := 0; i < iterations; i++) {
            totalLength += len(param)
        }
        return totalLength
    }
    // Now we can call innerFunction() freely
    someState = innerFunction(s1)
    someState += innerFunction(s2)
    return someState
}
myVar := outerFunction(100, "blah", "meh")

Внутренние функции часто удобны для локальных горутин:

func outerFunction(...) {
    innerFunction := func(...) {
        ...
    }
    go innerFunction(...)
}
Vthorsteinsson
источник
Замыкание в go в некоторых аспектах отличается от простой функции. Например, вы не можете вызвать закрытие рекурсивно.
Michał Zabielski
7
@ MichałZabielski: Вы можете вызвать его рекурсивно, если объявите его до того, как определите.
P Daddy
1

Просто надо сразу его вызвать, добавив ()в конец.

func main() {
    func inc(x int) int { return x+1; }()
}

Изменить: не может иметь имя функции ... так что сразу вызывается просто лямбда-функция:

func main() {
    func(x int) int { return x+1; }()
}
Ник
источник
1
Эээ, это не соответствует тому, что можно было бы ожидать от определения функции
corazza
1
@corazza Ах, я пропустил слово "декларация", когда прочитал вопрос. Виноват.
Ник
1
@corazza Также я напортачил с синтаксисом. Требуется удалить имя функции. Итак, это в основном лямбда-функция, которая вызывается немедленно.
Ник