{"id":14128521,"url":"https://github.com/KikyTokamuro/Simple-Functional-Programming","last_synced_at":"2025-08-03T23:31:19.027Z","repository":{"id":50749399,"uuid":"164716687","full_name":"KikyTokamuro/Simple-Functional-Programming","owner":"KikyTokamuro","description":"Функциональное программирование простым языком + примеры","archived":false,"fork":false,"pushed_at":"2019-11-20T20:46:03.000Z","size":67,"stargazers_count":30,"open_issues_count":0,"forks_count":9,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-12-04T04:11:09.493Z","etag":null,"topics":["functional-programming","hacktoberfest","haskell","python","scheme","simple"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"unlicense","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/KikyTokamuro.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-01-08T19:17:47.000Z","updated_at":"2024-07-03T16:01:49.000Z","dependencies_parsed_at":"2022-09-05T02:42:07.997Z","dependency_job_id":null,"html_url":"https://github.com/KikyTokamuro/Simple-Functional-Programming","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KikyTokamuro%2FSimple-Functional-Programming","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KikyTokamuro%2FSimple-Functional-Programming/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KikyTokamuro%2FSimple-Functional-Programming/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KikyTokamuro%2FSimple-Functional-Programming/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/KikyTokamuro","download_url":"https://codeload.github.com/KikyTokamuro/Simple-Functional-Programming/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":228571844,"owners_count":17938772,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["functional-programming","hacktoberfest","haskell","python","scheme","simple"],"created_at":"2024-08-15T16:01:47.550Z","updated_at":"2024-12-07T06:31:20.852Z","avatar_url":"https://github.com/KikyTokamuro.png","language":"Python","funding_links":[],"categories":["Python"],"sub_categories":[],"readme":"# Основные понятия функционального программирования\n\n- [Краткое объяснение](#краткое-объяснение)\n- [Особенности функционального программирования](#особенности-функционального-программирования)\n- [Функции первого класса](#функции-первого-класса)\n- [Чистые функции](#чистые-функции)\n- [Ссылочная прозрачность](#ссылочная-прозрачность)\n- [Замыкание](#замыкание)\n- [Каррирование](#каррирование)\n- [Частичное применение](#частичное-применение)\n- [Хвостовая рекурсия и ее оптимизация](#хвостовая-рекурсия-и-ее-оптимизация)\n- [Углубление в функциональное программирование](#углубление-в-функциональное-программирование)\n\n## Краткое объяснение:\nЕсли верить [Википедии](https://ru.wikipedia.org/wiki/Функциональное_программирование), то функциональное программирование - это раздел дискретной математики и парадигма программирования, в которой процесс вычисления трактуется как вычисление значений функций в математическом понимании последних (в отличие от функций как подпрограмм в процедурном программировании). \nТо есть функциональное программирование предполагает обходиться вычислением результатов функций от исходных данных и результатов других функций, и не предполагает явного хранения состояния программы. Соответственно, не предполагает оно и изменяемость этого состояния (в отличие от императивного, где одной из базовых концепций является переменная, хранящая своё значение и позволяющая менять его по мере выполнения алгоритма).\n\n## Особенности функционального программирования:\n- [Чистые функции](https://ru.wikipedia.org/wiki/Чистота_функции) - это функции, которые не обладают побочными эффектами, а так же являются детерминированными.\n- Анонимные функции / [Лямбда функции](https://ru.wikipedia.org/wiki/Лямбда-выражение) - это функции, которые объявляются в месте использования, и не получают уникального идентификатора для доступа к ним. Чаще всего для определения анонимных функций используют лямбда выражения.\n- [Функции высшего порядка](https://ru.wikipedia.org/wiki/Функция_высшего_порядка) - это функции, которые могут принимать другие функции в качестве аргументов, или возвращать другие функции в качестве результата.\n- [Композиция функций](https://ru.wikipedia.org/wiki/Композиция_функций) - это применение одной функции к результату другой функции.\n- [Каррирование](https://ru.wikipedia.org/wiki/Каррирование) - это преобразование функции с многими аргументами, к набору функций от одного аргумента.\n- [Замыкание](https://ru.wikipedia.org/wiki/Замыкание_(программирование)) - это комбинация функции и окружения, в котором она была определена. То есть это функция, которая определяется в теле другой функции, и создается каждый раз во время ее выполнения, а так же она имеет ссылки на локальные переменные внешней функции.\n- [Неизменяемые данные](https://ru.wikipedia.org/wiki/Неизменяемый_объект) - это данные, состояние которых не может быть изменено после создания.\n- [Сопоставление с образцом](https://ru.wikipedia.org/wiki/Сопоставление_с_образцом) - это метод анализа и обработки данных, основанный на сопоставлении исследуемого значения с тем или иным образцом, в качестве которого может выступать число, строка, или иная конструкция поддерживаемая языком.\n- [Рекурсия](https://ru.wikipedia.org/wiki/Рекурсия#В_программировании) - это вызов функции из нее же самой, или через другие функции.\n\n## Функции первого класса:\nФункции могут передаваться в качестве аргументов другим функциям, возвращаться из функций, храниться в переменных и структурах данных и создаваться во время выполнения.\n\nПримеры на языке Python:\n```python\n# Определение функции которая прибавляет единицу к каждому элементу списка:\ndef inc(l):\n    return [x + 1 for x in l]\n\t\n# Функция, которая принимает функцию в качестве аргумента, и возвращает другую функцию как результат:\ndef changeList(f, l):\n    return f(l)\n\n# changeList(f, l) = f(l)\n\u003e\u003e\u003e changeList(inc, [1,2,3])\n[2, 3, 4]\n\n\n# Хранение функции в переменной\n\u003e\u003e\u003e c = changeList\n\u003e\u003e\u003e c(inc, [2, 3])\n[3, 4]\n\n\n# Хранение функций в структурах данных предоставляемых языком:\n# В данном случае, хранение анонимных функций в словаре.\nhashIncDec = {\n    'inc': lambda x: x + 1,\n    'dec': lambda x: x - 1\n}\n\n\u003e\u003e\u003e hashIncDec['inc'](5)\n6\n\u003e\u003e\u003e hashIncDec['dec'](5)\n4\n```\n\nПримеры на языке Scheme (GNU Guile):\n```scheme\n;; Определение функции которая прибавляет единицу к каждому элементу списка:\n(define (inc-list l)\n  (map 1+ l))\n\n;; Функция, которая принимает функцию в качестве аргумента, и возвращает другую функцию как результат:\n(define (change-list f l)\n  (f l))\n\nscheme@(guile-user)\u003e (change-list inc-list '(1 2 3))\n$1 = (2 3 4)\n\n\n;; Хранение функции в переменной\n(define c change-list)\n\nscheme@(guile-user)\u003e (c inc-list '(1 2 3))\n$2 = (2 3 4)\n\n\n;; Хранение функций в структурах данных предоставляемых языком:\n;; В данном случае, хранение анонимных функций в хеш-таблице.\n(define hash-inc-dec (make-hash-table))\n\n(hash-set! hash-inc-dec 'inc (lambda (x) (+ x 1) ))\n(hash-set! hash-inc-dec 'dec (lambda (x) (- x 1) ))\n\nscheme@(guile-user)\u003e ((hash-ref hash-inc-dec 'inc) 5)\n$3 = 6\nscheme@(guile-user)\u003e ((hash-ref hash-inc-dec 'dec) 5)\n$4 = 4\n```\n\n## Чистые функции:\n- В чистых функциях отсутствуют побочные эффекты, так же как и в математических функциях.\n- В чистых функциях для одного и того же аргумента, всегда будет возвращаться один и тот же результат.\n- Результат вызова функции определяется ее входными аргументами.\n- Если функция выполняет ввод/вывод информации, она уже не является чистой (чтение из файла, запись в файл, печать строки, и тд).\n- Чистые функции являются детерминированными.\n- Чистые функции не имеют состояния.\n\nТаким образом, чистые функции, делают работу кода более предсказуемым, а так же улучшают удобство его отладки, и дают возможность осуществлять композицию функций.\n\nПримеры на языке Python:\n```python\n# Чистая функция, которая ничего не изменяет, и при одном и том же аргументе, всегда возвращает один и тот же результат:\ndef inc(x):\n\treturn x + 1\n\t\n\u003e\u003e\u003e inc(10)\n11\n\u003e\u003e\u003e inc(10)\n11\n\u003e\u003e\u003e inc(10)\n11\n\n\n# Грязная функция, которая при одном и том же аргументе, возвращает разные результаты:\n# Грязные функции могут осуществлять ввод/вывод, так же они могут изменять состояние или глобальные переменные.\nfrom random import randint\n\ndef randInc(x):\n\treturn randint(0, 100) + x\n\n\u003e\u003e\u003e randInc(1)\n94\n\u003e\u003e\u003e randInc(1)\n6\n\u003e\u003e\u003e randInc(1)\n33\n```\n\nПримеры на языке Scheme (GNU Guile):\n```scheme\n;; Чистая функция, которая ничего не изменяет, и при одном и том же аргументе, всегда возвращает один и тот же результат:\n(define (inc x) \n\t(+ x 1))\n\nscheme@(guile-user)\u003e (inc 10)\n$1 = 11\nscheme@(guile-user)\u003e (inc 10)\n$2 = 11\nscheme@(guile-user)\u003e (inc 10)\n$3 = 11\n\n\n;; Грязная функция, которая при одном и том же аргументе, возвращает разные результаты:\n(define (rand-inc x)\n\t(+ (random 100) x))\n\nscheme@(guile-user)\u003e (rand-inc 1)\n$1 = 62\nscheme@(guile-user)\u003e (rand-inc 1)\n$2 = 52\nscheme@(guile-user)\u003e (rand-inc 1)\n$3 = 37\n```\n\n## Ссылочная прозрачность\nСсылочная прозрачность подразумевает, что любое подвыражение может быть заменено его значением в выражении в любое время без изменения всего выражения. В математике все функции являются ссылочно прозрачными согласно определению математической функции.\nТо есть к примеру `18 = square(4) + 2` может быть изменено на `18 = 16 + 2`.\nПоскольку прозрачность ссылок требует одинаковых результатов для любого заданного набора входных данных в любой момент времени, то ссылочно прозрачное выражение является детерминированным.\nСсылочная прозрачность возможна только при отсутствии побочных эффектов.\n\nПлюсы ссылочной прозрачности:\n- Возможность трансформации программы.\n- Каждое выражение может быть заменено его значением.\n- Возможности для оптимизации компилятора.\n- Программу с ссылочной прозрачностью легче объяснить, так как не приходиться волноваться о побочных эффектах.\n- Возможность для рефакторинга программы.\n\nПример на языке Python:\n```python\n# Ссылочная прозрачность \ndef square(x):\n\treturn x ** 2\n\t\n\u003e\u003e\u003e square(4) + 2 == 16 + 2\nTrue\n```\n\nПример на языке Scheme (GNU Guile):\n```scheme\n;; Ссылочная прозрачность\n(define (square x)\n\t(* x x))\n\t\nscheme@(guile-user)\u003e (= (+ (square 4) 2) (+ 16 2))\n$1 = #t\n```\n\n## Замыкание\nЗамыкание — это особый вид функции. Она определена в теле другой функции и создаётся каждый раз во время её выполнения. Синтаксически это выглядит как функция, находящаяся целиком в теле другой функции. При этом вложенная внутренняя функция содержит ссылки на локальные переменные внешней функции. Каждый раз при выполнении внешней функции происходит создание нового экземпляра внутренней функции, с новыми ссылками на переменные внешней функции. \n\nПример на языке Python:\n```python\n# Замыкание\ndef sum(x):\n    def sumXY(y):\n        return x + y\n    return sumXY\n\n\u003e\u003e\u003e s = sum(10)\n\u003e\u003e\u003e result = s(5)\n\u003e\u003e\u003e result\n15\n```\n\nПример на языке Scheme (GNU Guile):\n```scheme\n;; Замыкание\n(define (sum x)\n  (lambda (y)\n    (+ x y)))\n\n(define sum-x-y (sum 10))\n\nscheme@(guile-user)\u003e (sum-x-y 5)\n$1 = 15\n```\n\n## Каррирование\nКаррирование - это разложение функции с многими аргументами в цепочку функций с одним аргументом. Возможность такого преобразования впервые отмечена в трудах Готтлоба Фреге, систематически изучена Моисеем Шейнфинкелем в 1920-е годы, а наименование получило по имени Хаскелла Карри — разработчика комбинаторной логики, в которой сведение к функциям одного аргумента носит основополагающий характер. \nКаррированные функции могут принимать по одному аргументу за раз, а обычные функции должны принимать все аргументы сразу.\n\nПример:\n```\nОбычная функция:\nf(x, y) = x * x + y\nf(5, 10) = 5 * 5 + 10 = 25 + 10 = 35\nf(5, 10) = 35\n\nЭта же функция, преобразованная с помощью каррирования:\ng(x) = (x -\u003e y -\u003e x * x + y)\ng(5)(10) = (5 -\u003e 10 -\u003e 5 * 5 + 10) = 35\n\n(x -\u003e y -\u003e x * x + y) 5 10\n\t= (x -\u003e (y -\u003e x * x + y)) 5 10\n\t= ((x -\u003e (y -\u003e x * x + y)) 5) 10\n\t= (y -\u003e 5 * 5 + y) 10\n\t= 5 * 5 + 10\n\t= 35\n```\n\nПример на языке Python:\n```python\ndef f(x):\n\treturn lambda y: x * x + y\n\t\n\u003e\u003e\u003e f(5)(10)\n35\n```\n\nПример на языке Scheme (GNU Guile):\n```scheme\n(define (f x)\n\t(lambda (y) (+ (* x x) y)))\n\t\nscheme@(guile-user)\u003e ((f 5) 10)\n$1 = 35\n```\n\n## Частичное применение\nВозможность зафиксировать часть аргументов многоместной функции и создать другую функцию, меньшей арности.\nКогда функция вызывается с меньшим количеством аргументов, чем она ожидает, и она возвращает функцию, принимающую остальные аргументы. Это называется частичным применением функций.\nЧастично примененные функции не следует путать с каррированием.\n\nПримеры на языке Python:\n```python\nfrom functools import partial\n\n# Обычная функция принимающая 3 аргумента:\ndef f(x, y, z):\n    return (x + y + z) * 100\n\n\u003e\u003e\u003e f(2, 3, 4)\n900\n\n\n# Частичное применение этой же функции:\nfuncYZ = partial(f, 2) # x = 2\n\n\u003e\u003e\u003e funcYZ(3, 4) \n900\n\n\u003e\u003e\u003e partial(f, 2)(3, 4)\n900\n```\nПримеры на языке Scheme (GNU Guile):\n```scheme\n(define (partial fun . args)\n  (lambda x (apply fun (append args x))))\n\n\n;; Обычная функция принимающая 3 аргумента:\n(define (f x y z)\n  (* (+ x y z) 100))\n\n\n;; Частичное применение этой же функции:\n(define (funcYZ y z)\n  ((partial f 2) y z)) ;; x = 2\n\n\nscheme@(guile-user)\u003e (f 2 3 4)\n$1 = 900\nscheme@(guile-user)\u003e (funcYZ 3 4)\n$2 = 900\nscheme@(guile-user)\u003e ((partial f 2) 3 4)\n$3 = 900\n```\n\n## Хвостовая рекурсия и ее оптимизация\nЧастный случай рекурсии, при котором любой рекурсивный вызов является последней операцией перед возвратом из функции. Подобный вид рекурсии примечателен тем, что может быть легко заменён на итерацию путём формальной и гарантированно корректной перестройки кода функции.\n\nЧтобы понять хвостовую рекурсию нужно понимать, что такое хвостовой вызов. Хвостовой вызов - это вызов функции, который является последним действием в выполняемой функции.\n\nТиповой механизм реализации вызова функции основан на сохранении адреса возврата, параметров и локальных переменных функции в стеке и выглядит следующим образом: \n1. В точке вызова в стек помещаются параметры, передаваемые функции, и адрес возврата.\n2. Вызываемая функция в ходе работы размещает в стеке собственные локальные переменные.\n3. По завершении вычислений функция очищает стек от своих локальных переменных, записывает результат (обычно — в один из регистров процессора).\n4. Команда возврата из функции считывает из стека адрес возврата и выполняет переход по этому адресу. Либо непосредственно перед, либо сразу после возврата из функции стек очищается от параметров.\n\nТаким образом, при каждом рекурсивном вызове функции создаётся новый набор её параметров и локальных переменных, который вместе с адресом возврата размещается в стеке, что ограничивает максимальную глубину рекурсии объёмом стека. В чисто функциональных или декларативных (типа Пролога) языках, где рекурсия является единственным возможным способом организации повторяющихся вычислений, это ограничение становится крайне существенным, поскольку, фактически, превращается в ограничение на число итераций в любых циклических вычислениях, при превышении, которого будет происходить переполнение стека. \nПереполнение стека - компьютерная ошибка, когда в стеке вызовов храниться больше информации, чем он может вместить.\n\nНетрудно видеть, что необходимость расширения стека при рекурсивных вызовах диктуется требованием восстановления состояния вызывающего экземпляра функции (то есть её параметров, локальных данных и адреса возврата) после возврата из рекурсивного вызова. Но если рекурсивный вызов является последней операцией перед выходом из вызывающей функции и результатом вызывающей функции должен стать результат, который вернёт рекурсивный вызов, сохранение контекста уже не имеет значения — ни параметры, ни локальные переменные уже использоваться не будут, а адрес возврата уже находится в стеке. Поэтому в такой ситуации вместо полноценного рекурсивного вызова функции можно просто заменить значения параметров в стеке и передать управление на точку входа. До тех пор, пока исполнение будет идти по этой рекурсивной ветви, будет, фактически, выполняться обычный цикл. Когда рекурсия завершится (то есть исполнение пройдёт по терминальной ветви и достигнет команды возврата из функции) возврат произойдёт сразу в исходную точку, откуда произошёл вызов рекурсивной функции. Таким образом, при любой глубине рекурсии стек переполнен не будет.\n\nПримеры на языке Python:\n```python\n# Функция с хвостовым вызовом:\ndef f(x):\n\treturn tailCall(x * x) # Хвостовой вызов\n\n\n# Функция вычисляющая факториал, используя хвостовую рекурсию:\n# Промежуточные вычисления храняться в переменной `acc`.\ndef factorialTail(n, acc=1):\n\tif n == 0: return acc\n\telse: return factorialTail(n - 1, acc * n) # Хвостовой вызов\n\t\n\u003e\u003e\u003e factorialTail(5)\n120\n\u003e\u003e\u003e factorialTail(10)\n3628800\n\n\n# Функция вычисляющая факториал, без хвостовой рекурсии:\ndef factorial(n):\n\tif n == 0: return 1\n\telse: return factorial(n-1) * n # Не считается хвостовым вызовом, так как еще есть операция умножения\n  \n\u003e\u003e\u003e factorial(5)\n120\n\u003e\u003e\u003e factorial(10)\n3628800\n```\nПримеры на языке Scheme (GNU Guile):\n```scheme\n;; Функция вычисляющая факториал, используя хвостовую рекурсию:\n;; Промежуточные вычисления храняться в `acc`.\n(define (factorial-tail n)\n  (define (factorial-acc n acc)\n    (if (zero? n)\n\tacc\n\t(factorial-acc (- n 1) (* acc n))))\n  (factorial-acc n 1))\n  \nscheme@(guile-user)\u003e (factorial-tail 5)\n$1 = 120\nscheme@(guile-user)\u003e (factorial-tail 10)\n$2 = 3628800\n\n\n;; Функция вычисляющая факториал, без хвостовой рекурсии:\n(define (factorial n)\n  (if (= n 1) 1 (* (factorial(- n 1)) n))) ;; Не считается хвостовым вызовом, так как еще есть операция умножения\n\nscheme@(guile-user)\u003e (factorial 5)\n$3 = 120\nscheme@(guile-user)\u003e (factorial 10)\n$4 = 3628800\n\n\n;; Трассировка выполнения вышепредставленных функций:\n;; Можно видеть, что функция без хвостовой рекурсии занимает 10 вызовов в стеке.\nscheme@(guile-user)\u003e ,trace (factorial 10)\ntrace: (factorial 10)\ntrace: |  (factorial 9)\ntrace: |  |  (factorial 8)\ntrace: |  |  |  (factorial 7)\ntrace: |  |  |  |  (factorial 6)\ntrace: |  |  |  |  |  (factorial 5)\ntrace: |  |  |  |  |  |  (factorial 4)\ntrace: |  |  |  |  |  |  |  (factorial 3)\ntrace: |  |  |  |  |  |  |  |  (factorial 2)\ntrace: |  |  |  |  |  |  |  |  |  (factorial 1)\ntrace: |  |  |  |  |  |  |  |  |  1\ntrace: |  |  |  |  |  |  |  |  2\ntrace: |  |  |  |  |  |  |  6\ntrace: |  |  |  |  |  |  24\ntrace: |  |  |  |  |  120\ntrace: |  |  |  |  720\ntrace: |  |  |  5040\ntrace: |  |  40320\ntrace: |  362880\ntrace: 3628800\n;; А функция с хвостовой рекурсией выполняется как один вызов.\nscheme@(guile-user)\u003e ,trace (factorial-tail 10)\ntrace: (factorial-tail 10)\ntrace: 3628800\n```\nОптимизация хвостовых вызовов - это оптимизация, которая заменяет вызовы в хвостовых позициях переходами, что гарантирует выполнение циклов, реализованных с использованием рекурсии, в пространстве постоянного стека.\n\nЭто все замечательно, но в примере выше есть проблема, а именно то, что python не поддерживает оптимизацию хвостовых вызовов. Так как python скорее построен на идее итераций, чем на рекурсии. Но оптимизацию все же можно реализовать используя [декораторы](https://habr.com/post/158385/).\nЕсли язык программирования не поддерживает оптимизацию хвостовых вызовов, то выполнить безопасно рекурсию невозможно. Так как большое количество вызовов приведет к переполнению стека, и программа завершит свою работу с ошибкой.\n\nЯзыки программирования, которые поддерживают оптимизацию хвостовых вызовов:\n- Scheme\n- Haskell\n- Ocaml\n- Common Lisp\n- Erlang\n- Scala\n- F#\n- ...\n\n# Углубление в функциональное программирование\n\n- [Обзор основных функций высшего порядка](./higher-order-basic-functions.md)\n- [Ленивые вычисления](./lazy-evaluation.md)\n- [Предикаты](./predicate.md)\n- [Комбинаторы](./combinators.md)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FKikyTokamuro%2FSimple-Functional-Programming","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FKikyTokamuro%2FSimple-Functional-Programming","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FKikyTokamuro%2FSimple-Functional-Programming/lists"}