دليل قاطع للمنطق الشرطي في JavaScript

أنا مهندس واجهة أمامية وعالم رياضيات. أعتمد على تدريبي الرياضي يوميًا في كتابة الكود. ليس الإحصاء أو حساب التفاضل والتكامل الذي أستخدمه ، بل فهمي الشامل للمنطق المنطقي. غالبًا ما قمت بتحويل مجموعة معقدة من علامات العطف والأنابيب وعلامات التعجب وعلامات المساواة إلى شيء أبسط وأكثر قابلية للقراءة. أرغب في مشاركة هذه المعرفة ، لذلك كتبت هذا المقال. إنها فترة طويلة ولكن آمل أن تكون مفيدة لك كما كانت بالنسبة لي. استمتع!

قيم Truthy & Falsy في JavaScript

قبل دراسة التعبيرات المنطقية ، دعونا نفهم ما هو "صحيح" في JavaScript. نظرًا لأن JavaScript مكتوب بشكل فضفاض ، فإنه يقوم بإجبار القيم في منطقية في التعبيرات المنطقية. ifالتصريحات &&، ||والظروف الثلاثي جميع القيم إكراه في القيم المنطقية. لاحظ أن هذا لا يعني أنهم يعيدون دائمًا قيمة منطقية من العملية.

لا يوجد سوى ستة قيم خاطئة في JavaScript - false، null، undefined، NaN، 0، و ""- وكل شيء آخر صحيح . وهذا يعني أن []و {}كلاهما truthy، والتي تميل إلى رحلة الناس.

العوامل المنطقية

في المنطق الرسمي ، لا يوجد سوى عدد قليل من العوامل: النفي ، والاقتران ، والفصل ، والضمني ، والشرط. كل من هذه ديه أي ما يعادل جافا سكريبت: !، &&، ||، if (/* condition */) { /* then consequence */}، و ===، على التوالي. هذه العوامل تخلق جميع البيانات المنطقية الأخرى.

جداول الحقيقة

أولاً ، لنلقِ نظرة على جداول الحقيقة لكل عامل من عوامل التشغيل الأساسية لدينا. يخبرنا جدول الحقيقة عن مدى صحة التعبير بناءً على صدق أجزائه . جداول الحقيقة مهمة. إذا كان تعبيران يولدان جدول الحقيقة نفسه ، فإن هذين التعبيرين متكافئين ويمكنهما استبدال أحدهما الآخر .

و النفي جدول واضح وصريح للغاية. النفي هو العامل المنطقي الوحيد الذي يعمل فقط على إدخال واحد. هذا يعني أن !A || Bهذا ليس هو نفسه !(A || B). تعمل الأقواس مثل تدوين التجميع الذي تجده في الرياضيات.

على سبيل المثال ، يجب قراءة الصف الأول في جدول حقيقة النفي (أدناه) على النحو التالي: "إذا كانت العبارة A هي True ، فإن التعبير! A هو False."

إن نفي عبارة بسيطة ليس بالأمر الصعب. إن نفي عبارة "إنها تمطر" هو "إنها لا تمطر" ، وإنكار مبدأ جافا سكريبت البدائي trueهو بالطبع false. ومع ذلك ، فإن نفي العبارات أو التعبيرات المعقدة ليس بهذه البساطة. ما هو نفي عبارة "إنها تمطر دائما " أم isFoo && isBar؟

و بالتزامن يبين الجدول أن التعبير A && Bهو الصحيح إلا إذا كل من A و B صحيحة. يجب أن يكون هذا مألوفًا جدًا من كتابة JavaScript.

في المفرق وينبغي أيضا أن يكون الجدول مألوفة جدا. يكون الانفصال (عبارة OR المنطقية) صحيحًا إذا كان أحدهما أو كليهمامن A و B صحيحة.

و ضمنا الجدول ليس كما مألوفة. بما أن "أ" تعني " ب" ، فإن "أ" يعني أن "ب" صحيح. ومع ذلك ، يمكن أن يكون B صحيحًا لأسباب أخرى غير A ، وهذا هو سبب صحة آخر سطرين من الجدول. الوقت الوحيد الذي يكون فيه التضمين خاطئًا هو عندما يكون A صحيحًا ويكون B خطأً لأن A لا يشير إلى B.

بينما ifيتم استخدام العبارات للتأثيرات في JavaScript ، لا ifتعمل جميع العبارات بهذه الطريقة. عادة ، نستخدم ifكعنصر تحكم في التدفق ، وليس للتحقق من الصدق حيث تكون النتيجة مهمة أيضًا في الشيك. هذا هو البيان الضمني النموذجي if:

function implication(A, B) { if (A) { return B; } else { /* if A is false, the implication is true */ return true; }}

لا تقلق من أن هذا أمر محرج إلى حد ما. هناك طرق أسهل لترميز التضمينات. بسبب هذا الإحراج ، سأستمر في استخدام كرمز للتأثيرات خلال هذه المقالة.

و Bicondition المشغل، التي تسمى أحيانا إذا وفقط اذا (IFF)، يقيم في صحيح فقط إذا كانت المعاملات اثنين، A و B، وتقاسم قيمة truthiness نفسها. نظرًا للكيفية التي تتعامل بها JavaScript مع المقارنات ، فإن الاستخدام ===للأغراض المنطقية يجب أن يُستخدم فقط في المعاملات التي يتم إرسالها إلى القيم المنطقية. هذا هو ، بدلا من A === B، يجب أن نستخدم !!A === !!B.

تحفظات

هناك نوعان من المحاذير كبيرة لمعالجة شفرة جافا سكريبت مثل منطق اقتراحي: الدائرة القصيرة و ترتيب العمليات .

قصر الدائرة شيء تفعله محركات JavaScript لتوفير الوقت. لا يتم تقييم أي شيء لن يغير ناتج التعبير بالكامل. doSomething()لا يتم استدعاء الوظيفة في الأمثلة التالية مطلقًا لأنه ، بغض النظر عما يتم إرجاعه ، لن تتغير نتيجة التعبير المنطقي:

// doSomething() is never calledfalse && doSomething();true || doSomething();

تذكر أن أدوات العطف ( &&) صحيحة فقط إذا كانت كلتا الجملتين صحيحة ، وأن الانفصال ( ||) خاطئ فقط إذا كانت العبارتان خاطئتين . في كل حالة من هذه الحالات ، بعد قراءة القيمة الأولى ، لا يلزم إجراء المزيد من العمليات الحسابية لتقييم النتيجة المنطقية للتعبيرات.

بسبب هذه الميزة ، يكسر JavaScript أحيانًا التبديل المنطقي. منطقيا A && Bما يعادل B && A، ولكن كنت كسر البرنامج الخاص بك إذا كنت خففت window && window.mightNotExistإلى window.mightNotExist && window. هذا لا يعني أن صدق التعبير المخفف يختلف ، فقط أن JavaScript قد يتسبب في خطأ في محاولة تحليله.

لقد فاجأني ترتيب العمليات في JavaScript لأنني لم أتعلم أن المنطق الرسمي له ترتيب للعمليات ، بخلاف التجميع ومن اليسار إلى اليمين. اتضح أن العديد من لغات البرمجة &&لها أسبقية أعلى من ||. هذا يعني أنه &&تم تجميعه (لم يتم تقييمه) أولاً ، من اليسار إلى اليمين ، ثم ||يتم تجميعه من اليسار إلى اليمين. هذا يعني أنه لاA || B && C يتم تقييمها بنفس الطريقة ، بل بالأحرى .(A || B) && CA || (B && C)

true || false && false; // evaluates to true(true || false) && false; // evaluates to false

لحسن الحظ ، فإن التجميع ، ()له الأولوية القصوى في JavaScript. يمكننا تجنب المفاجآت والغموض من خلال ربط العبارات التي نريد تقييمها معًا يدويًا في تعبيرات منفصلة. وهذا هو السبب في كثير من نسالة كود تحظر وجود حد سواء &&و ||ضمن نفس المجموعة.

حساب جداول الحقيقة المركبة

الآن بعد أن عُرفت صحة العبارات البسيطة ، يمكن حساب صحة التعبيرات الأكثر تعقيدًا.

للبدء ، احسب عدد المتغيرات في التعبير واكتب جدول الحقيقة الذي يحتوي على صفين ونصف.

