Home

Awesome

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 выполнен здесь.

Словарь сленга функционального программирования

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

Содержание

1. Функции

<!-- - https://www.haskell.org/tutorial/functions.html - https://wiki.haskell.org/Function -->

Определение функции

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 стиль записи

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)

<источник>

Лямбда-функция

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ылки (англ.яз.)

  1. Raul Rojas. A Tutorial Introduction to the Lambda Calculus
  2. Ben Lynn. Lambda Calculus
<!-- https://ncatlab.org/nlab/show/lambda-abstraction -->

Ccылки (рус.яз.)

  1. Денис Москвин. Лямбда-исчисление
  2. Антон Холомьёв. Лямбда-исчисление

Функция высшего порядка (ФВП)

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, который объединяет типы, выражающие числа.

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

2. Типы

Тип (данных)

Тип представляет собой набор возможных значений. Например, у типа 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.

Ссылки

Алгебраический тип данных

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

Тип-сумма (sum type)

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

Булевский тип данных "правда-ложь" является самым простым типом-суммой:

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, использующиеся для управлениями эффектами вычислений, также являются типами-суммой. <!-- Добавить ссылки на определния. -->

Тип-произведение (product type)

Тип-произведение объединяет элементы таким образом, что количество новых значений представляет собой произведение возможных количеств входящих значений.

В большинстве языков программирования есть тип кортеж (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

Ссылки:

<!-- ## Functor An object that implements a `map` function which, while running over each value in the object to produce a new object, adheres to two rules: __Preserves identity__ ``` object.map(x => x) ≍ object ``` __Composable__ ``` object.map(compose(f, g)) ≍ object.map(g).map(f) ``` (`f`, `g` are arbitrary functions) A common functor in JavaScript is `Array` since it abides to the two functor rules: ```js ;[1, 2, 3].map(x => x) // = [1, 2, 3] ``` and ```js const f = x => x + 1 const g = x => x * 2 ;[1, 2, 3].map(x => f(g(x))) // = [3, 5, 7] ;[1, 2, 3].map(g).map(f) // = [3, 5, 7] ``` ## Pointed Functor An object with an `of` function that puts _any_ single value into it. ES2015 adds `Array.of` making arrays a pointed functor. ```js Array.of(1) // [1] ``` ## Lifting Lifting is when you take a value and put it into an object like a [functor](#pointed-functor). If you lift a function into an [Applicative Functor](#applicative-functor) then you can make it work on values that are also in that functor. Some implementations have a function called `lift`, or `liftA2` to make it easier to run functions on functors. ```js const liftA2 = (f) => (a, b) => a.map(f).ap(b) // note it's `ap` and not `map`. const mult = a => b => a * b const liftedMult = liftA2(mult) // this function now works on functors like array liftedMult([1, 2], [3]) // [3, 6] liftA2(a => b => a + b)([1, 2], [3, 4]) // [4, 5, 5, 6] ``` Lifting a one-argument function and applying it does the same thing as `map`. ```js const increment = (x) => x + 1 lift(increment)([2]) // [3] ;[2].map(increment) // [3] ``` ## Monoid An object with a function that "combines" that object with another of the same type. One simple monoid is the addition of numbers: ```js 1 + 1 // 2 ``` In this case number is the object and `+` is the function. An "identity" value must also exist that when combined with a value doesn't change it. The identity value for addition is `0`. ```js 1 + 0 // 1 ``` It's also required that the grouping of operations will not affect the result (associativity): ```js 1 + (2 + 3) === (1 + 2) + 3 // true ``` Array concatenation also forms a monoid: ```js ;[1, 2].concat([3, 4]) // [1, 2, 3, 4] ``` The identity value is empty array `[]` ```js ;[1, 2].concat([]) // [1, 2] ``` If identity and compose functions are provided, functions themselves form a monoid: ```js const identity = (a) => a const compose = (f, g) => (x) => f(g(x)) ``` `foo` is any function that takes one argument. ``` compose(foo, identity) ≍ compose(identity, foo) ≍ foo ``` ## Monad A monad is an object with [`of`](#pointed-functor) and `chain` functions. `chain` is like [`map`](#functor) except it un-nests the resulting nested object. ```js // Implementation Array.prototype.chain = function (f) { return this.reduce((acc, it) => acc.concat(f(it)), []) } // Usage Array.of('cat,dog', 'fish,bird').chain((a) => a.split(',')) // ['cat', 'dog', 'fish', 'bird'] // Contrast to map Array.of('cat,dog', 'fish,bird').map((a) => a.split(',')) // [['cat', 'dog'], ['fish', 'bird']] ``` `of` is also known as `return` in other functional languages. `chain` is also known as `flatmap` and `bind` in other languages. ## Comonad An object that has `extract` and `extend` functions. ```js const CoIdentity = (v) => ({ val: v, extract () { return this.val }, extend (f) { return CoIdentity(f(this)) } }) ``` Extract takes a value out of a functor. ```js CoIdentity(1).extract() // 1 ``` Extend runs a function on the comonad. The function should return the same type as the comonad. ```js CoIdentity(1).extend((co) => co.extract() + 1) // CoIdentity(2) ``` ## Applicative Functor An applicative functor is an object with an `ap` function. `ap` applies a function in the object to a value in another object of the same type. ```js // Implementation Array.prototype.ap = function (xs) { return this.reduce((acc, f) => acc.concat(xs.map(f)), []) } // Example usage ;[(a) => a + 1].ap([1]) // [2] ``` This is useful if you have two objects and you want to apply a binary function to their contents. ```js // Arrays that you want to combine const arg1 = [1, 3] const arg2 = [4, 5] // combining function - must be curried for this to work const add = (x) => (y) => x + y const partiallyAppliedAdds = [add].ap(arg1) // [(y) => 1 + y, (y) => 3 + y] ``` This gives you an array of functions that you can call `ap` on to get the result: ```js partiallyAppliedAdds.ap(arg2) // [5, 6, 7, 8] ``` ## Setoid An object that has an `equals` function which can be used to compare other objects of the same type. Make array a setoid: ```js Array.prototype.equals = function (arr) { const len = this.length if (len !== arr.length) { return false } for (let i = 0; i < len; i++) { if (this[i] !== arr[i]) { return false } } return true } ;[1, 2].equals([1, 2]) // true ;[1, 2].equals([0]) // false ``` ## Semigroup An object that has a `concat` function that combines it with another object of the same type. ```js ;[1].concat([2]) // [1, 2] ``` ## Foldable An object that has a `reduce` function that applies a function against an accumulator and each element in the array (from left to right) to reduce it to a single value. ```js const sum = (list) => list.reduce((acc, val) => acc + val, 0) sum([1, 2, 3]) // 6 ``` ## Lens ## A lens is a structure (often an object or function) that pairs a getter and a non-mutating setter for some other data structure. ```js // Using [Ramda's lens](http://ramdajs.com/docs/#lens) const nameLens = R.lens( // getter for name property on an object (obj) => obj.name, // setter for name property (val, obj) => Object.assign({}, obj, {name: val}) ) ``` Having the pair of get and set for a given data structure enables a few key features. ```js const person = {name: 'Gertrude Blanch'} // invoke the getter R.view(nameLens, person) // 'Gertrude Blanch' // invoke the setter R.set(nameLens, 'Shafi Goldwasser', person) // {name: 'Shafi Goldwasser'} // run a function on the value in the structure R.over(nameLens, uppercase, person) // {name: 'GERTRUDE BLANCH'} ``` Lenses are also composable. This allows easy immutable updates to deeply nested data. ```js // This lens focuses on the first item in a non-empty array const firstLens = R.lens( // get first item in array xs => xs[0], // non-mutating setter for first item in array (val, [__, ...xs]) => [val, ...xs] ) const people = [{name: 'Gertrude Blanch'}, {name: 'Shafi Goldwasser'}] // Despite what you may assume, lenses compose left-to-right. R.over(compose(firstLens, nameLens), uppercase, people) // [{'name': 'GERTRUDE BLANCH'}, {'name': 'Shafi Goldwasser'}] ``` Other implementations: * [partial.lenses](https://github.com/calmm-js/partial.lenses) - Tasty syntax sugar and a lot of powerful features * [nanoscope](http://www.kovach.me/nanoscope/) - Fluent-interface -->

3. Общие понятия

Cсылочная прозрачность (referential transparency)

Выражение, которое можно заменить на его значение без изменения поведения программы, обладает свойством ссылочной прозрачности.

Cсылочная прозрачность упрощает понимание и изменение кода программ.

Ссылки:

Эквациональное рассуждение (equational reasoning)

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

Упорощенно, эквациональное рассуждение - это процесс интерпретации или доказательства свойств программы путем подстановки равных выражений.

Примечание: перевод термина дан по справочнику ruhaskell, но на практике русским термином пользуются редко, чаще употребляется в английском написании.

Ссылки:

<!-- ## Category A category in category theory is a collection of objects and morphisms between them. In programming, typically types act as the objects and functions as morphisms. To be a valid category 3 rules must be met: 1. There must be an identity morphism that maps an object to itself. Where `a` is an object in some category, there must be a function from `a -> a`. 2. Morphisms must compose. Where `a`, `b`, and `c` are objects in some category, and `f` is a morphism from `a -> b`, and `g` is a morphism from `b -> c`; `g(f(x))` must be equivalent to `(g • f)(x)`. 3. Composition must be associative `f • (g • h)` is the same as `(f • g) • h` Since these rules govern composition at very abstract level, category theory is great at uncovering new ways of composing things. __Ссылки__ * [Category Theory for Programmers](https://bartoszmilewski.com/2014/10/28/category-theory-for-programmers-the-preface/) -->

Выражение

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]

Литература

Другие англоязычные словари и глоссарии

Книги и сайты на русском языке

Выступления

<blockquote class="twitter-tweet"><p lang="en" dir="ltr">I&#39;ve been asked about the famous talk of Rich Hickey &quot;Simple made Easy&quot; (aka &quot;Simplicity Matters&quot;).<br><br>Absolutely a must-watch for every developer who wants to call himself a qualified senior or architect.<a href="https://t.co/ouFIdjCOxC">https://t.co/ouFIdjCOxC</a> <a href="https://t.co/HFAZkoXx0s">https://t.co/HFAZkoXx0s</a></p>&mdash; Alexander Granin (@graninas) <a href="https://twitter.com/graninas/status/1217328045729730561?ref_src=twsrc%5Etfw">January 15, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

(infoq transcript)

Развитие словаря

Что можно сделать в этом словаре лучше? Конечно, это решать читателям. Вы можете дать комментарий в issues этого проекта, или написать переводчику.