Автор оригинала: FreeCodeCamp Community Member.
Ник Гард
Я инженер-интерфейс и математик. Я полагаюсь на мою математическую подготовку ежедневно в письменном виде. Это не статистика или исчисление, которое я использую, но, скорее, моего тщательного понимания логической логики. Часто я превратил сложное сочетание амперсандов, труб, восклицательных знаков и равен подписи во что-то проще и гораздо более читаемое. Я хотел бы поделиться этим знанием, поэтому я написал эту статью. Это долго Но я надеюсь, что это так полезно для вас, как это было для меня. Наслаждаться!
Пвердые и фальсивые значения в JavaScript
Перед изучением логических выражений давайте понять, что «правда» в JavaScript. Поскольку JavaScript слабо набирается, он принуждает значения в логические значения в логических выражениях. Если заявления, && , и тройные условия все принуждают ценности в логические значения. Примечание
Есть только шесть Falsy Значения в JavaScript – ложь , null , undefined , NaN , 0 и "" – и все остальное truthy Отказ Это означает, что [] и {} оба правды, которые склонны путешествовать людей.
Логические операторы
В формальной логике существует только несколько операторов: отрицание, сочетание, дизъюнкция, подразумевание и биконденсиция. Каждый из них имеет эквивалент JavaScript: Действительно , && С С Если (/* условие */) {/* Тогда следствие */} и ===
Истинные столы
Во-первых, давайте посмотрим на Истинные столы Для каждого из наших основных операторов. Таблица правды рассказывает нам, какова правдивость выражение основан на правде своей части Отказ Истинные столы важны. Если два выражения генерируют ту же таблицу правды, то эти выражения эквивалентны и могут заменить друг друга Отказ
Отрицание Стол очень прост. Отрицание – единственный унарный логический оператор, действующий только на одном входе. Это означает, что Действительно А B не то же самое, что Действительно (A
Например, первый ряд в таблице прав истины отрицания (ниже) следует прочитать так: «Если оператор A верно, то выражение! A является ложным ».
Отрицание простого утверждения не сложно. Отрицание «идет дождь» – это «это не дождь», а отрицание примитив JavaScript правда это, конечно, ложь Отказ Однако отрицание сложных утверждений или выражений не так просто. Что является отрицанием «это всегда Дождь “или isfoo && isbar ?
Соединение Стол показывает, что выражение A && B верно только если оба A и B верно. Это должно быть очень знакомо от записи JavaScript.
Дизъюнкция Стол также должен быть очень знакомым. Дизъюнкция (логика или утверждение) верно, если либо или оба a и b верны.
Последствия Таблица не так знакома. Так как подразумевает B, быть настоящим подразумевает B верно. Тем не менее, B может быть верным по причинам, отличным от A, поэтому последние две строки таблицы верны. Единственным временем, которое является ложным, когда A верно, а B неверно, потому что тогда A не подразумевает B.
В то время как Если Заявления используются для последствий в JavaScript, не все Если заявления работают таким образом. Обычно мы используем Если Как контроль потока, не так, как чек правдоподобных, где следствие также имеет значение в чеке. Вот архетипический Последствия Если утверждение:
function implication(A, B) { if (A) { return B; } else { /* if A is false, the implication is true */ return true; }}Не волнуйтесь, что это несколько неловко. Есть более простые способы кодирования последствий. Из-за этой неловкости я буду продолжать использовать → как символ для последствий по всей этой статье.
БИКОНДИЯ Оператор, иногда называемый IF-And-, если (iff), оценивает true только в том случае, если два операнда A и B имеют одинаковую ценность правности. Из-за того, как обрабатывает символы JavaScript, использование ===. Для логических целей следует использовать только на операндах, отлитых в логические значения. То есть вместо А мы должны использовать ! A === !! B Отказ
Оговорки
Есть два больших предостережения для лечения кода JavaScript, как пропозициональная логика: короткое замыкание и Порядок операций Отказ
Короткое замыкание – это то, что двигатели JavaScript должны сэкономить время. То, что не изменит вывод всего выражения, не оценивается. Функция dosomething () В следующих примерах никогда не вызывается, потому что, независимо от того, что он возвращается, результат логического выражения не изменится:
// doSomething() is never calledfalse && doSomething();true || doSomething();
Напомним, что соединения ( && ) верны только если Оба заявления верны и дискзрены ( Несомненно ложные Только если оба утверждения ложь.
Из-за этой функции JavaScript иногда нарушает логическую коммутативность. Логически A && B эквивалентно B && A , но вы сломали вашу программу, если вы комбайны Окно && window.mightnotexist в window.mightnotexist && окно Отказ Это не сказать, что правдивость Образцовому выражению – это любой другой, просто этот JavaScript Май бросить ошибку, пытаясь разбирать его.
Порядок операций в JavaScript поймал меня врасплох, потому что я не учил эту формальную логику имел порядок операций, кроме группировки и слева направо. Оказывается, многие языки программирования считают && иметь более высокий приоритет, чем Отказ Это означает, что && сгруппирован (не оценен) сначала, слева направо и Тогда сгруппирован влево-справа. Это означает, что А B && C это не оценивается так же, как (A
true || false && false; // evaluates to true(true || false) && false; // evaluates to false
К счастью, группировка , () проводит самый верхний приоритет в JavaScript. Мы можем избежать сюрпризов и неоднозначность, вручную связывающим заявления, которые мы хотим оценить вместе в дискретные выражения. Вот почему многие кодовые льготы запрещают иметь оба && и
Вычисляющее соединение таблицы правды
Теперь, когда достоверность простых утверждений известна, правда более сложных выражений может быть рассчитана.
Для начала подсчитайте количество переменных в выражении и напишите таблицу истины, которая имеет 2 ⁿ строк.
Далее создайте столбец для каждой из переменных и заполните их всеми возможными комбинацией истинных/ложных значений. Я рекомендую заполнить первую половину первого столбца с T и вторая половина с F Затем расквартировать следующую колонку и так далее, пока не выглядит так:
Затем напишите выражение и решайте его в слоях, от внутренних групп наружу за каждую комбинацию значений правды:
Как указано выше, выражения, которые производят ту же таблицу истины, могут быть заменены друг для друга.
Правила замены
Теперь я охвачу несколько примеров правил замены, которые я часто использую. Никакие таблицы истины не включены ниже, но вы можете построить их самостоятельно, чтобы доказать, что эти правила верны.
Двойное отрицание
Логично, А и ! А эквивалентны. Вы всегда можете удалить двойное отрицание или Добавить двойное отрицание к выражению, не меняя правдивость. Добавление двойного отрицания пригодится, когда вы хотите отрицать часть сложного выражения. Одно предупреждение вот что в JavaScript ! Также действует, чтобы привлечь ценность в логическом виде, что может быть нежелательным побочным эффектом.
Коммутация
Любая дизъюнкция ( ), соединение ( && ), или BiCondition ( === ) может поменять порядок его частей. Следующие пары – логически
Ассоциация
Несколько неисправностей и соединения являются двоичными операциями, то есть они работают только на двух входах. Хотя они могут быть закодированы в более длинные цепи – А B Слияние D – Они неявно связаны слева направо – ((A
Распределение
Ассоциация не работает во всем союзам, так и с динамикам. То есть (A && (B C))! = = ((A && b) C) Отказ Для того, чтобы разобрать B и C В предыдущем примере вы должны распределить Соединение –
Еще одно распространенное количество распределения – это двойное распределение (похоже на фольгу в алгебре): 1 ((A B) && (C d)) === ((A b) && c)
Материальное влияние
Выражения подразумевающихся ( A → B ) обычно переводится в код как Если (а) {b} Но это не очень полезно, если выражение составного выражения имеет несколько последствий в нем. Вы бы закончились вложенным Если Заявления – кодовый запах. Вместо этого я часто использую правило замены материала, которое говорит, что A → B значит либо А ложно или B правда.
Тавтология и противоречие
Иногда в ходе манипулирования составными логическими выражениями вы получите простое соединение или дизъюнкцию, которое включает в себя только одну переменную и его отрицание или булевую буквальную букву. В этих случаях выражение либо всегда правда (тавтология), либо всегда ложное (противоречие) и может быть заменена логическими буквальными буквами в коде.
С этими эквиваленами являются дизъюнкция и связь с другими логическими буквальными буквальными буквами. Они могут быть упрощены только для правдимости переменной.
Транспозиция
При манипулировании подразумевании ( a → b ), общая ошибка, которую люди принимают, это предположить, что отрицание первой части, А , подразумевает вторую часть, B также отрицается – Действительно А →! B Отказ Это называется Конверс о влиянии и это не обязательно правда Отказ То есть, имеющие первоначальное значение, не говорят нам, если общение верно, потому что А не необходимо Состояние B Отказ (Если общение также верно – по вопросам независимых причин – тогда A и B бикондиция.)
Однако мы можем знать из оригинального значения, это то, что контрабапозитивные правда. С B это необходимое условие для А (Напомним из таблицы правды для этого, если B верно, А также должен быть правдой), мы можем утверждать, что Действительно B →! А Отказ
Материальная эквивалентность
Имя Бикондиционный исходит из того факта, что он представляет два условных (следствие) заявления: А означает, что A → B и B → A Отказ Ценности правды А и B заблокированы друг на друга. Это дает нам правило первого материального эквивалентности:
Использование материального следствия, двойное распределение, противоречие и коммутацию, мы можем манипулировать этим новым выражением в чем-то легче кода: 1 ((A → b) && (b → a)) === (! А Б) && (! B А)) 2 (! А Б) && (! B А)) ===. ((! A &&! Б) (B &&! B))
Экспортировать
Вложенные Если утверждения, особенно если нет еще Части, являются кодовым запахом. Простое вложенное Если Заявление может быть уменьшено в одно утверждение, где условный – это соединение двух предыдущих условий:
if (A) { if (B) { C }}// is equivalent toif (A && B) { C}Законы Демооргана
Законы Демооргана необходимы для работы с логическими заявлениями. Они говорят, как распространять отрицание в сочетании или дизъюнкции. Рассмотрим выражение Действительно (A B) Отказ Законы Демиоргана говорят, что при отрицании дизъюнкции или соединения отрицайте каждое утверждение и измените && к или наоборот. Таким образом Действительно (A B) такой же, как Действительно A &&! B Отказ Точно так же Действительно (A && B)
Тройное положение (Если-тогда-else)
Тервые утверждения ( a? B: C ) происходить регулярно в программировании, но они не совсем последствия. Перевод с тройного до формальной логики на самом деле является соединением двух последствий, A → B и Действительно A → C , что мы можем написать как: (! А Б) && (a
XOR (эксклюзивный или)
Эксклюзивный или, часто сокращенный Хор , значит, «один или другой, но не оба. «Это отличается от обычного или оператора только в том, что оба значения не могут быть верными. Это часто то, что мы имеем в виду, когда мы используем «или» на простом английском языке. У JavaScript не имеет родного оператора XOR, так как бы мы представляли это? 1 ” A или B, но не как A и B ” 2. (A B) &&! (A && B) прямой перевод 3 (A b) && (! А Действительно Б) Законы Демооргана 4 (! А ! B) && (a b)
В качестве альтернативы, 1 ” A или B, но не как A и B ” 2. (A B) &&! (A && B) прямой перевод 3 (A b) && (! А Действительно Б) Законы Демооргана 4 (A &&! А) (A &&! Б) (B &&! А) (B &&! Б) Двойное распределение 5 (A &&! Б) (B &&! А)
Установить логику
До сих пор мы рассмотрели заявления о выражениях с участием двух (или нескольких) ценностей, но теперь мы обратим наше внимание на наборы значений. Как правило, как логические операторы в сложных выражениях сохраняют правду в предсказуемых путях, Функции предиката Наборы сохраняют правду в предсказуемые пути.
А Функция предиката Это функция, вход которой является значением из множества, а выход которого является логией. Для следующих примеров кода я буду использовать массив номеров для набора и двух функций предиката: isodd => n% 2! = = 0; А nd => n% 2 === 0;.
Универсальные заявления
А Универсальный Заявление о том, что относится к Все Элементы в наборе, что означает, что его функция предиката возвращает true для каждого элемента. Если предикат возвращает false для любого (или более) элемента, то универсальное утверждение неверно. Array.prototype.ewly принимает функцию предикатов и возвращает правда Только если каждый элемент массива возвращает true для предиката. Это также прекращает ранние (с false ), если предикат возвращает false, не выполняет предикат по всему элементам массива, поэтому на практике Избегайте побочных эффектов в предикатах Отказ
В качестве примера рассмотрим массив [2, 4, 6, 8] и универсальное утверждение «каждый элемент массива даже». Использование Iseven и встроенная универсальная функция JavaScript, мы можем запустить [2, 4, 6, 8]. Every (Iseven) и найти, что это правда Отказ
Экзистенциальные заявления
экзистенциально Заявление оказывает определенное требование о наборе: по меньшей мере один элемент в установленном наборе возвращает true для функции предиката. Если предикат возвращает false для каждого элемента в наборе, то экзистенциальное утверждение неверно.
JavaScript также поставляет встроенное экзистенциальное утверждение: Array.prototype.some Отказ Похоже на каждый , некоторые вернется рано (с правдой), если элемент удовлетворяет его предикату. В качестве примера [1, 3, 5] .some (isodd) будет работать только одну итерацию предиката ИЗОДД (потребляя 1 и возвращение истина ) и вернуть правда Отказ [1, 3, 5] .some (Iseven) вернется ложь Отказ
Универсальное значение
Как только вы проверили универсальное утверждение против набора, скажите nums.ewly (Isodd) , заманчиво думать, что вы можете схватить элемент из набора, который удовлетворяет предикату. Тем не менее, есть один улов: в логической логике, истинное универсальное утверждение не подразумевает что набор не пустым. Универсальные утверждения о пустых наборах являются всегда правда Поэтому, если вы хотите захватить элемент из набора, удовлетворяющего некоторому состоянию, вместо этого используйте экзистенциальную проверку. Доказать это, бегите []. Every (() = > Фаль SE). Это будет правда.
Отрицание универсальных и экзистенциальных утверждений
Отрицание этих утверждений может быть удивительным. Отрицание универсального заявления, скажем, nums.ewly (Isodd) , не nums.ewly (Iseven) , а скорее Nums.some (Iseven) Отказ Это экзистенциальное утверждение с предикатом отрицательно. Точно так же отрицание экзистенциального утверждения является универсальным утверждением с предикатом отрицательным.
Установить перекрестки
Два набора могут быть связаны друг с другом всего в нескольких отношениях, в отношении их элементов. Эти отношения легко построены с диаграммами Venn, а могут (в основном) определяться в коде с использованием комбинаций универсальных и экзистенциальных утверждений.
Два набора могут поделиться некоторыми, но не всеми своими элементами, как типичный объединенные Диаграмма Венна:
Один набор может содержать все элементы другого набора, но имеют элементы, не переданные вторым набором. Это подмножество отношения, обозначаемые как Подмножество ⊆ Superset Отказ
Два набора могут поделиться нет элементы. Это несерьезно наборы.
Наконец, два набора могут делиться каждому элементу. То есть они являются подмутами друг друга. Эти наборы равный Отказ В формальной логике мы бы написали A ⊆ B && B ⊆ A ⟷ A , но в JavaScript есть некоторые осложнения с этим. В JavaScript, Массив это заказал установить и может содержать дубликаты значения, поэтому мы не может Предположим, что двунаправленный код подмножества B.EVERY (EL => A. INCLUDES (EL)) && A.EWERY (EL => B. wclude S (EL)) подразумевает A R лучи А и B равны л Отказ Если А и B наборы (что означает, что они были созданы с новым Установите ()), то их значения уникальны, и мы можем сделать проверку двунаправленного подмножества на S EE, если Отказ
Перевод логики на английский
Этот раздел, вероятно, является наиболее полезным в статье. Здесь, теперь, когда вы знаете логические операторы, их таблицы истины и правила замены, вы можете узнать, как перевести английскую фразу в код и упростить Это. В изучении этого навыка перевода вы также сможете читать Код лучше, сохраняя сложную логику в простых фразах в вашем уме.
Ниже приведена таблица логического кода (слева) и их английских эквивалентов (справа), которое было сильно заимствовано из отличной книги, Основы логики Отказ
Ниже я пройду через несколько реальных примеров с моей собственной работы, где я интерпретирую от английского в код, и наоборот и упрощаю код с правилами замены.
Пример 1.
Недавно, чтобы удовлетворить требования ГДПР ЕС, мне пришлось создать модаль, который показал политику Cookie моей компании и позволил пользователю установить их предпочтения. Чтобы сделать это как можно более ненавязчиво, у нас имели следующие требования (в порядке приоритета):
- Если пользователь не был в ЕС, никогда не Показать модаль предпочтения GDPR.
- 2 Если приложение программно необходимо показать модаль (если действие пользователя требует большего разрешения, чем в данный момент), показать модаль.
- Если пользователю разрешено иметь менее навязчивый GDPR баннер не показывать модаль.
- Если у пользователя есть не Уже установить свои предпочтения (по иронии судьбы, сохраненные в файле cookie), показать модаль.
Я начал с серии Если Заявления, смоделированные непосредственно после этих требований:
const isGdprPreferencesModalOpen = ({ shouldModalBeOpen, hasCookie, hasGdprBanner, needsPermissions}) => { if (!needsPermissions) { return false; } if (shouldModalBeOpen) { return true; } if (hasGdprBanner) { return false; } if (!hasCookie) { return true; } return false;}Быть понятным, приведенный выше код работает, но Возвращая логические литералы – это код запаха Отказ Поэтому я прошел через следующие шаги:
/* change to a single return, if-else-if structure */let result;if (!needsPermissions) { result = false;} else if (shouldBeOpen) { result = true;} else if (hasBanner) { result = false;} else if (!hasCookie) { result = true} else { result = false;}return result;/* use the definition of ternary to convert to a single return */return !needsPermissions ? false : (shouldBeOpen ? true : (hasBanner ? false : (!hasCookie ? true : false)))
/* convert from ternaries to conjunctions of disjunctions */return (!!needsPermissions || false) && (!needsPermissions || ((!shouldBeOpen || true) && (shouldBeOpen || ((!hasBanner || false) && (hasBanner || !hasCookie))))
/* simplify double-negations and conjunctions/disjunctions with boolean literals */return needsPermissions && (!needsPermissions || ((!shouldBeOpen || true) && (shouldBeOpen || (!hasBanner && (hasBanner || !hasCookie))))
/* DeMorgan's Laws */return needsPermissions && (!needsPermissions || ((!shouldBeOpen || true) && (shouldBeOpen || ((!hasBanner && hasBanner) || (hasBanner && !hasCookie))))
/* eliminate tautologies and contradictions, simplify */return needsPermissions && (!needsPermissions || (shouldBeOpen || (hasBanner && !hasCookie)))
/* DeMorgan's Laws */return (needsPermissions && !needsPermissions) || (needsPermissions && (shouldBeOpen || (hasBanner && !hasCookie)))
/* eliminate contradiction, simplify */return needsPermissions && (shouldBeOpen || (hasBanner && !hasCookie))
Я оказался чем-то, что я думаю, что более элегантно и все еще читается:
const isGdprPreferencesModalOpen = ({ needsPermissions, shouldBeOpen, hasBanner, hasCookie,}) => ( needsPermissions && (shouldBeOpen || (!hasBanner && !hasCookie)));Пример 2.
Я нашел следующий код (написанный коллегой) при обновлении компонента. Опять же, я почувствовал желание устранить булевую буквальную доходность, поэтому я его поклонился.
const isButtonDisabled = (isRequestInFlight, state) => { if (isRequestInFlight) { return true; } if (enabledStates.includes(state)) { return false; } return true;};Иногда я делаю следующие шаги в моей голове или на бумаге царапины, но чаще всего я пишу каждый следующий шаг в коде, а затем удалить предыдущий шаг.
// convert to if-else-if structurelet result;if (isRequestInFlight) { result = true;} else if (enabledStates.includes(state)) { result = false;} else { result = true;}return result;// convert to ternaryreturn isRequestInFlight ? true : enabledStates.includes(state) ? false : true;
/* convert from ternary to conjunction of disjunctions */return (!isRequestInFlight || true) && (isRequestInFlight || ((!enabledStates.includes(state) || false) && (enabledStates.includes(state) || true))
/* remove tautologies and contradictions, simplify */return isRequestInFlight || !enabledStates.includes(state)
Тогда я в конечном итоге:
const isButtonDisabled = (isRequestInFlight, state) => ( isRequestInFlight || !enabledStates.includes(state));
В этом примере я не начал с английских фраз, и я никогда не удосужился интерпретировать код на английском языке во время манипуляций, но теперь, в конце концов, я могу легко перевести это: «Кнопка отключена, если либо запрос в полете или государство не в наборе включенных состояний. ” В этом есть смысл. Если вы когда-нибудь переведу свою работу на английский и это не Уменьшите, переведите свою работу. Это происходит со мной часто.
Пример 3.
При написании A/B Testing Framework для моей компании у нас были два основных списка включенных и инвалидов экспериментов, и мы хотели проверить, что каждый Эксперимент (каждый отдельный файл в папке) был записан в одном или другим списке Но не оба Отказ Это означает, что включенные и отключенные наборы являются Разрешенные И множество всех экспериментов представляет собой подмножество соединения двух наборов экспериментов. Причина, по которой набор всех экспериментов должен быть подмножественным сочетанием двух списков заключается в том, что не должно быть одного эксперимента, который существует снаружи два списка.
const isDisjoint = !enabled.some(el => disabled.includes(el)) && !disabled.some(el => enabled.includes(el));const isSubset = allExperiments.every( el => enabled.concat(disabled).includes(el));assert(isDisjoint && isSubset);
Заключение
Надеюсь, это все было полезно. Не только навыки перевода между английским и кодом полезны, но имеющие терминологию для обсуждения различных отношений (таких как соединения и последствия), а инструменты для их оценки (таблицы истины) удобны.