44 releases (6 breaking)

0.9.14 Oct 23, 2024
0.9.3 Sep 27, 2024
0.9.1 Jul 3, 2024
0.7.3 Mar 22, 2024

#30 in Programming languages

Download history 218/week @ 2024-09-13 100/week @ 2024-09-20 474/week @ 2024-09-27 72/week @ 2024-10-04 880/week @ 2024-10-11 152/week @ 2024-10-18 23/week @ 2024-10-25 4/week @ 2024-11-01 2/week @ 2024-11-08 142/week @ 2024-11-29 1433/week @ 2024-12-06

1,575 downloads per month

MIT/Apache

1.5MB
11K SLoC

mers

cargo install mers

Mers is a simple, safe programming language.

features

  • mers' syntax is simple and concise
  • mers is type-checked, but behaves almost like a dynamically typed language
  • it has no nulls or exceptions
  • references in mers are explicit: &var vs. just var
  • no gotos (or breaks or returns)
  • locking (useful for multithreading, any reference can be locked)

examples

Hello, World!

image

In mers, .function is the syntax used to call functions. Everything before the . is the function's argument. In this case, our argument is the string containing Hello, World!,

Variables

image

We use name := value to declare a variable, in this case my_var. We can then simply write my_var whenever we want to use its value.

If

image

An if is used to conditionally execute code. Obviously, since our condition is always true, our code will always run.

The condition in an if has to be a bool, otherwise...

image

Else

image

We can add else directly after an if. This is the code that will run if the condition was false.

Using If-Else to produce a value

Depending on the languages you're used to, you may want to write something like this:

var result
if (condition) {
  result = "Yay"
} else {
  result = "Nay"
}

But in mers, an if-else can easily produce a value:

image

We can shorten this even more by writing

image

What if the branches don't have the same type?

Rust also allows us to return a value through if-else constructs, as long as they are of the same type:

if true {
  "Yep"
} else {
  "Nay"
}

But as soon as we mix two different types, it no longer compiles:

if true {
  "Yep"
} else {
  5 // Error!
}

In mers, this isn't an issue:

image

The variable result is simply assigned the type String/Int, so mers always knows that it has to be one of those two.

We can see this if we add a type annotation:

image

Obviously, the if-else doesn't always return an Int, which is why we get an error.

Using If without Else to produce a value

If there is no else branch, mers obviously has to show an error:

image

Or so you thought... But no, mers doesn't care. If the condition is false, it just falls back to an empty tuple ():

image

Sum of numbers

image

Sum of something else?

If not all of the elements in our numbers tuple are actually numbers, this won't work. Instead, we'll get a type-error:

image

Loops

image

This program asks the user for a number. if they type a valid number, it prints that number. If they don't type a valid number, they will be asked again.

This works because parse_float returns ()/(Float), which happens to align with how loops in mers work:

A loop will execute the code. If it is (), it will execute it again. If it is (v), the loop stops and returns v:

image

With this, we can loop forever:

image

We can implement a while loop:

image

Or a for loop:

image

The else (()) tells mers to exit the loop and return () once the condition returns false.

Functions

Functions are expressed as arg -> something, where arg is the function's argument and something is what the function should do. It's usually convenient to assign the function to a variable so we can easily use it:

image

Since functions are just normal values, we can pass them to other functions, and we can return them from other functions:

image

Here, do_twice is a function which, given a function, returns a new function which executes the original function twice. So, add_one.do_twice becomes a new function which could have been written as x -> x.add_one.add_one.

Of course, this doesn't compromise type-safety at all:

image

Mers tells us that we can't call add_two with a String, because that would call the func defined in do_twice with that String, and that func is add_one, which would then call sum with that String and an Int, which doesn't work.

The error may be a bit long, but it tells us what went wrong. We could make it a bit more obvious by adding some type annotations to our functions:

image

Advanced variables

In mers, we can declare two variables with the same name:

image

As long as the second variable is in scope, we can't access the first one anymore, because they have the same name. This is not the same as assigning a new value to x:

image

The second x only exists inside the scope created by the code block ({), so, after it ends (}), x refers to the original variable again, whose value was not changed.

To assign a new value to the original x, we have to write &x =:

image

References

Writing &var returns a reference to var. We can then assign to that reference:

image

... or:

image

We aren't actually assigning to ref here, we are assigning to the variable to which ref is a reference. This works because the left side of an = doesn't have to be &var. As long as it returns a reference, we can assign to that reference:

This is used, for example, by the get_mut function:

image

Here, we pass a reference to our list (&list) and the index 0 to get_mut. get_mut then returns a ()/(&{Int/String}) - either nothing (if the index is out of bounds) or a reference to an element of the list, an Int/String. If it is a reference, we can assign a new value to it, which changes the list.

Multithreading

(...)


Note: all of the pictures are screenshots of Alacritty after running clear; mers pretty-print file main.mers && echo $'\e[1;35mOutput:\e[0m' && mers run file main.mers.

Dependencies

~1–11MB
~74K SLoC