Get Better Type Checking in JavaScript with the Maybe Type
source link: https://www.tuicool.com/articles/hit/BnYnum6
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Get Better Type Checking in JavaScript with the Maybe Type
The dynamic typing in JavaScript makes it incredibly flexible. But, this flexibility can act as a double edged sword. When values have the potential to change, JavaScript will give you runtime errors in your code or some crazy bugs that will take forever to track down because of type coercion.
One way to tackle this issue is to fill your code with conditionals for null
and undefined
values and type checks. But this will make your app’s logic that much more complex to read and refactor at a later time.
Maybe typeis a popular abstraction for defining values that may or may not exist. It encapsulates the type checking and guards our code against any error or bugs caused by the missing values.
In this post, I will show how to use Maybe and keep your app readable and maintainable. Let’s dive in.
The Maybe Data Type
Create an empty folder in your system. You may name it anything you want.
$ mkdir maybe $ cd maybe
Open this folder in a code editor. I like to use VS Code.
$ code .
Create a file named utils.js
file and write a simple utility function inside it.
module.exports.inc = n => n + 1
Here, the inc
function takes in a number, increments it by one and returns it as the result.
Create another file called index.js
and import the inc
function inside it.
const {inc} = require('./utils')
const input = 2; const result = inc(input); console.log(result);
Run this code in your terminal using the command node index.js
and you will get the output as 3
.
But what if you give a string as the input? In the index.js
file, pass any string to the input
and run the node index.js
command.
What you will get the as the output is the same string, but with a 1 attached to it. This happens because the plus operator now acts like a concatenator.
Change the value of input
to undefined
, and now your result will be a NaN
. To avoid this obstacle, we can add a conditional statement that checks the typeof
the input
.
const result = typeof input === 'number' ? inc(input) : 0
While this works, we can do something much better with the help of maybe
data type. Import the a new library into this module called crocks
.
$ yarn add crocks
This library contains the Maybe
data type inside it. Import this library inside index.js
file.
const Maybe = require('crocks/Maybe')
Maybe
is an object that wraps around a value and lets us differentiate between values that we want to act on and value that we do not want to act on. The reason this works is that Maybe
is made up of two underlying types and it can be any one of those at a time.
Maybe
is made up of a Just
and Nothing
type. Just
is a value that we want our code to work on, and Nothing
is the value
that we want our code to ignore.
Inside index.js
file, rewrite the code inside it using the Maybe
data type.
const input = Maybe.Just (2); const result = input.map (inc);
Running this code will get us the result as Just 3
. What happened is that we wrapped up our 2
in this Maybe
. Then, when we called map
on it to apply to that function, the Maybe
unwrapped the value, got the 2
out and passed it into the inc
function. The inc
function then incremented it by 1, and passed it to result
and wrapped it back up in the Just
.
Replacing the value inside input
with Maybe.Nothing()
will give us the result as Nothing
.
To use both of these types
simultaneously, create a new function inside index.js
called num
and pass it to the input
.
const numb = val => typeof val === 'number' ? Maybe.Just(val) : Maybe.Nothing();
const input = num(2); const result = input.map(inc); console.log(result);
Passing any type of data that is not a number will be skipped over by the function that expects only a number and yield us a Nothing
. Using the maybe
type, we've added an element of safety to a function without having to change that original function.
Creating Maybes In A More Flexible Way
Create another utility function inside the utils.js
file called upper
.
<strong>const to upper = s -> s.toUpperCase();</strong>
module.exports = {
inc,
upper,
}
Import this utility function in the index.js
file and use it as shown below:
const { inc, <strong>upper </strong>} = require('./utils');
// previous section's code
const input2 = 'bit';
const result2 = upper(input2);
<strong>console.log(result2);</strong>
Run this code using node index.js
command and you will notice that the value of result2
is BIT
.
But what if I pass something other than a string to input2
? The output will then be a s.toUpperCase is not function .
To solve this issue, we can create a new function that takes in a val
and checks its type to see if it is a string
. I will then pass it to input2
.
const safe = val => typeofval === 'string' ? Maybe.Just(val) : Maybe.Nothing();
const input2 = safe('bit');
const result2 = input2.map(upper);
Now if you pass a value like 5
to input2
, you will get the result as Nothing
. The only problem we have now is that the safe
function is not generic enough. Create two new functions that check if the type is a number
or a string
.
const number = val => typeof val === 'number'; const string = val => typeof val === 'string';
Create another function that takes in a predicate function and a value. All we are going to do here is call the predicate function on the value, and based on the result, this function will either return a Just
or a Nothing
.
const safe = (pred, val) => pred(val) ? Maybe.Just(val) : Maybe.Nothing();
Instead we can now call the new function safe
in our code.
const input = safe(number, 2);
const result = input.map(inc);
const input2 = safe(string, 'bit');
const result2 = input2.map(upper);
Get Rid of Just and Nothing from the Output
Currently we are getting the output as either a Nothing
or a Just
followed by some data. But we don’t want our data to have these words associated with them. We want our data to be raw so that we can easily use it somewhere else.
Let’s create a new utility function inside the utils.js
file that takes some number as input and doubles it.
<strong>const double = n => n * 2;</strong>
module.exports = {
inc,
upper,
double
}
I will use it in the index.js
file to double the output of the inc
function.
const {inc, upper, double} = require('./double');
// previous section's code
const doubleOfInc = double(result);
If you run this code, you will get the output as NaN
. That is because the result
contain Just 3
and the double
function only takes in a number as an input.
We can use Maybe
to solve this problem. Restructure the doubleOfInc
as a function like this:
const doubleOfInc = n => {
const safeNumber = safe (number, n);
return safeNumber.map(inc).map(double);
};
const result3 = doubleOfInc (2);
console.log(result3)
Even though we are getting a proper result now, it is still wrapped in a Just
or Nothing
. To get an unwrapped result, we need to use .option
.
const doubleOfInc = n => {
const safeNumber = safe (number, n);
return safeNumber.map(inc).map(double).option(0);
};
const result3 = doubleOfInc (2);
console.log(result3)
The .option
is a method that takes in a default value that will be returned to us in case of a Nothing
. If our result has a Just
in it, then it will unwrap the output and only give us that.
Access Object Properties Using Maybe
Inside the index.js
file, create an object with a few properties inside it.
const time= { date: 5, month: 7, year: 2018 }
const date = time.date; const month = time.month; const year = time.year;
I can now use the upper
function to turn the string to an uppercase. But that will not work if this object did not have the property that I am passing to the function. If that is the case, I will get an undefined
instead.
To solve this, you can pull in the safe
utility from the crocks
property. This utility function is a lot similar to the one that we had used in the earlier sections.
const safe = require('crocks/Maybe/safe');
Since the safe
function requires a predicate function, pull in a few more utility functions from the ramda
library. Also install the ramda
library using NPM/Yarn.
const { compose, isNil, not } = require('ramda');
Create a new function inside the index.js
file. I will call it isNotNil
and set it equal to compose
, and compose
will call the not
and isNil
functions.
const isNotNil = compose(not, isNil);
Now, rewrite the date
constant using safe
with isNotNil
.
const date = safe(isNotNil, time.date); const nextDate = inc(date); console.log(nextDate);
The output will now be something like Just 51
. This is not what I expected it to be…
We can also use prop
from the crocks
library. This is a generic utility function that will grab any property from any object. By using this, we can comment out the imports from ramda
and the isNotNil
predicate function.
const prop = require('crocks/Maybe/prop')
This function will pull the property off the object, and if it is not nil
then it will give us a Just
wrapping the value that was in that property. But if it is nil
, we will get a Nothing
.
const date = prop('date', time);
The added benefit here is that we can call prop
with just one argument. This argument will the property name. This way we get a reusable utility function.
Conclusion
In JavaScript, there are a lot of places where we are required to handle nullable
values. For example, null
values need to be managed when we need to access a value from an array with bracket notation or Array.prototype.find
.
By using Maybe
in place of nullable values, we do not have to worry if the value exists or not. Maybe
takes care of it for us.
There are many more advantages and disadvantages to using a Maybe
type in JavaScript. Try out the code in the post, figure out how else you can use Maybe
in your code, and get back to me with your thoughts/comments…
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK