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 |
#19 in Programming languages
529 downloads per month
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. justvar
- no
goto
s (orbreak
s orreturn
s) - locking (useful for multithreading, any reference can be locked)
examples
Hello, World!
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
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
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...
Else
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:
We can shorten this even more by writing
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:
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:
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:
Or so you thought... But no, mers doesn't care. If the condition is false, it just falls back to an empty tuple ()
:
Sum of numbers
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:
Loops
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
:
With this, we can loop forever:
We can implement a while loop:
Or a for loop:
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:
Since functions are just normal values, we can pass them to other functions, and we can return them from other functions:
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:
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:
Advanced variables
In mers, we can declare two variables with the same name:
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:
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 =
:
References
Writing &var
returns a reference to var
.
We can then assign to that reference:
... or:
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:
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