Импликация и эквивалентность: если-то, вакуумная истинность и guard clauses
p → q ложно ровно в одном случае: p истинно, q ложно. Ложное p делает правило вакуумно истинным. Обратное и противоположное НЕ равносильны импликации — равносильна контрапозиция, и каждый guard clause и ранний return в коде — это рассуждение по контрапозиции.
В валидаторе чекаута была строка, выглядевшая ленивой: if (order.coupon) assertValid(order.coupon). Новичок прочёл бизнес-правило — «купон каждого заказа должен быть валиден» — и решил, что в коде дыра: заказы без купона вообще не проверяются! «Исправление» стало валить валидацию всякий раз, когда купона нет. Около 80% заказов идут без купона; выручка чекаута легла на сорок минут. Исходный код был правильным. Бизнес-правило — это импликация: ЕСЛИ у заказа есть купон, ТО он должен быть валиден. А импликация, у которой часть «если» ложна, автоматически считается истинной. Заказ без купона удовлетворяет правилу, ничего не делая. Это называется вакуумной истинностью, и она тихо лежит в основе ваших вызовов every(), guard clauses и половины ранних return.
После этого урока ты умеешь читать таблицу истинности p → q, объяснять вакуумную истинность и где она встречается в методах массива, определять контрапозицию (и почему только она равносильна импликации) и объяснять, почему guard clauses — это контрапозиция в коде.
Импликация p → q ложна ровно в одной строке: p истинно и q ложно. Импликация p → q читается «если p, то q». У её таблицы форма, которую стоит запомнить: ложь ровно в одной строке.
p | q | p → q |
|---|---|---|
| И | И | И |
| И | Л | Л |
| Л | И | И |
| Л | Л | И |
Лучшая ментальная модель — обещание: «Если сдашь экзамен, куплю мороженое». Когда я нарушил слово? Только когда ты сдал, а мороженого нет — p истинно, q ложно. Ты сдал и получил: обещание сдержано. Не сдал, но мороженое всё равно купил: про этот случай я ничего не обещал. Не сдал, мороженого нет: тоже не нарушено.
При ложном p импликация вакуумно истинна. Две нижние строки говорят: когда p ложно, p → q истинно при любом q. Это вакуумная истинность — правило соблюдено, потому что его ни разу не проверили делом. «Если у заказа есть купон, он должен быть валиден» выполняется заказом без купона. Стандартная библиотека построена на этом: [].every(n => n > 0) возвращает true, потому что «для всех элементов n > 0» не имеет элемента, который мог бы нарушить условие. Код вида if (items.every(ok)) proceed() продолжит работу на пустом списке — обычно правильно, но это должно быть осознанным решением.
Импликация компилируется в !p || q в коде. Раз p → q ложна только при истинном p и ложном q, она эквивалентна !p || q:
// Правило: если у заказа есть купон, он должен быть валиден.
const satisfiesRule = (order) => !order.coupon || isValid(order.coupon);Заказ без купона: !false → true — правило выполнено вакуумно. Заказ с купоном: !true || isValid(...) — вычисляется isValid. Усилить до order.coupon && isValid(...) — потребовать, чтобы купон существовал, — это другое высказывание, и именно этот баг внёс новичок.
Равносильна только контрапозиция; обратное и противоположное — ловушки. Из p → q строятся три родственных утверждения:
- Обратное (converse):
q → p— стрелка развёрнута. Не равносильно. - Противоположное (inverse):
!p → !q— обе стороны отрицены. Не равносильно (то же, что обратное). - Контрапозиция (contrapositive):
!q → !p— развёрнуто и отрицено. Равносильно — та же таблица.
Guard clauses используют это равносильство. Правило «если мы обрабатываем запрос, он должен быть аутентифицирован» (process → authed) имеет контрапозицию «если не аутентифицирован — не обрабатывать» — буквально if (!req.authed) return reject(req). Каждый ранний return исполняет одну контрапозицию, и то, что доживает до низа функции, удовлетворяет всем правилам.
Перепишите правило «если пользователь премиум, он не видит рекламу» как guard clause через контрапозицию.
Правило: premium → noAds
Контрапозиция: !noAds → !premium, то есть «если пользователь видит рекламу, он не премиум».
Как guard clause в пути рендеринга:
function renderPage(user, content) {
if (user.seesAds && user.isPremium) {
throw new Error("Нарушен инвариант: премиум-пользователь не должен видеть рекламу");
}
// ... остальная логика рендеринга
}Или на уровне контроля доступа:
function showPremiumBadge(user) {
if (!user.isPremium) return null; // guard clause — контрапозиция
return <PremiumBadge />;
}Заметьте, что не равносильно: вывод «если не видит рекламу, значит премиум» (обратное). Бесплатный триал тоже может прятать рекламу, поэтому такой вывод неверен.
▸Почему это работает
Почему «из лжи следует что угодно» определено как истина, а не ложь или неопределённость? Потому что универсальные утверждения должны переживать нерелевантные случаи. «Каждое кратное четырёх чётно» — очевидно верно. Формально: если x кратно 4, то x чётно, для всех x. Проверим x = 3: антецедент ложен. Если бы ложный антецедент делал импликацию ложной, x = 3 опровергал бы верную теорему о кратных четырёх — абсурд. Считать непроверенный случай истинным — единственный выбор, позволяющий правилам квантифицировать по неопрятному миру, отвечая лишь там, где они реально применимы.
p → q, где p = И и q = Л. Импликация И или Л?
p → q, где p = Л и q = Л. Импликация И или Л?
[].every(n => n > 0) в JavaScript — true или false?
Что равносильно p → q: (а) !p || q, или (б) p && q?
Какова контрапозиция утверждения «если идёт дождь, земля мокрая»?
Какое утверждение логически ЭКВИВАЛЕНТНО правилу «если пользователь премиум, он не видит рекламу»?
Импликация p → q — обещание, ложное ровно в одной строке таблицы истинности: антецедент истинен, консеквент ложен — условие выполнено, обещание нарушено. Она не утверждает причинности, только ограничение на значения истинности, и потому компилируется в !p || q. При ложном p импликация вакуумно истинна: правило не проверялось, значит, стоит — пустой массив проходит every(), заказ без купона удовлетворяет «если есть купон, он валиден». Усиление импликации до конъюнкции — другое, куда более сильное высказывание; разница — ровно все случаи, где p не выполняется. Из p → q строятся обратное q → p, противоположное !p → !q и контрапозиция !q → !p; равносильна лишь контрапозиция. Guard clauses обналичивают это равносильство: «если обрабатываем, то аутентифицирован» становится ранним выходом «если не аутентифицирован — отклонить», и стопка охранников оставляет низ функции в состоянии, где доказуемо выполняются все правила. Полная эквивалентность p ↔ q — лицензия свободно подставлять условия.
Практика
Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.
Примени это
Примени этот урок в реальном проекте.