Вложенные функции: неправильное использование побочных эффектов?
Я изучаю функциональное программирование и пытался решить пару проблем в функциональном стиле. Одна вещь, которую я испытал, разделяя мою проблему на функции, заключалась в том, что у меня было два варианта: использовать несколько разрозненных функций со сходными списками параметров или использовать вложенные функции, которые в качестве замыканий могут просто ссылаться на привязки в родительской функции.
Хотя в итоге я выбрал второй подход, потому что он уменьшал количество вызовов функций и, казалось, "чувствовал себя лучше", из моего прочтения мне кажется, что я упускаю один из основных пунктов функционального программирования, в том смысле, что эта сторона кажется -effecty"? Теперь предоставленные, эти вложенные функции не могут изменять внешние привязки, поскольку язык, который я использовал, предотвращает это, но если вы посмотрите на каждую отдельную внутреннюю функцию, вы не сможете сказать "при одинаковых параметрах эта функция будет возвращать те же результаты" потому что они используют переменные из родительской области... я прав?
Какой желаемый способ продолжить?
Спасибо!
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))
Лично я бы пошел с первым подходом, но любой из них будет работать одинаково хорошо.
Рассмотрим следующий (надуманный) фрагмент Haskell:
putLines :: [String] -> IO ()
putLines lines = putStr string
where string = concat lines
string
является локально связанной именованной константой. Но разве это не функция, не принимающая аргументов, которая закрывается lines
и поэтому ссылочно-непрозрачный? (В Haskell константы и нулевые функции действительно неразличимы!) Считаете ли вы приведенный выше код "побочным эффектом" или не функциональным из-за этого?