Getting started with MojiScript: FizzBuzz (part 1)

JavaScript Joel - Sep 27 '18 - - Dev Community

4 people looking at laptop

What is MojiScript

MojiScript is an async-first, opinionated, and functional language designed to have 100% compatibility with JavaScript engines.

Because MojiScript is written async-first, asynchronous tasks not only become trivial, but become a pleasure to use. Read this article for more on async in MojiScript: Why async code is so damn confusing (and a how to make it easy).

MojiScript is also JavaScript engine compatible, meaning it runs in node.js and browsers without the need to transpile!

Even though it runs in any JavaScript engine, you will notice significant differences between MojiScript and JavaScript.

Significant differences you say?

Well, JavaScript as you know it will not run. But other than that...

screenshot of JavaScript errors

It's best to forget everything you know about JavaScript when learning MojiScript.

But don't worry, there are easy ways to interop with JavaScript. We won't be convering JavaScript iterop in this article though.

FizzBuzz

You should already be familiar with FizzBuzz. If not, the game is simple:

Write a program that prints the numbers from 1 to 100. But for multiples of three print "Fizz" instead of the number and for the multiples of five print "Buzz". For numbers which are multiples of both three and five print "FizzBuzz". -- FizzBuzz Test

Start

We'll be writing a node application, so I am assuming you already have node and git installed.

Make sure you have node v10.9.0+

Install the mojiscript-starter-app

git clone https://github.com/joelnet/mojiscript-starter-app.git
cd mojiscript-starter-app
Enter fullscreen mode Exit fullscreen mode

Make sure it builds and runs.

npm ci
npm run build
npm start --silent
Enter fullscreen mode Exit fullscreen mode

If everything went well you should see:

Hello World
Enter fullscreen mode Exit fullscreen mode

Format on Save

Visual Studio Code is highly recommended as your editor. It adds some nice features like Format on Save.

If there's another IDE you love that doesn't Format on Save, then you can just run this command:

npm run watch
Enter fullscreen mode Exit fullscreen mode

This is important because your app will not build if your formatting is off.

You can also run this command to fix your formatting.

npm run build -- --fix
Enter fullscreen mode Exit fullscreen mode

It's little things like this that make MojiScript such a joy to code in!

Files

There are two files that are important:

src/index.mjs - Load dependencies and start app.
src/main.mjs - Your app without dependencies makes it easy to test.

note: We are using the .mjs file extension so that we can use node --experimental-modules which gives us the ability to import and export without transpiling.

Open up src/main.mjs That is where we will start.

It should look like this:

import pipe from 'mojiscript/core/pipe'

const main = ({ log }) => pipe ([
  'Hello World',
  log
])

export default main
Enter fullscreen mode Exit fullscreen mode

Let's write some code!

First let's create a loop from 1 to 100.

Import these two functions:

  • range - Creates an Iterable from start to end.
  • map - Maps over an Iterable.
import range from 'mojiscript/list/range'
import map from 'mojiscript/list/map'
Enter fullscreen mode Exit fullscreen mode

Modify your main to look like this:

const main = ({ log }) => pipe ([
  () => range (1) (101),
  map (log)
])
Enter fullscreen mode Exit fullscreen mode

run your app and you should see the console output numbers 1 to 100.

Next, I'd like to get rid of those "magic numbers" 1 and 100. You shouldn't be hard-coding values directly into your source, at least not in src/main.mjs. You can however put those values in src/index.mjs since it's responsibility is to load and inject dependencies and add configuration.

So open up src/index.mjs add those numbers to a new value state.

const state = {
  start: 1,
  end: 100
}
Enter fullscreen mode Exit fullscreen mode

Add the state to the run command

run ({ dependencies, state, main })
Enter fullscreen mode Exit fullscreen mode

Now src/index.mjs should look like this:

import log from 'mojiscript/console/log'
import run from 'mojiscript/core/run'
import main from './main'

const dependencies = {
  log
}

const state = {
  start: 1,
  end: 100
}

run ({ dependencies, state, main })
Enter fullscreen mode Exit fullscreen mode

Go back to src/main.mjs and modify main to use start and end.

const main = ({ log }) => pipe ([
  ({ start, end }) => range (start) (end + 1),
  map (log)
])
Enter fullscreen mode Exit fullscreen mode

run npm start again to make sure it works.

Let's break to talk about Pipes

