An introduction to MojiScript's enhanced map

JavaScript Joel - Oct 30 '18 - - Dev Community

World map

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]
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Making this change opens up the possibility to easily compose new functions.

const newFunc = map (func) // a new function is born!
newFunc (values)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Unfortunately, JavaScript's map doesn't support Iterators.

range (1) (4)
  .map (double) //=> Error: map is not a function​​
Enter fullscreen mode Exit fullscreen mode

Even if we do some sorcery, we can't get it to work...

Array.prototype.map.call(range (1) (4), double) //=> []
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

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']
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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')
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

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 ])
Enter fullscreen mode Exit fullscreen mode

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!

Cheers!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .