Функциональное программирование связано не столько с шаблонами, сколько с законами.Законы позволяют программисту рассуждать о своих программах, как математик может рассуждать об уравнении.
Давайте посмотрим на сложение чисел.Добавление представляет собой двоичную операцию (она занимает два числа) и всегда выдает другое число.
1 + 2 = 3
2 + 1 = 3
1 + (2 + 3) = 6
(1 + 2) + 3 = 6
((1 + 2) + 3) + 4 = 10
(1 + 2) + (3 + 4) = 10
1 + (2 + 3) + 4 = 10
1 + (2 + (3 + 4)) = 10
Мы можем добавлять числа в любомзаказать и все равно получить тот же результат.Это свойство ассоциативности и оно составляет основу ассоциативного закона .
Добавление нуля несколько интересно или воспринимается как должное.
1 + 0 = 1
0 + 1 = 1
3 + 0 = 3
0 + 3= 3
Добавление нуля к любому числу не изменит число.Это известно как элемент идентификации .
Эти две вещи ( 1 ) - ассоциативная двоичная операция и ( 2 )элемент идентификации, составляют моноид .
Если мы можем ...
- кодировать ваши предикаты как элементы домена
- создайте двоичную операцию для элементов
- , определите элемент идентификации
... тогда мы получим преимущества принадлежности к категории моноидов, что позволяет нам рассуждать о нашей программе в уравнительный способ.Там нет образца, чтобы учиться, только законы для соблюдения.
1.Создание домена
Получить правильные данные непросто, тем более на многопарадигмальном языке, таком как JavaScript.Этот вопрос касается функционального программирования, хотя functions было бы неплохо.
В вашей программе ...
build() {
var fnPredicate = (p) => true;
if (typeof config.minIncome == 'number') {
fnPredicate = (p) => fnPredicate(p) && config.minIncome <= p.income;
}
if (typeof config.member == 'boolean') {
fnPredicate = (p) => fnPredicate(p) && config.member === p.member;
}
// .. continue to support more predicateparts.
},
... мы видимсмесь уровня программы и уровня данных.Эта программа жестко запрограммирована для понимания только ввода, который может иметь эти конкретные ключи (minIncome
, member
) и их соответствующие типы (number
и boolean
), а также операцию сравнения, используемую для определения предиката.
Давайте сделаем это по-настоящему простым.Давайте возьмем статический предикат
item.name === "Sally"
Если бы я хотел этот же предикат, но сравнивал с использованием другого элемента, я бы обернул это выражение в функцию и сделал бы item
параметром функции.1108 *
const nameIsSally = item =>
item.name === "Sally"
console .log
( nameIsSally ({ name: "Alice" }) // false
, nameIsSally ({ name: "Sally" }) // true
, nameIsSally ({ name: "NotSally" }) // false
, nameIsSally ({}) // false
)
Этот предикат прост в использовании, но он работает только для проверки имени Салли .Мы повторяем процесс, оборачивая выражение в функцию, и делаем name
параметром функции.Эта общая техника называется абстракция и постоянно используется в функциональном программировании.
const nameIs = name => item =>
item.name === name
const nameIsSally =
nameIs ("Sally")
const nameIsAlice =
nameIs ("Alice")
console .log
( nameIsSally ({ name: "Alice" }) // false
, nameIsSally ({ name: "Sally" }) // true
, nameIsAlice ({ name: "Alice" }) // true
, nameIsAlice ({ name: "Sally" }) // false
)
Как видите, не имеет значения, что выражение, которое мы завернули, уже было функцией.JavaScript имеет первоклассную поддержку функций, что означает, что они могут рассматриваться как значения.Программы, которые возвращают функцию или получают функцию в качестве входных данных, называются функциями высшего порядка .
Выше наши предикаты представлены в виде функций, которые принимают значение любого типа ( a) и выдает логическое значение .Мы будем обозначать это как a -> Boolean
.Таким образом, каждый предикат является элементом нашего домена, и этот домен является всеми функциями a -> Boolean
.
2.Двоичная операция
Мы проведем упражнение абстракции еще раз.Давайте возьмем статическое комбинированное предикатное выражение.
p1 (item) && p2 (item)
Я могу повторно использовать это выражение для других элементов, обернув его в функцию и сделав item
параметром функции.
const bothPredicates = item =>
p1 (item) && p2 (item)
Но мы хотим объединить любые предикаты .Опять же, мы оборачиваем выражение, которое хотим повторно использовать в функции, затем присваиваем параметр (ы) для переменной (переменных), на этот раз для p1
и p2
.
const and = (p1, p2) => item =>
p1 (item) && p2 (item)
Прежде чем мыДавайте проверим наш домен и убедимся, что наша двоичная операция and
выполнена правильно.Двоичная операция должна:
- принимать в качестве входных данных два (2) элемента из нашего домена (
a -> Boolean
) - возвращать в качестве вывода элемент нашего домена
- операция должна быть ассоциативной: f (a, b) == f (b, a)
Действительно, and
принимает два элементанаш домен p1
и p2
.Возвращаемое значение - item => ...
, которое является функцией, получающей item
и возвращающей p1 (item) && p2 (item)
.Каждый из них является предикатом, который принимает одно значение и возвращает логическое значение.Это упрощает до Boolean && Boolean
, что, как мы знаем, является еще одним логическим значением.Подводя итог, можно сказать, что and
принимает два предиката и возвращает новый предикат, что и должно делать двоичная операция.
const and = (p1, p2) => item =>
p1 (item) && p2 (item)
const nameIs = x => item =>
item.name === x
const minIncome = x => item =>
x <= item.income
const query =
and
( nameIs ("Alice")
, minIncome (5)
)
console .log
( query ({ name: "Sally", income: 3}) // false
, query ({ name: "Alice", income: 3 }) // false
, query ({ name: "Alice", income: 7 }) // true
)
3.Элемент идентификации
Элемент идентификации при добавлении к любому другому элементу не должен изменять элемент.Таким образом, для любого предиката p
и элемента идентификации предиката empty
, следующее должно содержать
и (p, пусто) == p
и (пусто), p) == p
Мы можем представить пустой предикат как функцию, которая принимает любой элемент, и всегда возвращает true
.
const and = (p1, p2) => item =>
p1 (item) && p2 (item)
const empty = item =>
true
const p = x =>
x > 5
console .log
( and (p, empty) (3) === p (3) // true
, and (empty, p) (3) === p (3) // true
)
Сила законов
Теперь, когда у нас есть двоичная операция и элемент тождества, мы можем объединить произвольныйколичество предикатов.Мы определяем sum
, который подключает наш моноид непосредственно к reduce
.
// --- predicate monoid ---
const and = (p1, p2) => item =>
p1 (item) && p2 (item)
const empty = item =>
true
const sum = (...predicates) =>
predicates .reduce (and, empty) // [1,2,3,4] .reduce (add, 0)
// --- individual predicates ---
const nameIs = x => item =>
item.name === x
const minIncome = x => item =>
x <= item.income
const isTeenager = item =>
item.age > 12 && item.age < 20
// --- demo ---
const query =
sum
( nameIs ("Alice")
, minIncome (5)
, isTeenager
)
console .log
( query ({ name: "Sally", income: 8, age: 14 }) // false
, query ({ name: "Alice", income: 3, age: 21 }) // false
, query ({ name: "Alice", income: 7, age: 29 }) // false
, query ({ name: "Alice", income: 9, age: 17 }) // true
)
Предикат пустой суммы по-прежнему возвращает действительный результат.Это похоже на пустой запрос, который соответствует всем результатам.
const query =
sum ()
console .log
( query ({ foo: "bar" }) // true
)
Свободное удобство
Использование функций для кодирования наших предикатов делает их полезными и для других целей.,Если у вас есть массив элементов, вы можете использовать предикат p
непосредственно в .find
или .filter
.Конечно, это верно для предикатов, созданных с использованием and
и sum
тоже.
const p =
sum (pred1, pred2, pred3, ...)
const items =
[ { name: "Alice" ... }
, { name: "Sally" ... }
]
const firstMatch =
items .find (p)
const allMatches =
items .filter (p)
Сделать это модулем
Вы не делаетехотите определить глобальные переменные типа add
и sum
и empty
.Когда вы упаковываете этот код, используйте какой-то модуль.
// Predicate.js
const add = ...
const empty = ...
const sum = ...
const Predicate =
{ add, empty, sum }
export default Predicate
Когда вы используете его
import { sum } from './Predicate'
const query =
sum (...)
const result =
arrayOfPersons .filter (query)
Викторина
Обратите внимание на сходство между нашим элементом идентификации предиката и элементом идентификации для &&
T &&?== T
?&& T == T
F &&?== F
?&& F == F
Мы можем заменить все ?
выше на T
, и уравнения будут выполнены.Ниже, как вы думаете, элемент идентичности для ||
?
T ||?== T
?||T == T
F ||?== F
?||F == F
Какой элемент идентичности для *
, двоичное умножение?
n *?= n
?* n = n
Как насчет элемента идентификации для массивов или списков?
concat (l,?) == l
concat (?, l) == l
Весело?
Я думаю, вам понравятся контравариантные функторы .На той же арене преобразователи .Есть демонстрация, показывающая, как построить высокоуровневый API на основе этих низкоуровневых модулей.