بعد ذلك ، قم بإنشاء عمود لكل من المتغيرات واملأها بكل مجموعة ممكنة من قيم الصواب / الخطأ. أوصي بملء النصف الأول من العمود الأول Tبالنصف الثاني والنصف الثاني به F، ثم تقسيم العمود التالي إلى ربع ، وهكذا حتى يبدو كما يلي:

ثم اكتب التعبير وحله في طبقات ، من المجموعات الداخلية إلى الخارج لكل مجموعة من قيم الحقيقة:

كما هو مذكور أعلاه ، يمكن الاستعاضة عن التعبيرات التي تنتج نفس جدول الحقيقة عن بعضها البعض.

قواعد الاستبدال

سأغطي الآن عدة أمثلة لقواعد الاستبدالات التي أستخدمها غالبًا. لا توجد جداول حقيقة مدرجة أدناه ، ولكن يمكنك بناؤها بنفسك لإثبات صحة هذه القواعد.

نفي مزدوج

Logically, A and !!A are equivalent. You can always remove a double negation or add a double negation to an expression without changing its truthiness. Adding a double-negation comes in handy when you want to negate part of a complex expression. The one caveat here is that in JavaScript !! also acts to coerce a value into a boolean, which may be an unwanted side-effect.

A === !!A

Commutation

Any disjunction (||), conjunction (&&), or bicondition (===) can swap the order of its parts. The following pairs are logically equivalent, but may change the program’s computation because of short-circuiting.

(A || B) === (B || A)

(A && B) === (B && A)

(A === B) === (B === A)

Association

Disjunctions and conjunctions are binary operations, meaning they only operate on two inputs. While they can be coded in longer chains — A || B || C || D — they are implicitly associated from left to right — ((A || B) || C) || D. The rule of association states that the order in which these groupings occur make no difference to the logical outcome.

((A || B) || C) === (A || (B || C))

((A && B) && C) === (A && (B && C))

Distribution

Association does not work across both conjunctions and disjunctions. That is, (A && (B || C)) !== ((A && B) || C). In order to disassociate B and C in the previous example, you must distribute the conjunction — (A && B) || (A && C). This process also works in reverse. If you find a compound expression with a repeated disjunction or conjunction, you can un-distribute it, akin to factoring out a common factor in an algebraic expression.

(A && (B || C)) === ((A && B) || (A && C))

(A || (B && C)) === ((A || B) && (A || C))

Another common occurrence of distribution is double-distribution (similar to FOIL in algebra):

1. ((A || B) && (C || D)) === ((A || B) && C) || ((A || B) && D)

2. ((A || B) && C) || ((A || B) && D) ===

((A && C) || B && C)) || ((A && D) || (B && D))

(A || B) && (C || D) === (A && C) || (B && C) || (A && D) || (B && D)

(A && B) ||(C && D) === (A || C) && (B || C) && (A || D) && (B || D)

Material Implication

Implication expressions (A → B) typically get translated into code as if (A) { B } but that is not very useful if a compound expression has several implications in it. You would end up with nested if statements — a code smell. Instead, I often use the material implication rule of replacement, which says that A → B means either A is false or B is true.

(A → B) === (!A || B)

Tautology & Contradiction

Sometimes during the course of manipulating compound logical expressions, you’ll end up with a simple conjunction or disjunction that only involves one variable and its negation or a boolean literal. In those cases, the expression is either always true (a tautology) or always false (a contradiction) and can be replaced with the boolean literal in code.

(A || !A) === true

(A || true) === true

(A && !A) === false

(A && false) === false

Related to these equivalencies are the disjunction and conjunction with the other boolean literal. These can be simplified to just the truthiness of the variable.

(A || false) === A

(A && true) === A

Transposition

When manipulating an implication (A → B), a common mistake people make is to assume that negating the first part, A, implies the second part, B, is also negated — !A → !B. This is called the converse of the implication and it is not necessarily true. That is, having the original implication does not tell us if the converse is true because A is not a necessary condition of B. (If the converse is also true — for independent reasons — then A and B are biconditional.)

What we can know from the original implication, though, is that the contrapositive is true. Since Bis a necessary condition for A (recall from the truth table for implication that if B is true, A must also be true), we can claim that !B → !A.

(A → B) === (!B → !A)

Material Equivalence

