Loading…

Mint: A new language designed for building single page applications

The JavaScript ecosystem has grown to become unwieldy. Mint promises is a simplified front-end development experience, but not a library or a framework—a new language designed for SPAs.

Article hero image

Learning JavaScript today as a beginner would be a very arduous journey. It’s a twenty-year-old language that was initially designed for a different, simpler web. In trying to meet the new challenges of the modern web, we’ve gotten so many transpilers, bundlers, UI frameworks, and syntax variations; these abstractions are necessary since Vanilla JS can only take you so far. But things are only going to get more confusing as the ecosystem grows and the language evolves. All that growth has led to duplication: There are more than two ways to import and export functions, both incompatible to each other, and two different ways to check equality. You'll have to somehow sift through all the options to pick and choose the tools for your development toolkit:

  • What UI framework should I use?

React, Angular, Vue, Svelte…

  • How do I handle application-wide state?

Context API, Redux, Recoil, Mobx…

  • How about styling ?

styled-components, Sass, Less

  • What about testing ?

Jest, Mocha…

The sheer amount of options is disorienting:

I believe web development needs streamlining—there are more important things to ponder, and it's not Redux vs Mobx.

So when I encountered Mint, I was all ears. What Mint promises is a simplified front-end development experience. It’s not a library like React or a robust framework like Angular. It’s an entirely new language designed just for single page app development. The language is transpiled to JavaScript.

As a web development tool, it has the following built in:

  1. Routing
  2. Styling
  3. Application-wide state management
  4. Components
  5. Testing

The author cites Elm as his main inspiration, and it shows. The language touts very Elm-like characteristics:

  1. Zero runtime errors
  2. Immutable data structures
  3. Static typing

So why should you care about this language ? Why should you try it for your next project ?

Because Mint offers a set of language-level features that is impossible to replicate in JavaScript without resorting to a “heckin chunk” of libraries for the basics.

Mint’s entire executable weighs ~34mb and covers most use cases without resorting to external dependencies. A nice side effect of this is that you never have to leave Mint’s documentation, scouring the web for information on third-party libraries. In fact, the Mint executable contains an offline version of the documentation, which can be opened with a simple terminal command.

Here are some eye-opening features I noticed while using Mint to develop a personal project.

No runtime errors

Mint is a statically-typed language. It draws inspiration from Elm and Haskell and shares their promise of zero runtime exceptions. This means if your Mint code compiles, you can be 100% sure you won’t encounter errors from undefined values. This is arguably its most significant feature.

But before we talk about runtime errors, we’ll first need to go into Mint’s exhaustive type checking. In Mint, you must handle all possible data values. For example, take this switch case statement involving enums:

enum UserType {
Trial
Free
Premium
}


fun greet (type : UserType) : String {
 case(type) {
   UserType::Trial => "You are on Trial"
   UserType::Free => "Upgrade to Premium"
 }
}

This would throw a compilation error, because we did not handle the possibility of type being UserType::Premium.

There aren’t null values in Mint; errors and unexpected values are instead encoded as enums. Functions with the chance of erroring, e.g HTTP requests and JSON.parse return a Maybe or Result enum, which must always be handled exhaustively.

enum Result(error, value) {
 Err(error)
 Ok(value)
} //in-built enum


enum Maybe(value) {
 Nothing
 Just(value)
} //in-built enum


Result(Http.ErrorResponse, Response) //http request, returns either an error or an Ok response
Maybe(Object) //Json.parse, might return Nothing.

Equality

There are two perks of Mint’s equality over JavaScript’s:

  1. There’s only one equality operator, which doesn’t support implicit type coercion and as such doesn’t allow comparison on multiple data types.
  2. Equality in Mint is value-based.

Let’s go over these.

Benefits of having only one equality operator

Mint has only one equality operator (==), which doesn’t allow comparison on multiple data types. JavaScript has two equality operators:

  1. The double equals (==) that compares the value of the two operands regardless of data type. When these operands have different data types, they are implicitly coerced into one.
  2. The triple equals (===) that compares both value and data type of operands.

The double equals is very problematic, and its use is discouraged. 1 and “1” are equal, so is false and 0. These examples might seem somewhat reasonable so let's go a bit further:

  1. "1" and [1] are equal.
  2. “” and false are equal.
  3. “0” and false are equal.
  4. [] and false are equal.
  5. [[]] and false are also equal.