I would like to talk a little bit about pipe and how it works. Imagine data moving through the pipe and being transformed (or Morphed) at each step of the way.

With this pipe, if we were to pass a 4 through it, it will be morphed into a 9, then an 18. log performs a side effect and doesn't morph the value at all so the 18 would be returned from the pipe.

const main = pipe ([
  //         |
  //         | 4
  //         ▼ 
  /*-------------------*/
  /**/  x => x + 5,  /**/
  /*-------------------*/
  //         |
  //         | 9
  //         ▼
  /*-------------------*/
  /**/  x => x * 2,  /**/
  /*-------------------*/
  //         |
  //         | 18
  //         ▼
  /*-------------------*/
  /**/      log,     /**/
  /*-------------------*/
  //         |
  //         | 18
  //         ▼
])
Enter fullscreen mode Exit fullscreen mode

So in our FizzBuzz example, we start with { start: 1, end: 100 } morph that into an Iterable of 1 to 100 and then log each value. The pipe would return an array of 1 to 100.

Back to FizzBuzz

So far all we have done is create an array. We still have to calculate the Fizziness of each number.

import allPass from 'mojiscript/logic/allPass'
import cond from 'mojiscript/logic/cond'

const isFizz = num => num % 3 === 0
const isBuzz = num => num % 5 === 0
const isFizzBuzz = allPass ([ isFizz, isBuzz ])

const fizziness = cond ([
  [ isFizzBuzz, 'FizzBuzz' ],
  [ isFizz, 'Fizz' ],
  [ isBuzz, 'Buzz' ],
  [ () => true, x => x ]
])
Enter fullscreen mode Exit fullscreen mode

isFizzBuzz is true if both isFizz and isBuzz are true.

cond is similar to JavaScript's switch. It compares either a function or a value and then will execute a function or a value. The last condition [ () => true, x => x ] will always return true and then will return the value passed into fizziness. This is the default case.

Finally, add the fizziness morphism to your main

const main = ({ log }) => pipe ([
  ({ start, end }) => range (start) (end + 1),
  map (fizziness),
  map (log)
])
Enter fullscreen mode Exit fullscreen mode

Function Compostion

You may have noticed map being called twice. We are looping through 1 to 100 twice. It's not a big deal here because 100 iterations is microscopic. But other applications this might be important.

We can compose fizziness and log together using a pipe and modify our main to use our new logFizziness function.

// logFizziness :: Function -> Number -> Number
const logFizziness = log => pipe ([
  fizziness,
  log
])

const main = ({ log }) => pipe ([
  ({ start, end }) => range (start) (end + 1),
  map (logFizziness (log))
])
Enter fullscreen mode Exit fullscreen mode

Now we are iterating through the iterator only once.

Our final src/main.mjs should look like this:

import cond from 'mojiscript/logic/cond'
import pipe from 'mojiscript/core/pipe'
import map from 'mojiscript/list/map'
import range from 'mojiscript/list/range'
import allPass from 'mojiscript/logic/allPass'

const isFizz = num => num % 3 === 0
const isBuzz = num => num % 5 === 0
const isFizzBuzz = allPass ([ isFizz, isBuzz ])

const fizziness = cond ([
  [ isFizzBuzz, 'FizzBuzz' ],
  [ isFizz, 'Fizz' ],
  [ isBuzz, 'Buzz' ],
  [ () => true, x => x ]
])

const logFizziness = log => pipe ([
  fizziness,
  log
])

const main = ({ log }) => pipe ([
  ({ start, end }) => range (start) (end + 1),
  map (logFizziness (log))
])

export default main
Enter fullscreen mode Exit fullscreen mode

Part 2

In part 2 I'm going to go over asynchronous mapping, Infinity, reduce, unit testing and more! This is where MojiScript really starts to get fun!

Follow me here, or on Twitter @joelnet so you do not miss Part 2!

End

Did you notice you just learned currying, partial application, function composition, functors, and category theory? Gasp! Of course not. That's because we were having too much fun!

If you thought MojiScript was fun, give it a star https://github.com/joelnet/MojiScript! If you have questions, put em in the comments!

Do not miss Part 2 where I reveal the mysteries of life!

Read my other articles:

Why async code is so damn confusing (and a how to make it easy)

How I rediscovered my love for JavaScript after throwing 90% of it in the trash

Cheers!

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