MojiScript
MojiScript is an async-first, opinionated, and functional library
Install / Use
/learn @joelnet/MojiScriptREADME
MojiScript


MojiScript is an Async First, opinionated, and functional library and language designed to have 100% compatibility with JavaScript engines. This will allow full access to JavaScript modules (NPM) and all tooling already available to JavaScript. This means that MojiScript language features can run in any JavaScript application and vice-versa.
MojiScript's design is derived from Functional Programming concepts such as Currying, Partial Application, Function Composition, Category Theory, and Atomic Design.
NEW: Join the discussion in the MojiScript Discord chat!
Environment Support
This library supports ES6. If you need to support ES5, you will need to transpile it with Babel.
Table of Contents
- Philosophy
- Benefits
- Quickstart
- Examples
- Star Wars Console - Check this out!
- Express Hello World - Express "Hello World" web server.
- Express Static File Server - Express static file server.
- Async Simple
- Hello World
- Conditionals
- map/filter/reduce
- FizzBuzz
- Recursion
- API Documentation
- Style Guide
- Complementary Libraries
- Variables
- Objects
- String Templates
- Expressions
- Pipes
- Conditionals
- Error Handling
- Morphisms
- Application Layout
- Unit Tests
- Previous Art
- Contributors
Philosophy
The MojiScript philosophy is to provide a functional-style application framework, making asynchronous tasks intuitive and easy.
MojiScript is heavily opinionated and prevents code considered to be async-unfriendly like for loops and statement blocks.
Benefits
- The Asynchronous-first design greatly simplifies writing and reasoning about Asynchronous code. Worry less about callbacks, promises, async, await, etc.
- Atomic Design, function composition, and Pipes encourages maximum code re-use, testability and the ability to compose smaller functions into larger ones.
- Compatibility with ECMAScript gives our applications full access to the JavaScript ecosystem. It also allows us to import elements from MojiScript into existing JavaScript applications.
- A modular design allows for features to be imported on an as needed basis, keeping packages small.
- Plays well with functional libraries. Check out the Complementary Libraries section for libraries that can benefit your MojiScript applications.
Quickstart
Clone the starter app.
git clone https://github.com/joelnet/mojiscript-starter-app.git
cd mojiscript-starter-app
Install, build, and run
npm ci
npm run build
npm start
If everything works, you should see:
Hello World
If your editor does not format on save, you can run the following command:
npm run watch
Style Guide
All values must be declared with const.
// BAD
let value = {}
// GOOD
const value = {}
Variables should be named in lower camel case.
// BAD
const AddNumbers = x => y => x + y
// GOOD
const addNumbers = x => y => x + y
Expressions or Pipes and their arguments should be separated with a space. Arguments should be surrounded with parentheses. Further discussed at #438.
// BAD
add(1)(2)
// GOOD
add (1) (2)
Prefer String Templates
// BAD
const func = x => `Value: ${x}`
// GOOD
const func = $`Value: ${0}`
// BAD
const func = ({ prop }) => `Prop: ${prop}`
// GOOD
const func = $`Prop: ${'prop'}`
Following Atomic Design principles, code should be broken down into Atoms. This maximizes reusability, testability, composability, and readability.
// BAD
const getOrdersText = ifElse (({ length }) => length > 0) ($`${0} orders`) ($`No Orders`)
// GOOD
const hasOrders = ({ length }) => length > 0
const getOrdersText = ifElse (hasOrders) ($`${0} orders`) ($`No Orders`)
// GOOD
const hasOrders = ({ length }) => length > 0
const ifHasOrders = ifElse (hasOrders)
const getOrdersText = ifHasOrders ($`${0} orders`) ($`No Orders`)
ifElse and the condition should be on the same line. Longer statements can be broken out into multiple lines. If it is long, consider breaking it down further.
// BAD
ifElse
(lessThan0)
(Math.abs)
(Math.sqrt)
// GOOD
ifElse (lessThan0) (Math.abs) (Math.sqrt)
// GOOD
ifElse (lessThan0)
(Math.abs)
(Math.sqrt)
Pipes must be multi-line.
// BAD
const main = pipe ([ add ])
// GOOD
const main = pipe ([
add
])
Arrays must have a space after the opening bracket and before the closing bracket.
// BAD
const array = [1, 2, 3]
// GOOD
const array = [ 1, 2, 3 ]
No semi-colons.
// BAD
const value = 888;
// GOOD
const value = 888
Complementary Libraries
- Sanctuary - Recommended collection of useful functions.
- Ramda - Another recommended collection of useful functions.
- List - 🐆 An immutable list with unmatched performance and a comprehensive functional API. (use
list/curried)
Variables
Variables are constant.
// constant
const path = './data'
path = './hello'
//=> Error("Path is read-only")
Objects declared with const are now also immutable!
const state = {
// mutable
count: 0
}
state.count = 1
//=> error Unallowed reassignment fp/no-mutation
A variable can be a value (Number, String, Object), an Expression, or a Pipe.
Objects
Objects are plain data objects.
const cat = {
name: 'mojo',
dob: Date.parse('July 14, 2009'),
weight: 14
}
note: Objects may contain functions, but those functions will not have a reference to the object itself. Behavior and data should be decoupled.
String Templates
String Templates make strings a joy to work with.
import $ from 'mojiscript/string/template'
const searchTemplate = $`Searching for: "${0}"`
const nameTemplate = $`${'first'} ${'last'}`
searchTemplate ('Skywalker') //=> 'Searching for: "Skywalker"'
nameTemplate ({ first: 'Luke', last: 'Skywalker' }) //=> 'Luke Skywalker'
Have a look at the Axios Example for more on how String Templates can improve your code.
Expressions
Expressions can be compared to a synchronous function that takes 1 argument and returns 1 argument.
const increase = x => x + 1
Multiple Arguments
Multiple arguments can be simulated a couple different ways.
Currying and Closures
const add = x => y => x + y
add (3) (4) //=> 7
Objects
const add = ({ x, y }) => x + y
add ({ x: 3, y: 4 }) //=> 7
Arrays
const add = ([x, y]) => x + y
add ([3, 4]) //=> 7
Compound Expressions
Compound expressions combine multiple expressions. The last expression will return the value of the Compound Expression. This is typically done to handle side effects.
const tap = func => value => (
func(value),
value
)
Pipes
Pipes can be compared to an asynchronous function that takes 1 argument and returns 1 argument.
Each pipe can contain multiple Pipes or Expressions. A Pipe will return the result of the final Pipe or Expression.
// increase :: Number -> Number
const increase = pipe ([
x => x + 1
])
increase (1) //=> 2
Pipes are a stream of data
A Pipe should be viewed as a stream of data, that performs Morphisms (or transformation between Categories) along each step.
import pipe from 'mojiscript/core/pipe'
import run from 'mojiscript/core/run'
import log from 'mojiscript/console/log'
const state = 4
const main = pipe ([
// |
// | 4
// ▼
/*-------------------*/
/**/ x => x + 5, /**/
/*-------------------*/
// |
// | 9
// ▼
/*-------------------*/
/**/ x => x * 2, /**/
/*-------------------*/
// |
// | 18
// ▼
/*-------------------*/
/**/ log, /**/
/*-------------------*/
// |
// | 18
// ▼
])
run ({
