This is an abridged and modified Russian translation of Functional Programming Jargon with examples in Haskell. Some code examples borrowed from Turkish translation.
Эта статья - сокращенный перевод и переработка публикации Functional Programming Jargon с примерами на Нaskell, часть из которых взята из турецкой версии статьи. Перевод оригинальной статьи с примерами на JavaScript выполнен здесь.
У функционального программирования (ФП) есть много преимуществ, и как результат, его популярность растет. При этом у любой парадигмы программирования есть своя терминология и жаргон, и ФП - не исключение. С помощью этого словаря мы надеемся упростить задачу изучения ФП.
Содержание
Function
Функция f :: X -> Y
каждому элементу x
типа X
сопоставляет элемент f x
типа Y
.
x
называется аргументом функции, а f x
- значением функции.
Функции, которые соответствуют данному определению, являются:
Результат функции полностью зависит только от аргумента, что делает функции независимыми от контекста, в котором они исполняются. Это делает функции удобными для тестирования и переиспользования.
Примечание: в программировании функцией может называться и та последовательность операций, которая приводит к побочным эффектам (записи на диск, проведению ввода-вывода, изменению глобальных переменных). Чтобы отличить их от функций в данном определении, такие операции можно называть процедурами.
Создание из двух функций f(x)
и g(x)
третьей функции h
, результатом
которой является применение функции f
к g(x)
: h(x) = f(g(x))
.
В Haskell композиция функций производится оператором .
(точка). Такая запись
в point-free форме более лаконична, чем запись с аргументом:
-- Предположим, у нас определены две функции
-- со следующими подписями
even :: Int -> Bool
not :: Bool -> Bool
-- Давайте сдлаем новую функцию, которая проверяет
-- является ли значение нечетным
myOdd :: Int -> Bool
myOdd x = not (even x)
-- ... или сделаем то же самое с использованием оператора .
myOdd :: Int -> Bool
myOdd = not . even
-- Функция desort создается путем комбинации
-- сортировки и перечисления списка в обратном порядке
desort = reverse . sort
Point-free style
Способ описать функцию без обозначения ее аргументов в явном виде.
Сравните:
sum = foldr (+) 0
и
sum' xs = foldr (+) 0 xs
Обе фукнции выполняют одно и то же действие суммирования, однако запись в point-free стиле считается более лаконичной и будет содержать меньше ошибок. Однако запись более сложных функций в point-free стиле может затруднять понимание логики вычислений.
Point-free стиль также иногда называется бесточечный стиль.
In Haskell you can simplify function definitions by η-reducing them. For example, instead of writing:
f x = (some expresion) x
you can simply write
f = (some expression)
[<источник>](https://her.esy.fun/posts/0010-Haskell-Now/)источник>
Lambda
Неименованная, анонимная функция.
\x -> x + 1
Анонимные функции часто используются с функциями высшего порядка.
Prelude> map (\x -> x + 1) [1..4]
[2,3,4,5]
-- хотя этот пример можно записать и без лямбды
map (+1) [1..4]
Название дано по 11-й букве греческого алфавита λ
(лямбда).
Примечание: исходя из более строгой терминологии, принятой в лямбда-исчислении, широко распространенные “лямбда” и “лямбда-функция” - это не совсем точные, сленговые выражения:
Для того, чтобы определить функцию, не обязательно задавать её имя. Для этого можно воспользоваться лямбда-абстракцией
\x -> x + 1
. Такие функции будем называть анонимными.
Lambda calculus
Раздел математики, в котором формализовано и анализируется понятие вычислимости.
Лямбда-исчисление также можно рассматривать как компактный универсальный язык программирования, c помощью которого может быть определена и рассчитана любая вычислимая функция. [1]
Большинство функциональных языков программирования построены на той или иной версии лямбда-исчисления.
Ccылки (англ.яз.)
Ccылки (рус.яз.)
Higher-Order Function (HOF)
Функция, которая принимает другую функцию как аргумент и/или возвращает функцию как результат.
Prelude> let add3 a = a + 3
Prelude> map add3 [1..4]
[4,5,6,7]
Prelude> filter (<4) [1..10]
[1,2,3]
Partial function
Частичная функция - это функция, для которой нарушается свойство тотальности. Частичная функция недоопределена: существуют значения аргумента, для которых частичная функция не может вычислить результат или не закончит свое исполнение.
Частичные функции запутывают анализ программы и могут приводить к ошибкам ее исполнения.
Пример запроса несуществующего элемента списка:
[1,2,3] !! 5
Для устранения частичных функций могут применяться следующие приемы:
Подробнее см. например здесь.
Purity
Функция является чистой, если ее значение определяется только значением аргумента и если она не производит побочных эффектов.
Все функции в языке Haskell являются чистыми. Настолько чистыми, что для того, чтобы получить побочный эффект в виде записи на диск или вывода на экран надо еще очень постараться.
Side effects
У функции или выражения есть побочный эффект помимо вычисления значения, если она взаимодействует (осуществляет чтение или запись) во внешние изменяемое состояние.
Таких фукнций на Haskell нет, примеры на JavaScript:
const differentEveryTime = new Date()
console.log('IO is a side effect!')
Arity
Количество аргументов, которое принимает функция (унарная, бинарная и т.д.)
Prelude> let sum a b = a + b
Prelude> :t sum
sum :: Num a => a -> a -> a
-- Арность функции sum равна 2
Currying
Преобразование функции, которая принимает несколько аргументов, в функцию, которая принимает один аргумент и возвращает функцию, которая далее применяентся к последующим аргументам.
В отличие от других языков программирования функции многих аргументов в Haskell каррированы по умолчанию. Это значит, что частичное применение (см. ниже) доступно для всех функций многих аргументов и не требует специальных действий.
В примере ниже функция sum
обрабатывает кортеж из двух значений (a, b)
. Функция
curry
преобразует функцию sum
в curriedSum
, которая последовательно принимает
два аргумента a
и b
.
Prelude> let sum (a, b) = a + b
Prelude> let curriedSum = curry sum
Prelude> curriedSum 40 2
42
Partial Application
Вызов функции с меньшим количеством аргументов, чем необходимо для ее завершения. В этом случае создается новая функция, которая будет обрабатывать оставшиеся аргументы.
Частичное применение позволяет из более общих или сложных функций получать более простые функции с необходимым специфическим поведением.
-- Создаем функцию сложения двух элементов
Prelude> let add x y = x + y
-- Создадим функцию для увеличения аргумента на единицу.
-- Частично применим функцию add к значению 1.
-- В результате получилась новая функция,
-- которой мы присвоили имя inc
Prelude> let inc = add 1
Prelude> inc 8
Prelude> 9
Предупреждение. Частичное применение не следует путать с частичной функцией - это похожие названия, означающие разные вещи.
Ссылки
Прим. переводчика: термины, которые собраны в этом разделе, не показались мне фундаментальными или интересными. Здесь вы найдете их краткое определение, но заучивать или сильно вникать в них, на мой взгляд, не нужно.
Indepotent
Функция является идемпотентной, если ее повторное применение не влияет на исходный результат:
f(f(x)) ≍ f(x)
Prelude> abs (abs (-1))
1
Prelude Data.List> sort (sort [1,4,3,1,5])
[1,1,3,4,5]
Predicate
Функция, возвращающая значение правда или ложь. Обычно используется для фильтрации последовательности значений по какому-либо признаку.
Prelude> let predThree a = a < 3
Prelude> filter predThree [1..10]
[1,2]
Closure
Использование доступных для функции переменных, помимо непосредственно переданных ей аргументов.
В других языках программирования замыкания играют важную роль, например, в функциях-конструктуорах, которые используют собственные параметры для создания других функций.
Поскольку Haskell основан на лямбда-исчислении, замыкания используются в нем естественным образом и не являются чем-то особенным.
Вымученный пример замыкания на Haskell:
-- Переменная x не является аргументом лямбда-функции,
-- но доступна внутри тела функции f
f x = (\y -> x + y)
-- привычная запись на Haskell
f x y = x + y
Примечание: функция-комбинатор, в отличие от замыкания, использует только переданные ей аргументы.
Type signatures
Подпись, или аннотация, типа - это строка особого формата, которая показывает тип переменной или функции.
В Haskell подпись типа может задаваться напрямую в коде. Например, после определения inc :: Int -> Int
компилятор будет ожидать, что под именем inc
будет определена функция, которая принимает значение типа Int
и выдает результат также типа Int
. Если под именем
inc
будет определна функция с другим поведением, компилятор выдаст ошибку.
В отсутствие заданой подписи типа компилятор сам выводит ее наиболее общий вид:
Prelude> inc x = x + 1
Prelude> :t inc
inc :: Num a => a -> a
inc :: Num a => a -> a
- это подпись, выведенная компилятором. Она говорит о том,
что имя inc
- это функция, которая принимает значение некоторого типа а
и
выдает значение того же типа a
, при этом сам тип а
- принадлежит классу
типов Num
, который объединяет типы, выражающие числа.
В других языках программирования аннотации типов могут иметь справочную функцию и не проверяться компилятором.
Тип представляет собой набор возможных значений. Например, у типа Bool
есть
два значения True
и False
. Тип Int
включает в себя все целочисленные значения.
Типы бывают простые и составные. Например, мы можем создать новый составной тип данных Point
,объявив, что он состоит из двух значений типа Float
.
data Point = Point Float Float
Термины тип и тип данных взаимозаменяемы.
В функциональном программировании типы позволяют точно описывать с какими значениями работают функции. Использование типов повышает гарантии корректности исполнения программ. Например, получив значение непредусмотренного для функции типа компилятор выдаст сообщение об ошибке.
Цитата (источник):
… why we should care about types at all: what are they even for? At the lowest level, a computer is concerned with bytes, with barely any additional structure. What a type system gives us is abstraction. A type adds meaning to plain bytes: it lets us say “these bytes are text”, “those bytes are an airline reservation”, and so on. Usually, a type system goes beyond this to prevent us from accidentally mixing types up: for example, a type system usually won’t let us treat a hotel reservation as a car rental receipt.
Ссылки
Составной тип, который получается путем из соединения нескольких других типов. Соединение типов называется алгеброй, что повлияло на название термина. Чаще всего рассматриваются тип-сумма и тип-произведение.
Тип-сумма - это комбинация двух и более типов в новый тип таким образом, что число возможных значений в новом типе будет соответствовать сумме входящих элементов.
Булевский тип данных “правда-ложь” является самым простым типом-суммой:
data Bool = False | True
Три цвета светофора также тип-сумма:
data TrafficLight = Red | Yellow | Green
В примерах выше тип-сумма построен из простейших элементов, но эти элементы могут быть и более сложными.
Тип Move
описывает движение робота по прямой с целочисленными шагами вперед, назад или с остановкой.
data Move = Stop | Ahead Int | Back Int
Шаги робота теперь можно описать с помощью списка типа [Move]
, например, [Ahead 1, Stop, Stop, Back -2]
(шаг вперед, два назад).
Типы Maybe
и Either
, использующиеся для управлениями эффектами вычислений, также являются типами-суммой.
Тип-произведение объединяет элементы таким образом, что количество новых значений представляет собой произведение возможных количеств входящих значений.
В большинстве языков программирования есть тип кортеж (tuple), который является самым простым типом-произведением. Например, кортеж из трех булевых значений типа (Bool, Bool, Bool)
имеет 2*2*2 = 8 значений.
Привычные структры данных, такие как записи с полями значений также являются типами-произведением.
data Person = Person {name:String , age::Int}
Person "LittleBaby" 2
Место точки на плоскости соcтоит их двух координат, которые можно выразить типом-произведением двух значений типа Float
:
data Position = Position Float Float
Position 1.5 2.8
Ссылки:
Выражение, которое можно заменить на его значение без изменения поведения программы, обладает свойством ссылочной прозрачности.
Cсылочная прозрачность упрощает понимание и изменение кода программ.
Ссылки:
В случае, если программа состоит из выражений и в ней отсуствуют побочные эффекты, истина о состоянии системы может быть определена из составных частей программы.
Упорощенно, эквациональное рассуждение - это процесс интерпретации или доказательства свойств программы путем подстановки равных выражений.
Примечание: перевод термина дан по справочнику ruhaskell, но на практике русским термином пользуются редко, чаще употребляется в английском написании.
Ссылки:
Expression
Допустимый синтаксисом языка программирования набор переменных, операторов и функций, возвращающий значение.
Примеры выражений: 2+3
, fst ("this_"++"a", "that_"++"b")
, fmap (+1) (Just 5)
.
Value
1) Результат вычисления выражения.
2) Все, что можно присвоить переменной.
Примеры значений: 5
, "this_a"
, Just 6
.
Lazy evaluation
Механизм откладывания вычислений до момента, когда результат вычислений необходим для продолжения исполнения программы.
В функциональных языках ленивые вычисления позволяют, например, использовать такие структуры данных как бесконечные списки:
Prelude> let xs = [1..]
Prelude> take 5 xs
[1,2,3,4,5]
I've been asked about the famous talk of Rich Hickey "Simple made Easy" (aka "Simplicity Matters").
— Alexander Granin (@graninas) January 15, 2020
Absolutely a must-watch for every developer who wants to call himself a qualified senior or architect.https://t.co/ouFIdjCOxC https://t.co/HFAZkoXx0s
Что можно сделать в этом словаре лучше? Конечно, это решать читателям. Вы можете дать комментарий в issues этого проекта, или написать переводчику.