推荐理由:React,Vue 和 Angular 上的 UI 组件介绍,不过文章开头提到的 Material Design 也是很值得一看的。这是一个设计系统,能够让你直接使用里面提供的组件或者是自定义它们中的一些参数;也可以说它是一种设计的规范,旨在为不同平台的用户提供统一的体验。
11 Material Design UI Component Libraries for 2018
Useful React, Vue and Angular UI component libraries built with Material Design.
Google’s Material Design is a specification for a unified system of visual, motion, and interaction design that adapts across different devices.
With the rise of React, Vue, Angular and Web components, Material Design becomes a popular way to style the building blocks of our application’s UI.
To help you get started, we’ve gathered some of the finest UI component libraries in the open source community, implementing Material Design.
Tip: using Bit components from any of these libraries can be easily shared, discovered and developed right from any project (!). It’s open source, so you can just give it a try and create a shared component collection for your team.
A widely popular (40k stars) set of React components implementing Google’s Material Design. Perhaps the most popular UI component library on earth, MUI has 1000 contributors and provides an out-of-the-box framework to compose a React application using a well-crafted set of UI components.
React toolbox is a set of React components implementing Google’s Material Design with the power of CSS Modules. With over 8K stars and over 225 contributors, this library v2 is still under development and integrates into your bundler (webpack/other) workflow. Note that CSS-Modules is required.
A UI kit of Modular and customizable Material Design UI components for the web. Developed by a team at Google, it’s widely popular in the community with 11K stars and 300 contributors. Components are built to be production-ready consumable as individual packages (also perfect with Bit). The library even provides integration with other JS frameworks and libraries.
A material design implementation in React with 2K stars and a set of fully accessible UI components and Sass files oriented for the web. The library comes with built-in customizable themes and a bunch of useful dev scripts. Here’s a useful example of kicking things off with webpack 2 configs.
A 14K stars Material UI component framework for Vue.js 2. It aims to provide clean, semantic and reusable cross-platform components, supporting all modern browsers, including IE11 and Safari 9+ (using polyfills). You can try the vue-cli Webpack SSR template to play with next-gen SEO websites.
At 7K stars Vue Material is a Simple, lightweight library built according to the Google Material Design specs to the pixel. The library’s components support all modern web browsers, and out-of-the-box themes are provided. There’s even a fully-featured webpack setup template. Take a look.
A lightweight collection of essential UI components written with Vue and inspired by Material Design, with 3.5K stars. Note that unlike others, the library doesn’t include a grid system, typography styles, etc. Instead, it focuses on interactive components that require Javascript. Here are the components.
At 7K stars this popular Vue.js UI components library implements Material Design guidelines. The library aims at developers building mobile applications and some desktop applications with strict requirements for browser compatibility. On top of components, built-in themes are included.
Material2 is Google’s Angular (2+) and TypeScript implementation of Google’s Material Design. At 16K stars and with over 320 contributors, it’s the most popular NG component library on earth. It provides over 40 components and built-in themes, with cross-browser support. Here’s a live example.
Also at 16K stars, this UI component library implements Material Design for AngularJS. it was built to deliver a lean, lightweight set of AngularJS-native UI elements that implement the material design specification for use in AngularJS single-page applications (SPAs). Note that using AngularJS Material requires the use of AngularJS 1.4.x or higher.
At 30K stars, MDL is a set of useful Material Design Components in HTML/CSS/JS. Although on limited support, the project lets you add a Material Design look and feel to your static content websites. It doesn’t rely on any JavaScript frameworks or libraries and is cross-device optimized, gracefully degrades in older browsers, and offers an accessible experience.
At 35K stars Materialize is a A modern responsive front-end CSS framework based on Material Design. The project provides components and animations that provide feedback to users. Additionally, a single underlying responsive system across all platforms allow for a more unified user experience.
推荐理由:你能在这个项目里看到一个界面很古老的 windows 的系统文件管理器——包括未更改过的原版和作者自己增强之后的改版和它们两个的源代码。如果你并不对源代码感兴趣而只是想体验一下它的话,里面也放了可供直接打开的应用程序。看习惯了 win10 简单易懂的文件管理之后再回去看看以前的一大堆没有文字的图标,才会发现,技术的普及正是将功能的强大与操作的简单结合起来产生的成果。
今日推荐英文原文:《Intro to Functions in Javascript.》作者:Celestino Salim
In order to use functions we have to define them, the syntax may vary but most of the time, it will follow this structure:
The function keyword
A function name
A list of parameters
A block statement {}
An explicit return.
function helloWorld(parameters) {
return (parameters);
}
helloWorld("Hello, World!")
This syntax is known as a function declaration, it can be invoked before it is defined because of hoisting. We’re not going to dig deeper into Hoisting, but it is important to know that it’s JavaScript’s default behavior of moving declarations to the top.
Another syntax that we can use to define a function is function Expressions. Notice that in function declaration, the function has a name (helloWorld), while function expressions can be named or anonymous and they’re not hoisted. This means that they cannot be used before defining.
Based on the above we can split functions into 3 different types.
Named function
A named function is defined and then can be called when we need it by referencing its name and passing arguments to it.
Anonymous function
An anonymous function needs to be tied to something such as a variable or an event, in order to run.
Immediately invoked function expression(IIFE).
An IIFE will run as soon as the browser finds it.
In JavaScript, functions are first-class objects. One of the consequences of this is that functions can be passed as arguments to other functions and can also be returned by other functions.
A function that takes other functions as arguments or returns functions as its result is called a higher-order function, and the function that is passed as an argument is called a callback function.
Higher-order functions & Callbacks.
A good example of higher-order functions & callbacks is the built ins .filter &.map methods.
Filter() calls a provided callback function once for each element in an array, and constructs a new array of all the values for which callback returns a value that coerces to true.
The example above have a function called driverNamesWithRevenueOver that returns a function this is what we call higher-order function and the .map function(driver) is the callback function.
Nested Functions
A function can be nested, which means we can define a function inside another function and return it.
function outer(){
function inner(){
return "Hello World!";
}
return inner;
}
outer()()
Closures
A closure is an inner function made accessible from outside of its parent function with in-scope variables and their values incorporated from the environment in which it was created. In that environment, it has access to its own local variables, to its outer function’s variables and the global variables (declared outside its outer function).
function phrase(name) {
const hello = "Hello";
function printPhrase() {
console.log(hello + " " + name);
}
printPhrase();
}
phrase("Gavin");
In this code example, the printPhrase() function refers to the name variable and the hello parameter of the enclosing (parent) phrase()function. And the result is that, when phrase() is called, printPhrase() successfully uses the variables and parameters of the former to output:
Hello Gavin
undefined
Pure Functions & Impure Functions.
Pure Functions
Always returns the same result if the same arguments are passed in. It does not depend on any state, or data, change during a program’s execution. It must only depend on its input arguments.
The function does not produce any observable side effects such as network requests, input and output devices, or data mutation.
function calculateSquareArea(x) {
return x * x;
}
Impure functions
This is an impure function because it depends on an external tax variable. A pure function can not depend on outside variables.
Before Node.js, web developers always faced the issue of having to use different languages for client-side and server-side scripts. JavaScript was traditionally the language for client-side scripting with the HTML code running in the browser. The invention of Node.js in 2009 introduced server-side scripting with JavaScript code running on the server and generating full dynamic web pages before returning them to the browser. This was a major breakthrough in web development, as Node.js allows using JavaScript both client-side and server-side.
The huge JavaScript community quickly recognized the advantages of using the same language both for the client-side and server-side scripting, and Node.js has become a popular environment for web development. However, there is no limit to perfection, and developers have created quite a number of frameworks for Node.js development to facilitate the web application development.
A framework is basically a functional tool that helps JS developers grow their applications. Using a framework allows automating many repetitive processes, using the common libraries and templates and integrating reusable components. With the global popularity of JavaScript, the list of Node.js frameworks is quite long — that’s why we list only those which we use in our projects or consider to be ambitious.
Express.js
If you search for Node.js web frameworks, you are sure to come across Express.js very often. Express.js is among the top Node.js frameworks and not without reason — many developers love it for creating an efficient platform for building robust applications and APIs. It is one of the four components of the well-known MEAN stack (MongoDB, Express, Angular, and Node.js) which is a toolset that is very frequently used for building web applications. Here Express serves as a middleware between the front-end, database, and back-end of an application.
This routing framework is very flexible and lightweight and offers excellent possibilities in terms of using plugins, extensions and third-party databases. Express.js is often referred to as the server framework for Node.js, as it creates an HTTP server for Node.js and supports the middleware functionality needed to respond to the HTTP requests.
Express.js, being one of the most popular web frameworks for Node.js, has a superb set of documentation and enjoys great community support. This means that you will have little trouble finding reusable components and libraries for Express.js. Moreover, it doesn’t require the use of specific development practices or particular tools, so JavaScript developers get the utmost freedom.
The clear structure created by Express.js is capable of supporting large, feature-rich applications using big data, such as media streaming or live chats. Its excellent scalability possibilities allow building enterprise-size applications with top-quality performance. Using Express.js our DA-14 team built a powerful booking application for tradesmen, called Fixington. This service helps you find reliable tradespeople in a minute and book them online. While another well-known example is Myspace, a social network that, among other functions, supports video and audio sharing as well as online radio. One more case of a great web app designed with Express.js is Yummly, a recipe search website providing personalized cooking recommendations.
Koa2
Koa2 is the second version of the Koa framework. Koa is the product of the same team that created Express.js; however, it is rather different from Express.js and not nearly as popular. It is now in the process of heavy development, with version 2 being the stable release of this framework.
Koa2 belongs to the family of Node.js MVC frameworks, where MVC stands for Model-View-Controller. The three components of MVC are the model which is the data of the application, the view which is the data representation via the app UI, and the controller transferring requests and updates between the model and the view.
Comparing Koa2 vs Express, the first one is more modular and lightweight with better customization possibilities. Koa2 allows you to start with an application that is almost empty and add only those features you need, as you go along. Also, it has good error-handling capabilities due to its use of generators that allow doing without callbacks and the issues that may be related to them.
Koa2 uses a number of specific methods which makes it somewhat difficult to learn. Moreover, it requires at least some basic knowledge in promises and generators. In any case, this difficulty should not discourage JavaScript developers appreciating its obvious benefits.
Sails.js
Sails.js is another MVC framework for Node.js which was also created by the team behind Express.js; however, it comes with better capabilities than the latter. Diving deeper, Sails comes with an object-rational mapping (ORM) solution to enable the use of literally any database. This Waterline tool saves Node.js developers the trouble of configuring multiple database queries. What is more, this MVC Node.js framework supports Socket.io by default, which makes it one of the best choices for social media apps, messaging tools, and collaborative software solutions.
Talking about other benefits of Sails.js, it is easily compatible with multiple front-end development platforms without restricting the developers’ choice of their toolset. It uses the same MVC structure as many other frameworks ensuring seamless transitions. The request handling mechanism in Sails.js is the same that is used in Express.js, thus giving it the same quality performance. In general, it’s a good solution for quick and easy building of browser-based apps.
Sails.js was used in the development of websites for Verizon, a US-based telecommunications company, and the Detroit Lions, an American football team. In total, the homepage of the Sails.js framework lists about two dozen globally-known brands that chose it for their development.
NestJS
NestJS is a new kid on the block among Node.js frameworks. It is focused on server-side scripting and is intended to build server-side applications. What makes NestJS different from other Node.js web application frameworks is that it uses TypeScript, a special superset of JavaScript as its programming language. If you are familiar with Angular, which also uses TypeScript, you will have no problem building your backend with NestJS, as components and syntax will look quite familiar.
This framework has a modular structure organizing code into separate modules. With such structure, using external libraries is much easier. Moreover, the main idea behind Nest.JS is to provide architecture to the applications right out of the box, helping developers build easily maintainable, scalable, and loosely bound software solutions.
NestJS is now in the first stages of its development; however, it has already made some claims in the web application frameworks sector.
LoopBack
LoopBack is another creation of the Express.js team and is built right on top of it. Loopback is an API framework for Node.js allowing you to build APIs that work with any client and can connect various devices. APIs created with LoopBack can be easily connected to backend data sources. LoopBack is also known for the support of multiple databases — Oracle, MongoDB, and SQL.
Another useful feature of LoopBack is the built-in API explorer allowing software developers to generate API documentation and SDKs.
Learning LoopBack from scratch may seem a bit difficult; however, once you get proficient enough, you will appreciate its performance. Among the brands that use LoopBack for their websites and applications, you can find such names as GoDaddy, a US-based domain registrar and web-hosting provider, and Symantec developing cyber-security solutions. The DA-14 team employed LoopBack while building Cosmunity, a social media app for geeks that can already boast thousands of fans worldwide.
Meteor.js
No list of Node.js frameworks would be complete without Meteor. It is one of the most popular full-stack frameworks for Node.js. It enables easy and straightforward creation of real-time applications. The great thing about Meteor is that it uses the same API on the server side and the client side, thus improving the overall app performance.
Meteor works properly right out of the box. It suggests a recommended stack, thus you can immediately start working on the application. At the same time, the framework is very flexible and permits the use of any other technology that you find more suitable for a particular task.
Other advantages of Meteor include its extremely lightweight structure with much fewer lines of code required. Besides, Meteor offers quite a lot of pre-written modules that can be used right away. Also, the vast Meteor community is a source of many reusable packages and modules.
Meteor uses the same code on multiple devices, and that makes it the perfect choice for creating mobile applications running on different devices. With the same code used across all devices, the updates can be implemented easily.
Brands such as IKEA, a global furniture company, and Honeywell, a large corporation working on products and services for the Internet of Things sector, use Meteor for their web development.
Derby.js
Derby.js belongs to MVC frameworks and can be used both for client-side and server-side script development. Its “crown jewel” is Racer, a data synchronization engine that enables quick and efficient data timing between different browsers and servers and the application database.
The initial Derby.js package consists of a number of standard Node.js modules. However, you are free to add custom code and integrate third-party modules to build highly efficient web applications.
The great data synchronization possibilities offered by Derby.js make it an obvious choice for real-time collaboration apps. Quick automatic timing and concurrency allow support of multiple users of the same application with simultaneous editing options. The use of server rendering ensures good search engine support and immediate page loads.
Hapi.js
Unlike many other frameworks we’ve mentioned here, Hapi.js has nothing to do with Express.js, and thus may be considered one of its competitors. Hapi.js was first created with the aim of supporting the work of a large distributed team, and since then the framework is considered to be the perfect choice for enterprise-size applications that are built by large and distributed teams.
Hapi.js enables great collaboration possibilities through focus on plugins that allow working on separate components without affecting the rest of the application. This framework is very well suited for creating reusable components that will help businesses reduce development time.
Hapi.js framework was used in building the web applications and sites for Walmart retail chain, Yahoo web services provider, and, believe it or not, the UK Government.
Mean.io
Mean.io is another full-stack framework, and from its name it is obvious which stack it uses. It offers everything that is needed to start application development right out of the box, thus with Mean.io you can set to work immediately. As it is a complete development stack, there’s no need to select separate components and experiment with them. Moreover, all tools used in this framework are immediately configured to be used together.
The Mean.io framework includes not only the four components of the MEAN stack, but a number of other web development tools, such as GraphQL and Babel. This way, with Mean.io, you can have a complete JavaScript-based toolset for web application development.
Mean.io can be used to build applications of all sizes and complexity. The effectiveness of the MEAN stack is well-known in the developers’ community, and quite a lot of web applications and sites have been built using this toolset.
Total.js
Total.js is the last in our Node.js web framework comparison list; however, it also deserves our attention. It is an MVC server-side framework that is highly suitable for creating web applications. It is very flexible in terms of compatibility with various databases supporting MongoDB, MySQL, PostgreSQL, and works well with a number of frontend networks, such as Angular, React, and Ember.
Total.js has a modular structure, and its standard package includes various utilities that can be used in web development, such as an SMTP mail sender tool, an image processing tool, a web server, and others.
Total.js is great for creating responsive applications at a relatively low maintenance cost and can be relied on to provide sufficient scalability and performance.
How to choose the Node.js framework
We have looked at ten of the best-known Node.js frameworks that are available today and have seen that all of them have their strong points. So, how to make the right choice for your project?
Naturally, you should start with the type of project; analyze its specifics and the way they can be matched by each of the frameworks. You can also factor in such considerations as:
availability and completeness of documentation;
community size;
open issues on GitHub;
type of issues it can solve;
flexibility;
complexity;
compatibility with other tools you are planning to use.
At the same time, all frameworks are meant to ease the development process. Thus, each of them is going to add quality and raise the performance of your application.
A definitive guide to conditional logic in JavaScript
I am a front-end engineer and mathematician. I rely on my mathematical training daily in writing code. It’s not statistics or calculus that I use but, rather, my thorough understanding of Boolean logic. Often I have turned a complex combination of ampersands, pipes, exclamation marks, and equals signs into something simpler and much more readable. I’d like to share this knowledge, so I wrote this article. It’s long but I hope it is as beneficial to you as it has been to me. Enjoy!
Truthy & Falsy values in JavaScript
Before studying logical expressions, let’s understand what’s “truthy” in JavaScript. Since JavaScript is loosely typed, it coerces values into booleans in logical expressions. if statements, &&, ||, and ternary conditions all coerce values into booleans. Note that this doesn’t mean that they always return a boolean from the operation.
There are only six falsy values in JavaScript — false, null, undefined, NaN, 0, and "" — and everything else is truthy. This means that [] and {} are both truthy, which tend to trip people up.
The logical operators
In formal logic, only a few operators exist: negation, conjunction, disjunction, implication, and bicondition. Each of these has a JavaScript equivalent: !, &&, ||, if (/* condition */) { /* then consequence */}, and ===, respectively. These operators create all other logical statements.
Truth Tables
First, let’s look at the truth tables for each of our basic operators. A truth table tells us what the truthiness of an expression is based on the truthiness of its parts. Truth tables are important. If two expressions generate the same truth table, then those expressions are equivalent and can replace one another.
The Negation table is very straightforward. Negation is the only unary logical operator, acting only on a single input. This means that !A || B is not the same as !(A || B). Parentheses act like the grouping notation you’d find in mathematics.
For instance, the first row in the Negation truth table (below) should be read like this: “if statement A is True, then the expression !A is False.”
Negating a simple statement is not difficult. The negation of “it is raining” is “it is not raining,” and the negation of JavaScript’s primitive true is, of course, false. However, negating complex statements or expressions is not so simple. What is the negation of “it is always raining” or isFoo && isBar?
The Conjunction table shows that the expression A && B is true only if both A and B are true. This should be very familiar from writing JavaScript.
The Disjunction table should also be very familiar. A disjunction (logical OR statement) is true if either or both of A and B are true.
The Implication table is not as familiar. Since A implies B, A being true implies B is true. However, B can be true for reasons other than A, which is why the last two lines of the table are true. The only time implication is false is when A is true and B is false because then A doesn’t imply B.
While if statements are used for implications in JavaScript, not all ifstatements work this way. Usually, we use if as a flow control, not as a truthiness check where the consequence also matters in the check. Here is the archetypical implicationif statement:
function implication(A, B) {
if (A) {
return B;
} else {
/* if A is false, the implication is true */
return true;
}
}
Don’t worry that this is somewhat awkward. There are easier ways to code implications. Because of this awkwardness, though, I will continue to use →as the symbol for implications throughout this article.
The Bicondition operator, sometimes called if-and-only-if (IFF), evaluates to true only if the two operands, A and B, share the same truthiness value. Because of how JavaScript handles comparisons, the use of === for logical purposes should only be used on operands cast to booleans. That is, instead of A === B, we should use !!A === !!B.
The Complete Truth Table
Caveats
There are two big caveats to treating JavaScript code like propositional logic: short circuiting and order of operations.
Short circuiting is something that JavaScript engines do to save time. Something that will not change the output of the whole expression is not evaluated. The function doSomething() in the following examples is never called because, no matter what it returned, the outcome of the logical expression wouldn’t change:
// doSomething() is never called
false && doSomething();
true || doSomething();
Recall that conjunctions (&&) are true only ifboth statements are true, and disjunctions (||) are false only if both statements are false. In each of these cases, after reading the first value, no more calculations need to be done to evaluate the logical outcome of the expressions.
Because of this feature, JavaScript sometimes breaks logical commutativity. Logically A && B is equivalent to B && A, but you would break your program if you commuted window && window.mightNotExist into window.mightNotExist && window. That’s not to say that the truthiness of a commuted expression is any different, just that JavaScript may throw an error trying to parse it.
The order of operations in JavaScript caught me by surprise because I was not taught that formal logic had an order of operations, other than by grouping and left-to-right. It turns out that many programming languages consider &&to have a higher precedence than ||. This means that && is grouped (not evaluated) first, left-to-right, and then || is grouped left-to-right. This means that A || B && C is not evaluated the same way as (A || B) && C, but rather as A || (B && C).
true || false && false; // evaluates to true
(true || false) && false; // evaluates to false
Fortunately, grouping, (), holds the topmost precedence in JavaScript. We can avoid surprises and ambiguity by manually associating the statements we want evaluated together into discrete expressions. This is why many code linters prohibit having both && and || within the same group.
Calculating compound truth tables
Now that the truthiness of simple statements is known, the truthiness of more complex expressions can be calculated.
To begin, count the number of variables in the expression and write a truth table that has 2ⁿ rows.
Next, create a column for each of the variables and fill them with every possible combination of true/false values. I recommend filling the first half of the first column with T and the second half with F, then quartering the next column and so on until it looks like this:
Then write the expression down and solve it in layers, from the innermost groups outward for each combination of truth values:
As stated above, expressions which produce the same truth table can be substituted for each other.
Rules of replacements
Now I’ll cover several examples of rules of replacements that I often use. No truth tables are included below, but you can construct them yourself to prove that these rules are correct.
Double negation
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 to
if (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 : Bif-then-else definition
A ? !B : B is exclusive or (xor) in JavaScript
Alternatively,
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 && !A) || (A && !B) || (B && !A) || (B && !B)double-distribution
5. (A && !B) || (B && !A)contradiction replacement 6. A === !B or A !== Bmaterial equivalence
A === !B or A !== B is xor in JavaScript
Set Logic
So far we have been looking at statements about expressions involving two (or a few) values, but now we will turn our attention to sets of values. Much like how logical operators in compound expressions preserve truthiness in predictable ways, predicate functions on sets preserve truthiness in predictable ways.
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.
As an example, consider the array [2, 4, 6, 8], and the universal statement, “every element of the array is even.” Using isEven and JavaScript’s built-in universal function, we can run [2, 4, 6, 8].every(isEven) and find that this is true.
Array.prototype.every is JavaScript’s Universal Statement
Existential Statements
An existential statement makes a specific claim about a set: at least one element in the set returns true for the predicate function. If the predicate returns false for every element in the set, then the existential statement is false.
JavaScript also supplies a built-in existential statement: Array.prototype.some. Similar to every, some will return early (with true) if an element satisfies its predicate. As an example, [1, 3, 5].some(isOdd) will only run one iteration of the predicate isOdd (consuming 1 and returning true) and return true. [1, 3, 5].some(isEven) will return false.
Array.prototype.some is JavaScript’s Existential Statement
Universal Implication
Once you have checked a universal statement against a set, say nums.every(isOdd), it is tempting to think that you can grab an element from the set that satisfies the predicate. However, there is one catch: in Boolean logic, a true universal statement does not imply that the set is non-empty. Universal statements about empty sets are always true, so if you wish to grab an element from a set satisfying some condition, use an existential check instead. To prove this, run [].every(() => false). It will be true.
Universal statements about empty sets are always true.
Negating Universal and Existential Statements
Negating these statements can be surprising. The negation of a universal statement, say nums.every(isOdd), is not nums.every(isEven), but rather nums.some(isEven). This is an existential statement with the predicate negated. Similarly, the negation of an existential statement is a universal statement with the predicate negated.
Two sets can only be related to each other in a few ways, with regards to their elements. These relationships are easily diagrammed with Venn Diagrams, and can (mostly) be determined in code using combinations of universal and existential statements.
Two sets can each share some but not all of their elements, like a typical conjoined Venn Diagram:
A.some(el => B.includes(el)) && A.some(el => !B.includes(el)) && B.some(el => !A.includes(el)) describes a conjoined pair of sets
One set can contain all of the other set’s elements, but have elements not shared by the second set. This is a subset relationship, denoted as Subset ⊆ Superset.
B.every(el => A.includes(el)) describes the subset relationship B ⊆ A
The two sets can share no elements. These are disjoint sets.
A.every(el => !B.includes(el)) describes a disjoint pair of sets
Lastly, the two sets can share every element. That is, they are subsets of each other. These sets are equal. In formal logic, we would write A ⊆ B && B ⊆ A ⟷ A === B, but in JavaScript, there are some complications with this. In JavaScript, an Array is an ordered set and may contain duplicate values, so we cannot assume that the bidirectional subset code B.every(el => A.includes(el)) && A.every(el => B.includes(el)) implies the arrays A and B are equal. If A and B are Sets (meaning they were created with new Set()), then their values are unique and we can do the bidirectional subset check to see 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)), given that A and Bare constructed using new Set()
Translating Logic to English
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.
View a screen-readable version of this code-to-English translation chart here.
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):
If the user wasn’t in the EU, never show the GDPR preferences modal.
2. If the app programmatically needs to show the modal (if a user action requires more permission than currently allowed), show the modal.
If the user is allowed to have the less-obtrusive GDPR banner, do not show the modal.
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;
}
/* 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))))
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.
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 structure
let result;
if (isRequestInFlight) {
result = true;
} else if (enabledStates.includes(state)) {
result = false;
} else {
result = true;
}
return result;
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.
Hopefully this has all been helpful. Not only are the skills of translating between English and code useful, but having the terminology to discuss different relationships (like conjunctions and implications) and the tools to evaluate them (truth tables) is handy.