What is this madness? You may ask. Why it’s JavaScript’s equality system. It’s a mess to put things lightly. The double equality sign invokes type coercion that is as hilarious as it’s headache inducing; subtle bugs are sure to crop without conscientious use.

In contrast Mint won’t even allow you to compare two different data types.

What are the benefits of value based equality ?

Say you want to compare objects with nested objects, this is impossible with the equality operator:

const actor1 = {
name: "Tom Cruise",
movies: [{ title: "Mission Impossible" }]
}

const actor1duplicate = {
name: "Tom Cruise",
movies: [{ title: "Mission Impossible" }]
}

const actor2 = {
name: "Daniel Craig",
movies: [{ title: "Spectre" }]
}

actor1 === actor1 //true
actor1 === actor2 //false
actor1duplicate === actor1 //false

The actor1 and actor1duplicate variables clearly have the same values, but JavaScript does not consider them equal. To compare these sort of functions, you’d need to either hand-roll your own recursive function, by using the isDeepStrictEqualfunction from the Node.js util module or by adding a third-party dependency like lodash.

Doing this in Mint is simple:

record Movie {
title: String
}

record Actor {
name: String,
movies: Array(Movie)
}
actor1 = Actor("Tom Cruise", [Movie("Mission Impossible")]) /* constructor syntax */

actor1Duplicate = {
name = "Tom Cruise",
movies = [{title="Mission Impossible"}]
} /* literal syntax */

actor2 = Actor("Daniel Craig", [Movie("Spectre")])

actor1 == actor1 /*true*/
actor1 == actor2 /*false*/
actor1 == actor1Duplicate /*true*/

You might say: but this could be solved without language-level support. Yes, that’s true, but there’s a big difference between language-level support and support using user-defined functions.

To illustrate this here’s another scenario: How do we create an Iterable with unique objects in JavaScript?

This won’t work:

new Set([actor1, actor1Duplicate, actor2])

Running the above code would not remove duplicate actors because each array item in the Set initialization parameter is an Object and JavaScript uses referential equality for non-primitive values. So in JavaScript:

actor1 == actor1Duplicate /*false*/

Multiple questions on this have popped up over the years on Stack Overflow.

The answers are usually very unwieldy ranging from using JSON.stringify(object) to modifying the object’s prototype and using a dependency like lodash.

But since the value equality is a language-level feature in Mint, it means there’s no need for external dependencies and additional complexity. Since the set items are internally compared using their values not references:

actors = Set.fromArray([actor1,actor1Duplicate,actor2])

The deduplication just works, passively, and not as a result of a function explicitly written for this single use case, but instead as a result of how the language was designed.

Language-level implementations also means uniformity. You can expect everyone's code or library to behave similarly since they have access to the same set of data types and their corresponding operations.

Immutability

Immutable variables are unchangeable. Because of this, your application can make guarantees about state. This allows for less bugs, better performance, and easier to debug code.

All data types in Mint are immutable by default:

numbers = [1,2,3]
numbers[0] = 100 /* won't work */
user = {name="user1",email="email@gmail.com"}
user.name = "simdi" /* won't work */


updatedUser = { user | name = "simdi", email="email@gmail.com" }
/* looks similar to the spread operator syntax:
const updatedUser = {...user, name:"simdi",email:"email@gmail.com"}
 */
updatedNumbers = Array.updateAt(0, (){100}, numbers)

Aside from Object.freeze, you’ll need additional third party libraries like immutable.js to use immutable data structures in your JavaScript code.

Functional programming

Mint implements a few functional programming ideas like partial application. Partial application allows you to lazily apply function parameters; you don’t need to call a function with all its arguments. Instead, when you call the function with less arguments than expected. It simply returns a new function with the remaining arguments.

Here’s an example:

// partial application example
fun add(a, b){
a+b
}



add(5,1) == 6
increment = add(1)
decrement = add(-1)
increment(5) == 6
decrement(5) == 4

Partial application allows you to create specialized functions from general purpose ones. Doing this in JavaScript requires going through third party libraries. But Mint handles this without extra libraries.

Partial application amplifies another Mint feature: the pipe operator.

Pipe operator

Take a look at this code:

fun double(x:Number){x*2}
fun add(x, y){}
fun boundScore(min,max, score){}

x = boundScore(0,10, add(5, double(score)))

Mint allows you to simplify it to the following:

fun double(x:Number){x*2}
fun add(x, y){}
fun boundScore(min,max, score){}


x = score
    |> double
    |> add(5) /* partial application */
    |> boundScore(0, 10)

This |> syntax is called a pipe operator, it allows you to “pass” a value along a chain of functions. And if you’ve noticed, the functions double, add and boundScore are partially applied, since they are called with less values than they have. Partial application and the pipe syntax have synergy, and this synergy can’t be replicated with current JavaScript.

To be fair there’s a TC39 proposal for adding a pipeline operator to JavaScript, which would do essentially the same thing as the pipe. However, it’s still in stage one, so unless you install the Babel plugin, you won’t be able to use it as no browser supports it.

Styling

Styles in Mint are declared with a style keyword. These styles are locally scoped to the component. You write styles using regular Sass syntax:

style buttonstyle  {
  padding: 2em;
  color: black;
  background-color:white;

  &:hover {
    background-color:black;
    color: white;
  }

}

These styles can be applied using the “::” expression:

<button::buttonstyle>"I am a button"</button>

Styles in Mint come with a few extra perks: string interpolation and style props. With string interpolation in styles, you can easily embed expressions that evaluate to the string type directly in your CSS definition. Style props allow passing parameters just like a function.

Here’s an example:

style buttonStyle (primary: Bool, color: String, hoverColor: String) {
padding:2em;

color: #{color}; //String interpolation
background-color:white;

if (primary) {
background-color: blue;
}

&:hover {
background-color:black;
color: #{hoverColor};
  }
}

This buttonStyle can be applied to a button element like so:

<button::buttonStyle(true, "black","white")>"I am a primary button"</button>

Doing something similar with React isn’t possible using built-in style objects, because pseudo selectors aren’t supported, unless you use third party styling alternatives like CSSmodules and styled components. Even then, css-modules doesn’t support style props and string interpolation, as this example shows:

/* button.css */
.button {
    padding: 2em;
}
.primary {
    background-color: blue;
}
.button:hover {
    background-color: black;
}



/* button.jsx */
<button className={`${styles.button} ${props.primary ? styles.primary : ''}`}>
This is a button
</button>

Notice with CSS modules there’s no way to dynamically set the hover color based on props.

It’s indeed possible to do this with styled components in this manner:

const Button = styled.button
padding:2em;
color: ${props.color};
background-color: ${props.primary?'blue':'white'};
&:hover {
background-color:black;
color: ${props.hoverColor}

}

<Button color="white" hoverColor="black" primary>This is a button</Button>

There are two observations that can be made with styled components:

  1. It’s a tad bit more verbose than Mint’s solution.
  2. Styled components are tightly coupled to their styles. It’s hard to reuse the same style for other elements.

Multiple styling

Mint allows you to compose styles by chaining “::” expressions. Going back to the previous button example, let’s create an animate style:

style animate (duration:Number){
transition: all #{duration}ms;
} 

<button::buttonStyle(true, "black","white")::animate(500)>
"I am a primary button"
</button>

Implicit imports

Imports in Mint are implicit; you never have to import anything using an import statement. There is no import keyword. This prevents crazy nested imports common in a lot of codebases:

../../../function

.../.../util/function1

It makes relocating any component, function, or module to any folder or file effortless as there aren’t any import statements to resolve.

You might see this as a downside. For one, this lack of verbosity could become confusing with large codebases. Second, it means top-level constructs like components, functions, and modules cannot have the same names. One way to avoid this would be to “namespace” these constructs using modules. Of course, in large codebases, you’ll need to come up with a good naming scheme to avoid module naming collisions.

Conclusion

The current JavaScript ecosystem tries to take concepts from other languages, implementing them as libraries, and when that can’t be done using transpilers like Babel to access next-generation JavaScript features.

This is a testament to JavaScript’s versatility but is also its downfall; it leads to fragmentation, where there are a thousand and one ways to do a single thing.

If Mint catches on (I hope it does), you won’t be pondering whether to use Webpack or Parcel, Redux or Mobx, Styled Components or Sass. Instead, you would use a single language level implementation. Asking a question on SO, you wouldn’t have to say oh I’m using this library for navigation or that library for state management. The answers given would apply widely to others.

Perhaps we're heading towards an era where we think of JavaScript the same way of C—we are aware of it, but we hardly ever code in it, instead we clothe it in Typescript, Elm, ReasonMl ,and Mint, abstracting it away as low level arcana.

I believe Mint’s goal is a noble one, but a thought must be entertained—if it doesn’t catch on would it become the very thing it tried to destroy? Relevant xkcd comic:

If you liked my writing, you could follow me on Medium. Thanks for reading!

Login with your stackoverflow.com account to take part in the discussion.