The name biconditional comes from the fact that it represents two conditional (implication) statements: A === B means that A → BandB → A. The truth values of A and B are locked into each other. This gives us the first material equivalence rule:

(A === B) === ((A → B) && (B → A))

Using material implication, double-distribution, contradiction, and commutation, we can manipulate this new expression into something easier to code:

1. ((A → B) && (B → A)) === ((!A || B) && (!B || A))

2. ((!A || B) && (!B || A)) ===

((!A && !B) || (B && !B)) || ((!A && A) || (B && A))

3. ((!A && !B) || (B && !B)) || ((!A && A) || (B && A)) ===

((!A && !B) || (B && A))

4. ((!A && !B) || (B && A)) === ((A && B) || (!A && !B))

(A === B) === ((A && B) || (!A && !B))

Exportation

Nested if statements, especially if there are no else parts, are a code smell. A simple nested if statement can be reduced into a single statement where the conditional is a conjunction of the two previous conditions:

if (A) { if (B) { C }}// is equivalent toif (A && B) { C}
(A → (B → C)) === ((A && B) → C)

DeMorgan’s Laws

DeMorgan’s Laws are essential to working with logical statements. They tell how to distribute a negation across a conjunction or disjunction. Consider the expression !(A || B). DeMorgan’s Laws say that when negating a disjunction or conjunction, negate each statement and change the && to ||or vice versa. Thus !(A || B) is the same as !A && !B. Similarly, !(A && B)is equivalent to !A || !B.

!(A || B) === !A && !B

!(A && B) === !A || !B

Ternary (If-Then-Else)

Ternary statements (A ? B : C) occur regularly in programming, but they’re not quite implications. The translation from a ternary to formal logic is actually a conjunction of two implications, A → B and !A → C, which we can write as: (!A || B) && (A || C), using material implication.

(A ? B : C) === (!A || B) && (A || C)

XOR (Exclusive Or)

Exclusive Or, often abbreviated xor, means, “one or the other, but not both.” This differs from the normal or operator only in that both values cannot be true. This is often what we mean when we use “or” in plain English. JavaScript doesn’t have a native xor operator, so how would we represent this?

1. “A or B, but not both A and B”

2. (A || B) && !(A && B)direct translation

3. (A || B) && (!A || !B)DeMorgan’s Laws

4. (!A || !B) && (A || B)commutativity

5. A ? !B : Bتعريف if-then-else

A ? !B : B هو حصري أو (xor) في JavaScript

بدلا من ذلك،

1. "أ أو ب ، ولكن ليس كلاهما" أ "و" ب "

2. (A || B) && !(A && B)الترجمة المباشرة

3. (A || B) && (!A || !B)قوانين DeMorgan

4. (A && !A) || (A && !B) || (B && !A) || (B && !B)التوزيع المزدوج

5. (A && !B) || (B && !A)استبدال التناقض

6. A === !Bأو A !== Bمعادلة المواد

A === !B أوA !== B هو xor في JavaScript

ضبط المنطق

لقد بحثنا حتى الآن في عبارات حول التعبيرات التي تتضمن قيمتين (أو بضعة قيم) ، لكننا الآن سنوجه انتباهنا إلى مجموعات من القيم. تشبه إلى حد كبير كيف تحافظ العوامل المنطقية في التعبيرات المركبة على الصدق بطرق يمكن التنبؤ بها ، فإن الوظائف المسند في المجموعات تحافظ على الصدق بطرق يمكن التنبؤ بها.

A predicate function is a function whose input is a value from a set and whose output is a boolean. For the following code examples, I will use an array of numbers for a set and two predicate functions:isOdd = n => n % 2 !== 0; and isEven = n => n % 2 === 0;.

Universal Statements

A universal statement is one that applies to all elements in a set, meaning its predicate function returns true for every element. If the predicate returns false for any one (or more) element, then the universal statement is false. Array.prototype.every takes a predicate function and returns true only if every element of the array returns true for the predicate. It also terminates early (with false) if the predicate returns false, not running the predicate over any more elements of the array, so in practice avoid side-effects in predicates.

على سبيل المثال ، ضع في اعتبارك المصفوفة [2, 4, 6, 8]والبيان العالمي ، "كل عنصر من المصفوفة زوجي". باستخدام isEvenالوظيفة العامة المضمنة في JavaScript ، يمكننا تشغيلها [2, 4, 6, 8].every(isEven)وإيجاد ذلك true.

