Getting started with MojiScript: FizzBuzz (part 1)
JavaScript Joel
Posted on September 27, 2018
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...
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
Make sure it builds and runs.
npm ci
npm run build
npm start --silent
If everything went well you should see:
Hello World
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
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
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
Let's write some code!
First let's create a loop from 1 to 100.
Import these two functions:
-
range
- Creates an Iterable fromstart
toend
. -
map
- Maps over an Iterable.
import range from 'mojiscript/list/range'
import map from 'mojiscript/list/map'
Modify your main to look like this:
const main = ({ log }) => pipe ([
() => range (1) (101),
map (log)
])
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
}
Add the state to the run
command
run ({ dependencies, state, main })
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 })
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)
])
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
// ▼
])
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 ]
])
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)
])
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))
])
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
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
Posted on September 27, 2018
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
October 8, 2018