Mint: A new language designed for building single page applications
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:
- Routing
- Styling
- Application-wide state management
- Components
- Testing
The author cites Elm as his main inspiration, and it shows. The language touts very Elm-like characteristics:
- Zero runtime errors
- Immutable data structures
- 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:
- There’s only one equality operator, which doesn’t support implicit type coercion and as such doesn’t allow comparison on multiple data types.
- 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:
- 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.
- 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” and [1] are equal.
- “” and false are equal.
- “0” and false are equal.
- [] and false are equal.
- [[]] 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 isDeepStrictEqual
function 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:
- It’s a tad bit more verbose than Mint’s solution.
- 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!
Tags: mint, single page application, web development
41 Comments
Do we know why they called it Mint? I used a language called MINT back in the 80’s!
https://archive.org/stream/michaeldgodfrey_gmail_Mint/mint_djvu.txt
Well, they call it a “refreshing language” in their GitHub repository. So, maybe that’s why.
https://github.com/mint-lang/mint
The XKCD at the end is exactly what I was thinking reading through this.
Everything I need to know about development, I learned from xkcd.
An older but similar parable: http://neugierig.org/content/unix/
Yep, exactly this.
“Learning JavaScript today as a beginner would be a very arduous journey.” Only if you try to learn Everything and are trying to become a professional developer. Many people just dabble in programming for real basic stuff, which is just fine for JS. It’s also a decent primer for kids wanting to figure out if they want to become a programmer.
“The sheer amount of options is disorienting:” Again, only if you are trying to learn “everything” to be a professional coder.
And the same things could be said about most “industry standard” language. I work in C#, and there are more NuGet packages, frameworks, testing suites, and more that I’ll ever have heard of. That doesn’t prevent me from doing my job, let alone doing small personal projects, in the language.
All too often, people in software (including beginners) think you have to know literally everything about a language/framework/libraries/etc. to be able to use it. They spend days researching “everything” about it before even using it to see if “they are good enough” to use it. That’s a whole lot of BS. I’ve used over 30 distinct languages/frameworks/libraries/etc. (it’s nearly 40 if you unpack similar languages to their flavors, like SQL, T-SQL, MS SQL, MySQL, Oracle, etc.) over the past nearly 30 years as a student and professional, and I can’t say I know 100% of any of them. Yet I’ve been a professional software dev for over 8 years and done really well at it.
The fact that StackExchange/StackOverflow exists is proof that most (if not all) people don’t know 100% of whatever they are using, yet they are often still professional developers.
Yes, as a professional, you should know quite a bit, but that doesn’t mean you can’t learn a language on the job.
I normally suggest using JS as a first language. It’s easy to get started, learn the basics, and you don’t need specialized software to run it. You can power on a brand new PC and start programming in JS right away. It’s that simple. Yet it’s also able to be powered up to run all kinds of e-commerce sites, including the server API with Node.js, or you can do simple to fairly complex animated graphics with JS. The syntax is also very similar, if not he same, as a large variety of other languages used professionally.
JS isn’t horrible because of how complex it’s become, but rather it’s beautiful (in it’s own way) for it’s flexibility to be as simple or as complex as the dev wants it to be. It’s sort of like that meme you see on social media, where you are give 4-6 different flavors of ice cream to choose keep only 1 from “for life”. I always pick vanilla, since you can add nearly anything to make it any other flavor, which is kind of why it’s call Vanilla JS. Sure JS has it’s flaws, but ice cream gives brain freeze and has high calories, so nothing’s perfect. 😉
This guy knows the truth. Quick, get ‘im! Seriously, though, how many times do we have to talk about imposter syndrome before people start admitting that it’s real? I tell people all the time: I’m not good at my work because I HAVE all the answers. I’m good because I ask the right questions and know where to FIND the answers! I have heard (seen) lots of professional developers mention that they regularly have to Google “how to do in “.
This language flame war is just like what brand of power tools is best. If you can build me a nice deck with a Ryobi drill, then you get the job. Tools are secondary to skills. In the end, the test for me is whether or not I get results.
“It’s also a decent primer for kids wanting to figure out if they want to become a programmer.”
If JavaScript had been my “primer” before C, or C++, or C#, or Java – I would have quit. Literally just spent two hours debugging why a simple header component wouldn’t render in my JS app. I hate this shit.
Welcome to CoffeeScript 2.0.
You could mention also the Ocsigen project. See https://ocsigen.org/
or ReasonML (https://reasonml.github.io/) project as well.
Creating a new langage does not solve a single one of the problem you listed at the beginning of this article.
I was thinking this too
Interesting read, thanks.
I think you have a copy paste error in the Equality section, you’re using the same equality examples with the same result for both JS ant mint:
actor1 === actor1 //true
actor1 === actor2 //false
actor1duplicate === actor1 //false
Could it be you just copy-pasted the equality example and forgot to alter it for Mint? Because as fas as I can tell, they’re the same.
That was my fault. Thanks for the close read.
As per usual, XKCD explains the situation perfectly: https://xkcd.com/927/
The graphic comparing 2009 to 2019 is, aside from being out of date, misleading because of the duplicate icons.
Otherwise, I think this looks like a decent framework! I like its simplicity
It’s such a shame that there are duplicate icons considering how many things are left out. They wouldn’t need duplicates if they’d include Yarn, Jest, Mocha, Svelte, Bower, Grunt, Gulp, Rollup, Parcel, Lodash, Styled Components, Redux, MobX, Jade, Handlebars, Mustache, EJS, CoffeeScript, Deno, the list goes on and on…
Does MINT solve the problem that single page web applications are terrible at SEO? No? I didn’t think so.
Doesn’t SSR solve the SEO problem? Most modern JS frameworks supports SSR.
I’m not talking about first wave vs. second wave indexing. I’m talking about single page web applications vs. deep linking.
> React, Angular, Vue, Svelte
🎶 One of these things is not like the other
Svelte is so different from the other three… Svelte addresses many of the issues brought up in the article and you’ll have a hard time creating a smaller web page than the one you make with Svelte.
I like the code syntax in Mint though, that’s intuitive and readable.
Also, wow, they need to do some scaling: “An error occurred in the application and your page could not be served. If you are the application owner, check your logs for details. You can do this from the Heroku CLI with the command”
That’s not good. Failing due to success :/
The article mentions Elm for inspiration but I think the ‘monolithic simplification of the JS ecosystem’ concept was tried with Meteor.js. Now that wasn’t a language but the idea was similar. That really went nowhere in terms of adoption and was hard to wrap your head around. I wonder if the Mint authors are aware of this?
My biggest concern is performance, especially when it comes to checking equality. What do the performance numbers look like with larger objects? What about an object with a recursive reference?
Speculating on my part as I don’t know how Mint implements it, but theoretically, value comparison for equality has a lot of potential optimizations – especially in a language with immutable data structures.
– If two objects or sub-objects have the same reference, value comparison can be bypassed, they’re known to be equal. You don’t lose any efficiency in these cases.
– If two immutable objects or sub-objects have been compared before, the comparison can be memoized because their values will never change; the second/third/etc. time the same objects get compared can be a simple lookup of the result.
– Just as some languages create/deduplicate multiple references to the same string literal, immutable objects that are made by some kind of constructor for that object type could return references to the same one when the same values are passed for its inputs/components (again, memoization); then they become reference-equal, bypassing the need for actual value comparison.
In other words, not only does immutability mean that repeated operations on the same data can be memoized, but a lot of the difference between value comparison and reference comparison falls out of mutability in the first place.
Pretty sure Kotlin has all these features, including being able to transpile to JS. Why not simply extend that with the syntactic sugar instead of inventing yet another language?
Agreed, I feel like this article doesn’t dive deep enough into the “Why?” of a new language here. Elm is mentioned a few times, and I’m having a really hard time seeing what Mint offers that Elm doesn’t. And yeah, I think Kotlin is ultimately the way to go for the future of web development.
I agree with you about the double equals operator being madness, and I love a good overhaul, but I have to ask, isn’t the behavior you described a lot like the “not” operator in Python? (which I don’t know about everyone else, but I love “not”)
Why not use Elm? Not enough curly braces?
Second the question, what’s the value Mint brings over Elm? Also, how’s LSP support and what’s the JS/WebComponents interconnection story?
Because ELM is as good as dead. Development has stopped and it makes little sense to use it at this point. This is a bit the concern with MINT, looks interesting but will it survive?
> Because ELM is as good as dead
This isn’t true, the core team might be too opinionated to add new features but the community is very alive.
The runtime is *only* 34MB? I hope that you meant 34KB?
1) There is zero need for this language.
2) The syntax is wrong.
3) It moves towards a wrong direction technologically. WebAssembly and other technologies should be the future. The times of new languages and JS frameworks are over.
JavaScript development is far away from simple – for me, it seems like everyone who creates a new library or tool thinks hard about “how can I make it as different to anything else as possible?”. Trying to write a little bit of code in Typescript, we have to think about what editor (VSCode is my favorite), what module system, how to bundle, how to serve as http and how to debug. Then we need to think about Angular, Vu or other frameworks and install 114 MB of node modules (yes, my current fun project without any framework and just some simple libraries contains 114 MB of node modules). It’s not that you cannot manage all of that (many developers do it and many do it good), but a standard would be great – something that I can rely on and when I want to use component A, I don’t want to learn how to make component A work with my framework, just because it has been designed to be used in another framework.
TypeScript was my hope of a language that not only provides modern features to a language that was invented in just 1 week by one man just because Microsoft otherwise would have dominated the web with VBScript, but I was hoping that around TypeScript there would be standard a framework of concepts to build SPAs and small helper scripts for other html applications. I guess Mint will share the same problem: too many developers with very different ideas about what will be cool to set up and not enough time to find the solution that is already there. The “not-invented-by-me”-syndrome will take over, and we will get many solutions that solve “my very special problem” of adding up two numbers in a completely new way – just because I always wanted to invent a new framework, and it’s fun to do things in a way nobody can understand until she/he reads my blog and dives deep into the rabbit hole to just add some business-logic to the web page.
I was turned off as soon as the article said that Mint is statically typed. Dynamic typing is the whole beauty of JavaScript; it’s a scripting language after all, and it’s what gives it flexibility. With static typing tends to come boilerplate code.
Depends entirely how good the type inference is and how opinionated the type system is about nominal typing (“an X is a thing that inherits from X”) vs. structural typing (“does it fit what we want to use”, sometimes colloquially called “duck typing”). The boilerplate-heavy languages of the past tended to be nominally typed with little or no inference. That ends up with obvious situations where you’ve got to write a lot of code just to satisfy the type checker. The ones that have survived have moved towards adding degrees of type inference and supporting more structural typing over time. (For example, C++’s “auto” type type specifier and its structurally typed templates; even templates tended to get lots of type boilerplate back in the day, but I believe that’s reduced somewhat in combination with “auto” and should reduce further with “concepts”.) More to the point here: there are static languages (mostly either functional languages or newer languages) that have always been light on boilerplate that are built from the ground up for: static typing + good inference + first-class support for both nominal and structural approaches as appropriate.
[sorry for late reply!]
Web App Development architecture should be like “Android Development”, and Software development.
1. There should be only 2 different languages for whole development 1) Programming language, 2) Markup language.
2. There should be only one standard structure and framework, which should be followed like “De-facto” standard in Web development. there should be a libraries, but all libraries should be written in a single standard programming language.
3. Proper code-behind model should be provided. code for UI should be totally separated from code for “software logic and operations. Programming language should never include “Mark-up” and Mark-up should never include “Programming languages”.
4. There should be a universal and standard way to bind data to the UI, both at run-time, and build-time.
Any language, or platform which includes all above features, will be a future of web-development.