DEV Community

loading...

Getting started with MojiScript: FizzBuzz (part 1)

JavaScript Joel
Cofounded Host Collective (DiscountASP.net). Cofounded Player Axis (Social Gaming). Computer Scientist and Technology Evangelist with 20+ years of experience with JavaScript!
Updated on ・6 min read

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!

Discussion (7)

Collapse
jochemstoel profile image
Jochem Stoel • Edited

Where/how exactly do you let sleep not require an async/await function? Your sleep is identical to mine but mine requires you to await it, never found a way around that. Neither mojiscript/threading/sleep or mojiscript/core/pipe exports anything async.

const sleep = require('mojiscript/threading/sleep')
const pipe = require('mojiscript/core/pipe')
const log = console.log
const range = require('mojiscript/list/range')
const map = require('mojiscript/list/map')

const sleepAndLog = pipe([
    sleep(1000), // why this be work??
    log
])
pipe([
    ({ start, end }) => range(start)(end),
    map(sleepAndLog)
])({start: 0, end: 10})

Secondly, do you deliberately not comment your exports or is it lazyness? I can help you with that. Most looks pretty straightforward.

And last, maybe you should define a main property in your package.json so that mojiscript can be required the conventional way.

I also want to suggest you stop calling this a language but that is just my opinion and really up to you.

Collapse
joelnet profile image
JavaScript Joel Author

Where/how exactly do you let sleep not require an async/await function?

MojiScript's pipe, map, filter, and reduce work with both async and sync code. They do not need to await anything.

You could look at call, which will execute with a function after testing if the value is or is not a Promise. github.com/joelnet/MojiScript/blob...

Your sleep is identical to mine but mine requires you to await it, never found a way around that.

Post your code, I'll figure out the difference.

Secondly, do you deliberately not comment your exports or is it lazyness?

I'm still looking for a solution for auto-generated documentation. So I am holding off on putting docs directly into the code because the format may differ based on the tool I select. I have posted about the problems I am having here: Let's talk about the state of auto-generated documentation tools for JavaScript

There are full docs for MojiScript here: github.com/joelnet/MojiScript/blob...

And last, maybe you should define a main property in your package.json so that mojiscript can be required the conventional way.

I thought about this, but decided not to. I don't want the entire library being imported into your project. Only the functions you need will be imported to keep the size down.

I also want to suggest you stop calling this a language but that is just my opinion and really up to you.

This has been discussed in the Discord chat. More on the reasons why will be written shortly.

Collapse
jochemstoel profile image
Jochem Stoel

Joel, I would like to point out that a main.js does not prevent you from importing only the functions you need. It doesn't matter.

First of all, import statements will still work.

// this does not care about main.js in package.json
import pipe from 'mojiscript/core/pipe'

Secondly you can also require selectively only the stuff you need, exactly like import.

const { readFileSync, mkdir } = require('fs')
// or in your case
const { pipe, sleep, log } = require('mojiscript')
Thread Thread
joelnet profile image
JavaScript Joel Author

The design decision to create every function as it's own import was made to remove a footgun.

The footgun removed is the ability to import the entire library when you only need a few functions.

This way, the output bundle sir is only as big as the functions you import.

So MojiScript will have a microscopic footprint in your final output bundle because it is no longer possible to import the entire bundle.

Thread Thread
jochemstoel profile image
Jochem Stoel

I made a script called mojifier that converts your module into a single object with everything included / proper namespaces by iterating the directories/files.

Collapse
jorge_rockr profile image
Jorge Ramón

Amazing!

Just one question, does it has a better performance than normal JavaScript for Node.js?

Or it is just for doing functional coding style?

Collapse
joelnet profile image
JavaScript Joel Author • Edited

Thanks, but you are asking the wrong question!

It functions better than JavaScript.

Look at this JavaScript code. It is complicated to understand the output. This code outputs all give 6's after 1 second.

for (var i = 1; i < 6; i++) {
  setTimeout(() => console.log(i), 1000)
}
// => 6
// => 6
// => 6
// => 6
// => 6

Now look at the MojiScript alternative. This program will output 1 through 5 with 1 second delay between each output. (imports have been excluded for brevity)

const sleepThenLog = pipe ([
  sleep (1000),
  log
])

const main = pipe ([
  ({ start, end }) => range (start) (end),
  map (sleepThenLog)
])

main ({ start: 1, end: 6 })
// => 1
// => 2
// => 3
// => 4
// => 5

Did you notice that? Did we just create an asynchronous map? Shhh. articles coming out on Asynchronous map, filter, and reduce shortly. Stay tuned!