MojiScript extends and enhances map
in many ways like supporting iterables and asynchronous code. This an introduction into the features MojiScript's map
provides and the differences with traditional map
.
This post was inspired by an excellent question Twitter:
Traditional map
Let's start out with something you should already be familiar with. This is map
in it's most simple form.
const values = [1, 2, 3]
values.map(x => x * 2) //=> [2, 4, 6]
Now before we even begin this journey, I would like to recommend breaking the function x => x * 2
out of the map()
call.
const double = x => x * 2
const values = [1, 2, 3]
values.map(double) //=> [2, 4, 6]
I know it seems like I am saying something trivial and obvious, but it's something I rarely see when reading code.
This is something you can do with map
, filter
, reduce
, Promise.then
calls, and more.
Syntax
MojiScript's map
is a standalone function and not a method on an object. You still have the same values
, map
, and func
. But the way you call it is just a little bit different (but not too much).
// JavaScript
values.map(func)
// MojiScript
map (func) (values)
Making this change opens up the possibility to easily compose new functions.
const newFunc = map (func) // a new function is born!
newFunc (values)
Mapping Iterators
Because JavaScript's map is a method attached to Array
, it cannot easily be used with other types.
In this example, I am importing range
, which is an Iterator
.
import range from 'mojiscript/list/range'
console.log(...range (1) (4)) //=> 1 2 3
Unfortunately, JavaScript's map
doesn't support Iterators
.
range (1) (4)
.map (double) //=> Error: map is not a function
Even if we do some sorcery, we can't get it to work...
Array.prototype.map.call(range (1) (4), double) //=> []
In JavaScript-land, the Iterator
must be cast to an Array
first before it can be mapped.
Array.prototype.map.call([...range (1) (4)], double) // [2, 4, 6]
// ----------------
// /
// cast to an Array
[...range (1) (4)].map(double) //=> [2, 4, 6]
//---------------
// \
// cast to an Array
But with MojiScript, map
has no problems with Iterators
and the syntax is identical to mapping over an Array
.
map (double) (range (1) (4)) //=> [2, 4, 6]
Mapping strings
The same syntax is used for mapping chars in a string.
const charCode = x => x.charCodeAt(0)
// JavaScript
Array.prototype.map.call('abc', charCode) //=> [97, 98, 99]
// MojiScript
map (charCode) ('abc') //=> [97, 98, 99]
NodeLists
NodeLists are also supported!
// JavaScript
document.querySelectorAll('div[id]').map()
//=> Error: document.querySelectorAll(...).map is not a function
// MojiScript
const getId = element => element.getAttribute('id')
const divs = document.querySelectorAll('div[id]')
const ids = map (divs) (getIds)
//=> ['id1', 'id2', 'id3']
Maybe
The Maybe
type is an excellent alternative to a nullable type. Instead of using null and having to perform null checks, you can use a Maybe
type in it's place.
JavaScript:
const upper = string =>
string == null ? string : string.toUpperCase()
upper(null) //=> null
upper('abc') //=> 'ABC'
MojiScript:
Maybe
can eliminate the need for most null checks. Again, the syntax is the same as any other call to map
.
import map from 'mojiscript/list/map'
import Just from 'mojiscript/type/Just'
import Nothing from 'mojiscript/type/Nothing'
const upper = map (string => string.toUpperCase())
upper (Nothing) //=> Nothing
upper (Just ('abc')) //=> Just ('ABC')
Some helper methods to easily get you in and out of Maybes
:
import Just from 'mojiscript/type/Just'
import { fromMaybe, fromNullable } from 'mojiscript/type/Maybe'
import Nothing from 'mojiscript/type/Nothing'
fromNullable (null) //=> Nothing
fromNullable ('abc') //=> Just ('abc')
fromMaybe ('') (Nothing) //=> ''
fromMaybe ('') (Just ('abc')) //=> 'abc'
The Maybe
is far to big of a subject to cover here. Fortunately, I have written an entire article on the subject here: NULL, "The Billion Dollar Mistake", Maybe Just Nothing
Asynchronous map
MojiScript's map
also supports asynchronous mapping!
const double = x => x * 2
const asyncDouble = num => new Promise(resolve => {
setTimeout(() => {
console.log({ num })
resolve(double(num))
}, 1000)
})
map (asyncDouble) (range (1) (5))
//=> [2, 4, 6, 8, 10]
Mapping Async Iterables
MojiScript's map
also support async iterators!
const timeout = seconds =>
new Promise(resolve => setTimeout(resolve, seconds))
async function* asyncGen() {
await timeout (1000)
yield 1
await timeout (1000)
yield 2
await timeout (1000)
yield 3
}
const double = x => x * 2
const iter = asyncGen();
map (double) (iter)
//=> Promise([ 2, 4, 6 ])
Summary
MojiScript can map
: Array
, Iterators
, Async Iterators
, Functors
, and Strings
.
MojiScript's map
also supports asynchronous code, which is pretty f'n awesome.
Check out MojiScript. It's pretty awesome! Hop over to the MojiScript Discord chat and say hi!
My articles are very Functional JavaScript heavy, if you need more FP, follow me here, or on Twitter @joelnet!
Top comments (2)
is it not map(func)(values)?
one of your examples shows
map (range (1) (4)) (double) //=> [2, 4, 6]
looks the wrong way round to me. is this an error?
Good catch! I have updated the post. Cheers! 🍻