Array.prototype.every هو بيان JavaScript العالمي

بيانات وجودية

يقدم البيان الوجودي ادعاءً محددًا حول مجموعة: عنصر واحد على الأقل في المجموعة يعود صحيحًا للوظيفة الأصلية. إذا أرجع المسند خطأً لكل عنصر في المجموعة ، فإن العبارة الوجودية تكون خاطئة.

كما تزود جافا سكريبت بيان جودي المدمج في: Array.prototype.some. على غرار every، someسيعود مبكرًا (مع صواب) إذا كان العنصر يرضي المسند الخاص به. وكمثال على ذلك، [1, 3, 5].some(isOdd)سيتم تشغيل فقط التكرار واحد من المسند isOdd(المستهلكة 1والعودة true) والعودة true. [1, 3, 5].some(isEven)سيعود false.

Array.prototype.some هو بيان JavaScript الوجودي

التضمين العالمي

بمجرد التحقق من بيان عام مقابل مجموعة ، على سبيل المثال nums.every(isOdd)، من المغري التفكير في أنه يمكنك الحصول على عنصر من المجموعة التي ترضي المسند. ومع ذلك ، هناك مشكلة واحدة: في المنطق المنطقي ، لا تعني العبارة العالمية الحقيقية أن المجموعة ليست فارغة. تكون العبارات العامة حول المجموعات الفارغة صحيحة دائمًا ، لذا إذا كنت ترغب في الحصول على عنصر من مجموعة تفي ببعض الشروط ، فاستخدم التحقق الوجودي بدلاً من ذلك. لإثبات ذلك ، قم بتشغيله [].every(() => fal). سيكون صحيحا.

البيانات العامة حول المجموعات الفارغة صحيحة دائمًا .

نفي البيانات الكونية والوجودية

قد يكون نفي هذه العبارات مفاجئًا. إن نفي بيان عالمي ، على سبيل المثال nums.every(isOdd)، ليس nums.every(isEven)، بل بالأحرى nums.some(isEven). هذا بيان وجودي مع نفي المسند. وبالمثل ، فإن نفي البيان الوجودي هو بيان عالمي مع نفي المسند.

!arr.every(el => fn(el)) === arr.some(el => !fn(el))

!arr.some(el => fn(el)) === arr.every(el =&GT. ! fn (el))

تعيين التقاطعات

يمكن أن ترتبط مجموعتان ببعضهما البعض فقط بعدة طرق ، فيما يتعلق بعناصرها. يتم رسم هذه العلاقات بسهولة باستخدام مخططات Venn ، ويمكن (في الغالب) تحديدها في رمز باستخدام مجموعات من العبارات العامة والوجودية.

يمكن لمجموعتين مشاركة بعض عناصرها وليس جميعها ، مثل مخطط Venn النموذجي الملتصق :

A.some(el => B.includes(el)) && A.some(el => !B.includes(el)) && B.some(el => !A.inclيصف udes (el)) زوجًا ملتصقًا من المجموعات

يمكن أن تحتوي مجموعة واحدة على جميع عناصر المجموعة الأخرى ، ولكن تحتوي على عناصر لا تشاركها المجموعة الثانية. هذه علاقة مجموعة فرعية ، يُشار إليها باسم Subset ⊆ Superset.

B.every(el => A.includes(eل)) يصف علاقة المجموعة الفرعية B ⊆ A

لا يمكن للمجموعتين مشاركة أي عناصر. هذه مجموعات منفصلة .

A.every(el => !B.includes(eل)) يصف زوجًا منفصلًا من المجموعات

أخيرًا ، يمكن للمجموعتين مشاركة كل عنصر. أي أنها مجموعات فرعية من بعضها البعض. هذه المجموعات متساوية . في المنطق الرسمي ، نكتب A ⊆ B && B ⊆ A ⟷ A === B، لكن في JavaScript ، هناك بعض التعقيدات مع هذا. في JavaScript ، Arrayتعد a مجموعة مرتبة وقد تحتوي على قيم مكررة ، لذلك لا يمكننا افتراض أن رمز المجموعة الفرعية ثنائي الاتجاه B.every(el => A.includes(el)) && A.every(el => B.includes (el)) يشير إلى أن rالأشعة a Aو B متساوية l. إذا كانت Aو B عبارة عن مجموعات (بمعنى أنهما تم إنشاؤهما with newSet ()) ، فإن قيمهما فريدة ويمكننا إجراء فحص المجموعة الفرعية ثنائية الاتجاه إلى s ee if A=== B.

(A === B) === (Array.from(A).every(el => Array.from(B).includes(el)) && Array.from(B).every(el => Array.from(A).includes (el)) ، بالنظر إلى ذلك Aوالمجموعة التي تم إنشاؤها using new()

ترجمة المنطق إلى اللغة الإنجليزية

This section is probably the most useful in the article. Here, now that you know the logical operators, their truth tables, and rules of replacement, you can learn how to translate an English phrase into code and simplify it. In learning this translation skill, you will also be able to read code better, storing complex logic in simple phrases in your mind.

Below is a table of logical code (left) and their English equivalents (right) that was heavily borrowed from the excellent book, Essentials of Logic.

Below, I will go through some real-world examples from my own work where I interpret from English to code, and vice-versa, and simplify code with the rules of replacement.

Example 1

Recently, to satisfy the EU’s GDPR requirements, I had to create a modal that showed my company’s cookie policy and allowed the user to set their preferences. To make this as unobtrusive as possible, we had the following requirements (in order of precedence):

  1. If the user wasn’t in the EU, never show the GDPR preferences modal.
  2. 2. If the app programmatically needs to show the modal (if a user action requires more permission than currently allowed), show the modal.
  3. If the user is allowed to have the less-obtrusive GDPR banner, do not show the modal.
  4. If the user has not already set their preferences (ironically saved in a cookie), show the modal.

I started off with a series of if statements modeled directly after these requirements:

const isGdprPreferencesModalOpen = ({ shouldModalBeOpen, hasCookie, hasGdprBanner, needsPermissions}) => { if (!needsPermissions) { return false; } if (shouldModalBeOpen) { return true; } if (hasGdprBanner) { return false; } if (!hasCookie) { return true; } return false;}

To be clear, the above code works, but returning boolean literals is a code smell. So I went through the following steps:

/* 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))

I ended up with something that I think is more elegant and still readable:

const isGdprPreferencesModalOpen = ({ needsPermissions, shouldBeOpen, hasBanner, hasCookie,}) => ( needsPermissions && (shouldBeOpen || (!hasBanner && !hasCookie)));

Example 2

I found the following code (written by a coworker) while updating a component. Again, I felt the urge to eliminate the boolean literal returns, so I refactored it.

const isButtonDisabled = (isRequestInFlight, state) => { if (isRequestInFlight) { return true; } if (enabledStates.includes(state)) { return false; } return true;};

Sometimes I do the following steps in my head or on scratch paper, but most often, I write each next step in the code and then delete the previous step.

// 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)

Then I end up with:

const isButtonDisabled = (isRequestInFlight, state) => ( isRequestInFlight || !enabledStates.includes(state));

In this example, I didn’t start with English phrases and I never bothered to interpret the code to English while doing the manipulations, but now, at the end, I can easily translate this: “the button is disabled if either the request is in flight or the state is not in the set of enabled states.” That makes sense. If you ever translate your work back to English and it doesn’t make sense, re-check your work. This happens to me often.

Example 3

While writing an A/B testing framework for my company, we had two master lists of Enabled and Disabled experiments and we wanted to check that every experiment (each a separate file in a folder) was recorded in one or the other list but not both. This means the enabled and disabled sets are disjointed and the set of all experiments is a subset of the conjunction of the two sets of experiments. The reason the set of all experiments must be a subset of the combination of the two lists is that there should not be a single experiment that exists outside the two lists.

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);

Conclusion

نأمل أن يكون كل هذا مفيدًا. لا تعد مهارات الترجمة بين اللغة الإنجليزية والتعليمات البرمجية مفيدة فحسب ، بل إن امتلاك المصطلحات اللازمة لمناقشة العلاقات المختلفة (مثل الاقتران والآثار) وأدوات تقييمها (جداول الحقيقة) أمر مفيد.