In this post, we’ll discuss type concepts, compare static and dynamic types, and show an unobtrusive type inference package provided by Flow.org.
Let’s kick the tires. Add a small annotation in a comment at the top of a source file:
Now paste the file contents into Flow’s type inference analysis at https://flow.org/try/. Flow determines static types by inferring them via static flow analysis. This shifts type checking away from execution time. In actual use, we’d configure Flow as part of a build process, as directed at https://flow.org/en/docs/getting-started/.
The goal? To find type-conflict errors at build time instead of in production. If ambiguities surface at build-time, we would either apply Flow annotation to hotspot variables for analysis guidance or address any actual type error flagged in a Flow message.
What ARE Data Types?
A 25-cent paraphrase: “A data type is the set of operations and methods valid for a given data item.” See the references for the five dollar description.
Dynamic vs. Static Typing
- Dynamic typing: an item’s data type IS NOT settled until runtime. It is late-bound.
- Static typing: the item’s type IS known at build-time. It is early-bound, either by explicit setting or by type inference. The latter notion is the heart of Flow.
int are types. Each represents qualities and behaviors of an associated data item. For example, a
string and an
array each have a
slice method property. An
int does not. A
string and an
int each understand a differing
slice on pairs of
array involving a variable and literals.
let c = 123; console.log(c + "string"); c = 'string'; console.log(c + 123); c = ; console.log(c + 123); c = 123; console.log(123 + 123); c = 'string'; console.log(c.slice(0,1)); c = ; console.log(c.slice(0,1)); c = 123; console.log(c.slice(0,1));
The execution sysout stream:
The runtime handled six of the operations by dynamically casting data types. That
c variable had multiple personality disorder. That was Duck Typing in action. This late-bound typing can make programming satisfying and fun for developers – until it has a miss at runtime. Think “production.”
Our snippet’s execution tripped over the seventh demo expression despite its legal syntax. It tried to slice an
int. As you know, those are impervious to knives.
Why didn’t the parser know this? Because, being of a late-bound persuasion, it didn’t notice that the type of
“c” became an
“int”. In other words, it didn’t track the flow of a literal
int into the variable. Not to worry: runtime caught the wrong type for the operation, belching a red error message. Luckily, the code was not transferring a paycheck into our bank account.
The parser would have known the type of a literal if one were substituted for the
c. Literals are easy to check, being frozen in type and value. Variables? They have a habit of…varying. Alas, they’re crucial to real logic, so we’re forced to deal them…and their dynamic types.
How would a developer defend against our error? Maybe by a logic change or by a
typeof check? They could ideally scan the application for such opportunities while being careful not to introduce a new bug. We could argue that “defense” is a distraction from the task-at-hand. Here, the luster of Duck Typing seems to fade.
This is hard-typing. If the language-at-hand uses pure static typing, its parser knows applicable valid operations and methods. Parsing errors cause the logging of invalid situations. Such early binding historically came at the expense of a developer requirement to set types on each variable or constant.
It crushed some of the exhilaration provided by Duck Typed languages. On the other hand, the developer gained confidence that her logic would not fail because of a type of conflict.
Advantage of Static Typing
Let’s cut over to Java for a minute. There, any pre-1.6 compiler requires explicit static types. This reduces runtime surprises by ensuring correct type usage at compile-time.
Here’s an example using types tagged with angle bracket generics. The coder simply wants to make a list of strings.
List<String> list = new ArrayList<String>(); // Java 1.5
const cannot be reassigned, so the type remains an
array forever. That defends against a type-change at run time.
Static typing could constrain those types to string, maintaining readability while minimizing bugs due to potential defensive errors.
Downside of Static Typing
<> for the brackets, but the
ArrayList still seem to be redundant noise.
We had to tell the compiler that the list is a
ArrayList IS a
List, isn’t it? Verbosity multiplied across the application means less readability. A blur of code inhibits brilliant reasoning while exacerbating future misunderstandings.
How about a cool way to enjoy the confidence upside to static typing while minimizing developer-supplied type-noise? Say “type inference!” Thank you, academia and functional programming! How do you pronounce Haskell?
The goal: catch type errors at build-time without requiring much manual specification of types.
Hindley–Milner Type System
This type of inference paradigm originated in academia. It works well, but the implementation is complex. No, we’re saying that it’s complex! Haskell and Scala implemented it. Others followed.
Try Flow Online
Let’s enter that earlier seven-part snippet into the “Try” view of https://flow.org/ . The result:
Flow flagged line 16, the line that parsed error-free, but the line blew up in execution. Flow flagged line 8, as well, but that line executed okay. Was its result what we intended? Let us use a cast on those two lines to direct the parser.
Here is the Flow “Try” box for that code after placing the casts at lines 8 and 16. Success.
Following is the console after executing that version of the snippet:
Exception cured. At this point, we increased our confidence in this monument to destiny. Again, the beauty of the Flow approach is that we can open legacy code for analysis during the build, but without changing logic or language. Certainly, at some point, Flow may flag a type ambiguity. There, we’d need to provide a hint via a type annotation or make a logic fix.
Flow Configuration and Usage
Flow Type Annotations
The complete list of type annotations is in the API document at https://flow.org/en/docs/types/. We usually don’t apply a type annotation until Flow processing shows an ambiguity. The advantage of type inference is static typing, but with vastly reduced explicit typing.
Exception: a data container object may need proactive annotating. The remainder of the flow analysis may complete with less fuss after central data containers receive annotations. They are usually simple holders of members that have (wait for it)…types.
We submitted an existing Node REST application to Flow. We received a huge set of type complaints in response. We added annotations to an ES6
Person container class, as shown below.
Notice that the annotations resemble TypeScript or Adobe Flex types. Each follows its variable declaration. We had to add an additional set of instance meta-variables as well. See them between the class and the constructor? Post-processing strips them after the flow analysis.
Here is the reverted
Person class after flow processing:
PersonRepository client of class
Person needed an up-front
PersonObj annotation to describe the shape of its client
Person class. Its role is as an interface signature to the data container. Notice the
PersonObj reference in the angle-bracket annotation on the Map of persons?
Again, after flow reporting, the
PersonRepository copy reverted to the original for input to the downstream build process:
We didn’t change existing words or logic, except for type conflict repairs found by Flow issues. We encountered several! The overall result? A REST service that we feel is resistant to type errors during execution.
What About TypeScript?
Why use Flow if there is TypeScript? The two reportedly have influenced one another. Word on the street is that the type-checking is more-or-less equivalent except for smaller evolving details, such as nullable values.
- Data types
- Dynamic typing vs. static typing
- Pros and cons of each
- Type inference as a quieter static typing mechanism
- Online Flow checking
- Flow configuration and build-time usage
- A pair of examples
- Typescript compared to Flow
Give Flow a try. Paste your snippet-of-choice into the try-page at https://flow.org/try/ . It takes less than a minute.
These are worth clicking:
- Flow.org: https://flow.org/en/
- Type Inference: https://en.wikipedia.org/wiki/Type_inference
- Type System: https://en.wikipedia.org/wiki/Type_system
- Data Type: https://en.wikipedia.org/wiki/Data_type
- TypeScript: https://www.typescriptlang.org/