Существуют ли "алгоритмы" в функциональном программировании?

Не имеет смысла просить документировать "алгоритмы" вашего программного обеспечения (скажем, в спецификации дизайна), если он реализован в функциональной парадигме? Всякий раз, когда я думаю об алгоритмах в технических документах, я представляю себе цикл while с множеством последовательных шагов.

Взгляд на неформальное значение словаря для алгоритма:

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

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

EDIT - так много хороших ответов, это несправедливо для меня, чтобы выбрать лучший ответ: (Спасибо за все перспективы, все они делают замечательные наблюдения.

Ответы

Ответ 1

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

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

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

Лень делает это еще более интересным, размывая линии между "структурой данных" и "алгоритмом". Леневая структура данных, как и список, никогда не должна полностью существовать в памяти. Это означает, что вы можете составлять функции, которые создают большие промежуточные структуры данных, фактически не нуждаясь в том, чтобы использовать все это пространство или жертвуя асимптотической эффективностью. В качестве тривиального примера рассмотрим это определение factorial (да, это клише, но я не могу придумать ничего лучшего:/):

factorial n = product [1..n]

У этого есть две сложенные части: во-первых, мы генерируем список из 1 в n, а затем складываем его, умножая (product). Но, благодаря лени, список никогда не должен существовать в памяти полностью! Мы оцениваем как можно большую производящую функцию на каждом шаге product, а сборщик мусора восстанавливает старые ячейки, как мы с ними поработали. Таким образом, хотя это выглядит так, как будто ему нужна память O(n), на самом деле это уходит с O(1). (Ну, предполагая, что все номера принимают O(1) память.)

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

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

Ответ 2

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

Если вы используете функциональный язык (Lisp, ML, Haskell) или императивный/процедурный (C/Java/Python), вы указываете операции и их порядок (иногда порядок может не указываться, но это побочный вопрос).

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

Рассмотрим, например, функциональную реализацию факториала:

(defun ! (n)
  (if (zerop n)
      1
      (* n (! (1- n)))))

Как только можно легко увидеть порядок выполнения: 1 * 2 * 3 * .... * n и тот факт, что есть n-1 умножения и вычитания для аргумента n.

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

Ответ 3

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

Похоже, вы попадаете в технику/семантику здесь. Если вас попросят задокументировать алгоритм решения проблемы, кто бы вас ни попросил, вы хотите знать, как вы решаете проблему. Даже если это будет функционировать, будет достигнута серия шагов для достижения решения (даже при всей ленивой оценке). Если вы можете написать код для решения проблемы, вы можете написать код в псевдокоде, что означает, что вы можете написать код в терминах алгоритма, насколько я могу судить.

И, поскольку кажется, что вы сильно зацикливаетесь на определениях здесь, я задам вопрос, который доказывает мою точку зрения. Языки программирования, будь то функциональные или императивные, в конечном счете запускаются на машине. Правильно? Вашему компьютеру должна быть предложена пошаговая процедура для выполнения инструкций низкого уровня. Если это утверждение верно, то каждая компьютерная программа высокого уровня может быть описана с точки зрения их инструкций низкого уровня. Поэтому каждая программа, будь то функциональная или императивная, может быть описана алгоритмом. И если вы не можете найти способ описать алгоритм высокого уровня, тогда выведите байт-код/​​сборку и объясните свой алгоритм в терминах этих инструкций.

Ответ 4

Рассмотрим этот функциональный пример схемы:

(define (make-list num)
  (let loop ((x num) (acc '()))
    (if (zero? x)
        acc
        (loop (- x 1) (cons x acc)))))

(make-list 5)            ; dumb compilers might do this
(display (make-list 10)) ; force making a list (because we display it)

С вашей логикой make-list не будет считаться алгоритмом, поскольку он не выполняет вычисления пошагово, но это правда?

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

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