Вложенные функции: неправильное использование побочных эффектов?


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

Хотя я в конечном итоге пошел со вторым подходом, потому что он сделал вызовы функций меньше, и это, казалось, "чувствую себя" лучше, из моего чтения кажется, что я могу упустить один из главных моментов функционального программирования, в том, что это кажется "побочным эффектом"? Теперь понятно, что эти вложенные функции не могут изменять внешние привязки, так как язык, который я использовал, предотвращает это, но если вы посмотрите на каждую отдельную внутреннюю функцию, вы не можете сказать "учитывая те же параметры, Эта функция вернет те же результаты", потому что они используют переменные из родительской области... разве я не прав?

Что такое желаемый способ продолжения?

Спасибо!

3 5

3 ответа:

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

Один из примеров, когда вложенные функции полезны, - это замена циклов. Параметры вложенной функции могут выступать в качестве индукционных переменных, которые накапливают значения. Простой пример:
let factorial n =
    let rec facHelper p n =
        if n = 1 then p else facHelper (p*n) (n-1)
    in
    facHelper 1 n

В этом случае не имеет смысла объявлять функцию типа facHelper глобально, так как пользователи не стоит беспокоиться о параметре p.

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

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

Вот небольшой пример в схеме:

(define (foo a)
  (define (bar b)
    (+ a b))      ; getting a from outer scope, not purely functional
  (bar 3))

(define (foo a)
  (define (bar a b)
    (+ a b))      ; getting a from function parameters, purely functional
  (bar a 3))


(define (bar a b) ; since this is purely functional, we can remove it from its
  (+ a b))        ; environment and it still works

(define (foo a)
  (bar a 3))
Лично я бы пошел с первым подходом, но оба будут работать одинаково хорошо.

Рассмотрим следующий (надуманный) фрагмент Хаскелла:

putLines :: [String] -> IO ()
putLines lines = putStr string
    where string = concat lines

string является локально связанной именованной константой. Но разве это не функция, не принимающая никаких аргументов, которая закрывается над lines и поэтому является референциально непрозрачной? (В Haskell, констант и функций, не возвращающих значений, действительно неразличимы!) Считаете ли вы приведенный выше код "побочным эффектом" или нефункциональным из-за этого?