mirror of
https://github.com/Dummi26/mers.git
synced 2025-03-10 14:13:52 +01:00
updated readme. again.
This commit is contained in:
parent
29bc366439
commit
16258c7a0a
289
README.md
289
README.md
@ -1,27 +1,278 @@
|
|||||||
# mers 
|
# mers 
|
||||||
|
|
||||||
Mers is an experimental programming language inspired by high-level and scripting languages, but with error handling inspired by rust.
|
Mers is a simple and reliable programming language.
|
||||||
|
|
||||||
|
While mers can already be used right now,
|
||||||
|
many things about it will still change -
|
||||||
|
at least until 1.0.0 is released.
|
||||||
|
For serious projects, this probably
|
||||||
|
isn't the language you should be using.
|
||||||
|
|
||||||
## Why mers?
|
## Why mers?
|
||||||
|
|
||||||
mers has...
|
### it doesn't crash
|
||||||
|
|
||||||
- the type-safety of statically typed languages
|
If your program starts, **it won't crash**.
|
||||||
+ mers will not crash unless you `exit()` with a nonzero exit code or write flawed assumptions using the builtin `.assume*()` functions (Rust's `unwrap` or `expect`)
|
The only exceptions to this are
|
||||||
- the flexibility of dynamically typed languages
|
the builtin `exit()` function, which can exit with a nonzero exit code
|
||||||
+ `x := if condition() { "my string" } else { 12 } // <- this is valid`
|
and the builtin `assume` functions, similar to Rust's `unwrap()`.
|
||||||
- "correctness" (this is subjective and I'll be happy to discuss some of these decisions with people)
|
|
||||||
+ there is no `null` / `nil`
|
This is because **errors in mers are values**,
|
||||||
+ all references are explicit: if you pass a list by value, the original list will *never* be modified in any way.
|
and the compiler forces you to handle them.
|
||||||
+ errors are normal values! (no special treatment)
|
|
||||||
- a flexible type system to easily represent these errors and any complex structure including recursive types:
|
### type system
|
||||||
+ nothing: `[]` (an empty tuple)
|
|
||||||
+ a string: `string`
|
In mers, a type can consist of multiple types:
|
||||||
+ two strings: `[string string]` (a tuple)
|
|
||||||
+ many strings: `[string ...]` (a list)
|
```
|
||||||
+ Either a string or nothing (Rust's `Option<String>`): `string/[]`
|
// bool
|
||||||
+ Either an int or an error: (Rust's `Result<isize, String>`): `int/string` (better: `int/Err(string)`)
|
half := true
|
||||||
- compile-time execution through (explicit) macro syntax: `!(mers {<code>})` or `!(mers "file")`
|
// float/int
|
||||||
|
number := if half { 0.5 } else { 1 }
|
||||||
|
|
||||||
|
// some math functions that work with int and float
|
||||||
|
fn absolute_value(num int/float) {
|
||||||
|
if num >= 0 { num } else { 0 - num }
|
||||||
|
}
|
||||||
|
fn difference(a int/float, b int/float) {
|
||||||
|
absolute_value(a - b)
|
||||||
|
}
|
||||||
|
|
||||||
|
debug(difference(0.2, 2)) // float: 1.8
|
||||||
|
// although difference can work with floats, since we only give it ints, it also returns an int.
|
||||||
|
// this is known at compile time.
|
||||||
|
debug(difference(20, 5)) // int: 15
|
||||||
|
```
|
||||||
|
|
||||||
|
With this, you can write code like you would in dynamically typed languages:
|
||||||
|
|
||||||
|
```
|
||||||
|
user_input := if true { "some text input" } else { 19 }
|
||||||
|
```
|
||||||
|
|
||||||
|
But you get the compile-time checks you love from statically typed ones:
|
||||||
|
|
||||||
|
```
|
||||||
|
if user_input >= min_age {
|
||||||
|
println("welcome!")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Because `user_input` could be a string, the code above won't compile.
|
||||||
|
|
||||||
|
### it's simple
|
||||||
|
|
||||||
|
Almost everything in mers is built around this type system.
|
||||||
|
This eliminates many concepts you may be familiar with:
|
||||||
|
|
||||||
|
#### instead of null or exceptions, just add `[]` or the error(s) to the return type
|
||||||
|
|
||||||
|
```
|
||||||
|
// returns `[]` instead of dividing by zero
|
||||||
|
fn div_checked(a int, b int) -> int/[] {
|
||||||
|
if b != 0 {
|
||||||
|
a / b
|
||||||
|
} else {
|
||||||
|
[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the file content as a string.
|
||||||
|
// if the file can't be read, returns IoError and an error message.
|
||||||
|
// if the file's contents weren't Utf8, return NotUtf8 and the file's bytes.
|
||||||
|
fn load_file_as_string(path string) -> string/IoError(string)/NotUtf8([int ...]) {}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### structs are nothing more than tuples
|
||||||
|
|
||||||
|
```
|
||||||
|
// define our type named Person
|
||||||
|
type Person [int, string]
|
||||||
|
|
||||||
|
// function to get (but not set) the person's birth year
|
||||||
|
fn birth_year(self Person) self.0
|
||||||
|
|
||||||
|
// function to get or set the person's name
|
||||||
|
fn name(self Person/&Person) self.1
|
||||||
|
|
||||||
|
person := [1999, "Ryan"]
|
||||||
|
|
||||||
|
// Get the values
|
||||||
|
person.name() // Ryan
|
||||||
|
person.birth_year() // 1999
|
||||||
|
|
||||||
|
// Change the name
|
||||||
|
&person.name() = "James"
|
||||||
|
// or: name(&person) = "James"
|
||||||
|
person.name() // James
|
||||||
|
|
||||||
|
// This doesn't compile:
|
||||||
|
// &person.birth_year() = 1998
|
||||||
|
```
|
||||||
|
|
||||||
|
#### enums are already part of the type system
|
||||||
|
|
||||||
|
```
|
||||||
|
type DirectoryEntry File(string)/Dir(string)/Link(DirectoryEntry)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### return and break
|
||||||
|
|
||||||
|
Most people agree that `goto` statements are usually a bad idea.
|
||||||
|
But `return` and `break` have similar issues, although less severe:
|
||||||
|
|
||||||
|
**they create weird control flow:**
|
||||||
|
|
||||||
|
Maybe you want to see how long a function takes to execute, so
|
||||||
|
you add a `println` to the end - just to see absolutely no output.
|
||||||
|
Now you might think the function is stuck in an infinite loop somewhere,
|
||||||
|
although it just returned before it ever got to your println.
|
||||||
|
This can be annoying. If we remove `return` statements,
|
||||||
|
it becomes way easier to tell when code will or won't be executed.
|
||||||
|
|
||||||
|
**return is exclusive to functions, break to loops**
|
||||||
|
|
||||||
|
With mers, whenever possible, concepts in the language should work on *statements*.
|
||||||
|
|
||||||
|
An example of this are type annotations:
|
||||||
|
|
||||||
|
```
|
||||||
|
fn half(num int) -> int { num / 2 }
|
||||||
|
```
|
||||||
|
|
||||||
|
The `-> int` indicates that this function returns an int.
|
||||||
|
Actually, it indicates that the statement following it returns an int.
|
||||||
|
This obviously means the function will also return an int,
|
||||||
|
but since this syntax isn't specifically made for functions,
|
||||||
|
we can use it anywhere we want:
|
||||||
|
|
||||||
|
```
|
||||||
|
num := -> int { 4 + 5 }
|
||||||
|
half(-> int { double * 2})
|
||||||
|
```
|
||||||
|
|
||||||
|
**So how do we return values then?**
|
||||||
|
Simple!
|
||||||
|
|
||||||
|
```
|
||||||
|
// a function is just another statement
|
||||||
|
fn return_int() 4 + 5
|
||||||
|
|
||||||
|
// a block returns whatever its last statement returns,
|
||||||
|
fn return_string() {
|
||||||
|
a := "start"
|
||||||
|
b := "end"
|
||||||
|
a + " " + b
|
||||||
|
}
|
||||||
|
|
||||||
|
// this returns string/[], because there is no else
|
||||||
|
if condition() {
|
||||||
|
"some value"
|
||||||
|
}
|
||||||
|
|
||||||
|
// this returns string/int
|
||||||
|
if condition() {
|
||||||
|
"some value"
|
||||||
|
} else {
|
||||||
|
12
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Most returns should be relatively intuitive,
|
||||||
|
although some special ones (like loops)
|
||||||
|
use matching (see docs/intro)
|
||||||
|
|
||||||
|
### it has references
|
||||||
|
|
||||||
|
To explain why this is necessary,
|
||||||
|
let's look at examples from other languages.
|
||||||
|
|
||||||
|
Here is some JavaScript:
|
||||||
|
|
||||||
|
```js
|
||||||
|
function doSmth(list) {
|
||||||
|
list[0] = 0
|
||||||
|
}
|
||||||
|
function modify(num) {
|
||||||
|
num = 0
|
||||||
|
}
|
||||||
|
list = [1]
|
||||||
|
num = 1
|
||||||
|
doSmth(list)
|
||||||
|
modify(num)
|
||||||
|
console.log(list) // [ 0 ]
|
||||||
|
console.log(num) // 1
|
||||||
|
```
|
||||||
|
|
||||||
|
The same thing in Go:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func doSmth(list []int) {
|
||||||
|
list[0] = 0
|
||||||
|
}
|
||||||
|
func modify(num int) {
|
||||||
|
num = 0
|
||||||
|
}
|
||||||
|
func main() {
|
||||||
|
list := []int{1}
|
||||||
|
num := 1
|
||||||
|
doSmth(list)
|
||||||
|
modify(num)
|
||||||
|
fmt.Println(list) // [0]
|
||||||
|
fmt.Println(num) // 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In both cases, the function was able to modify the list's contents,
|
||||||
|
but unable to change the number to a different value.
|
||||||
|
This is not just inconsistent, it's also not always what you wanted to do:
|
||||||
|
|
||||||
|
- i passed the list by value, how could the function change the inner value?
|
||||||
|
- the function is called `modify`, why can't it modify the value?
|
||||||
|
+ In Go, we could use references to make this work - but that just makes the list example even more annoying, since we don't need a reference to change the inner value.
|
||||||
|
|
||||||
|
So, let's look at mers:
|
||||||
|
|
||||||
|
```
|
||||||
|
fn doSmth(list [int ...]) {
|
||||||
|
&list.get(0).assume1() = 0
|
||||||
|
}
|
||||||
|
fn modify(num &int) {
|
||||||
|
num = 0
|
||||||
|
}
|
||||||
|
list := [1 ...]
|
||||||
|
num := 1
|
||||||
|
doSmth(list)
|
||||||
|
modify(&num)
|
||||||
|
debug(list) // [1 ...]
|
||||||
|
debug(num) // 0
|
||||||
|
```
|
||||||
|
|
||||||
|
The list is unchanged, but the number was changed by `modify`,
|
||||||
|
because it was explicitly passed as a reference `&int`.
|
||||||
|
**Unless you pass a reference, the value you passed will not be changed.**
|
||||||
|
|
||||||
|
### compile-time execution via macros
|
||||||
|
|
||||||
|
```
|
||||||
|
// at compile-time, runs primes.mers to find the first few prime numbers
|
||||||
|
primes := !(mers "primes.mers")
|
||||||
|
|
||||||
|
// at compile-time, runs the code to create a string
|
||||||
|
// containing 256 '-'s
|
||||||
|
long_string := !(mers {
|
||||||
|
str := "-"
|
||||||
|
loop {
|
||||||
|
str = str + str
|
||||||
|
if str.len() >= 256 {
|
||||||
|
str
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
## How mers?
|
## How mers?
|
||||||
|
|
||||||
@ -37,7 +288,7 @@ Now, create a new text file (or choose one from the examples) and run it: `mers
|
|||||||
|
|
||||||
If a function is called from two threads, all local variables of that function are shared.
|
If a function is called from two threads, all local variables of that function are shared.
|
||||||
This doesn't affect builtin functions, and since functions usually don't take long to execute,
|
This doesn't affect builtin functions, and since functions usually don't take long to execute,
|
||||||
the change of anyone encountering this is low, but it's something to be aware of.
|
the chance of anyone encountering this is low, but it's something to be aware of.
|
||||||
It's a simple fix in theory, but a lot of work to implement, which is why the bug isn't fixed yet.
|
It's a simple fix in theory, but a lot of work to implement, which is why the bug isn't fixed yet.
|
||||||
|
|
||||||
## Docs
|
## Docs
|
||||||
|
Binary file not shown.
Binary file not shown.
@ -96,21 +96,20 @@ impl RFunction {
|
|||||||
pub fn out_by_map(&self, input_types: &Vec<VType>, info: &GlobalScriptInfo) -> Option<VType> {
|
pub fn out_by_map(&self, input_types: &Vec<VType>, info: &GlobalScriptInfo) -> Option<VType> {
|
||||||
// NOTE: This can ONLY use self.out_map, because it's used by the VSingleType.fits_in method.
|
// NOTE: This can ONLY use self.out_map, because it's used by the VSingleType.fits_in method.
|
||||||
let mut empty = true;
|
let mut empty = true;
|
||||||
let out = self
|
let out =
|
||||||
.out_map
|
self.out_map
|
||||||
.iter()
|
.iter()
|
||||||
.fold(VType::empty(), |mut t, (fn_in, fn_out)| {
|
.fold(VType::empty(), |mut t, (fn_in, fn_out)| {
|
||||||
if fn_in.len() == (input_types.len())
|
if fn_in.len() == (input_types.len())
|
||||||
&& fn_in
|
&& fn_in.iter().zip(input_types.iter()).all(|(fn_in, arg)| {
|
||||||
.iter()
|
arg.types.iter().any(|t| t.fits_in_type(fn_in, info))
|
||||||
.zip(input_types.iter())
|
})
|
||||||
.all(|(fn_in, arg)| arg.fits_in(fn_in, info).is_empty())
|
{
|
||||||
{
|
empty = false;
|
||||||
empty = false;
|
t.add_typesr(fn_out, info);
|
||||||
t.add_typesr(fn_out, info);
|
}
|
||||||
}
|
t
|
||||||
t
|
});
|
||||||
});
|
|
||||||
if empty {
|
if empty {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
|
Loading…
Reference in New Issue
Block a user