mirror of
https://github.com/Dummi26/mers.git
synced 2025-03-09 21:13:54 +01:00
improve README
This commit is contained in:
parent
6b2ae08731
commit
5d663d8d39
67
README.md
67
README.md
@ -1,70 +1,3 @@
|
||||
# mers
|
||||
|
||||
See [the mers readme](mers/README.md) for more info.
|
||||
|
||||
---
|
||||
|
||||
```
|
||||
"Hello, World!".println
|
||||
```
|
||||
|
||||
> `Hello, World!`
|
||||
|
||||
---
|
||||
|
||||
```
|
||||
my_var := "Hello, Variable!"
|
||||
my_var.println
|
||||
```
|
||||
|
||||
> `Hello, Variable!`
|
||||
|
||||
---
|
||||
|
||||
```
|
||||
(1, 2, 3, 4).sum.println
|
||||
```
|
||||
|
||||
> `10`
|
||||
|
||||
---
|
||||
|
||||
```
|
||||
(1, "2", 3, 4).sum.println
|
||||
```
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
```
|
||||
(1, 2, 3, 4).as_list.debug
|
||||
```
|
||||
|
||||
> `List<Int> :: [1, 2, 3, 4]`
|
||||
|
||||
---
|
||||
|
||||
```
|
||||
(1.0, 2.0).as_list.debug
|
||||
```
|
||||
|
||||
> `List<Float> :: [1, 2]`
|
||||
|
||||
---
|
||||
|
||||
```
|
||||
(1, 2, 3.5).as_list.debug
|
||||
```
|
||||
|
||||
> `List<Int/Float> :: [1, 2, 3.5]`
|
||||
|
||||
---
|
||||
|
||||
```
|
||||
int_list := (1, 2, 3).as_list
|
||||
float_list := (4.5, 6.0).as_list
|
||||
int_list.chain(float_list).as_list.debug
|
||||
```
|
||||
|
||||
> `List<Int/Float> :: [1, 2, 3, 4.5, 6]`
|
||||
|
391
mers/README.md
391
mers/README.md
@ -1,228 +1,215 @@
|
||||
# mers
|
||||
|
||||
Mers is a simple, safe programming language.
|
||||
|
||||
```sh
|
||||
cargo install mers
|
||||
```
|
||||
|
||||
Mers is a simple, safe programming language.
|
||||
## safety & type system
|
||||
|
||||
## features
|
||||
Mers is type-checked, which guarantees
|
||||
that a valid mers program will not crash
|
||||
unless `exit` or `panic` is called.
|
||||
|
||||
- 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 `goto`s (or `break`s or `return`s)
|
||||
- locking (useful for multithreading, any reference can be locked)
|
||||
The type system is kept simple on purpose.
|
||||
A variable's type is decided where it is declared,
|
||||
there is no type inference. Each type of expression
|
||||
has a defined way of finding the type of values it
|
||||
produces, for example:
|
||||
|
||||
- tuples and objects produce tuple and object types
|
||||
- a block produces the same type of value as the last expression it contains
|
||||
- an if-statement produces either the type of the first expression (if the condition was true), or the type of the second expression (which is `()` if there is no `else`)
|
||||
- type hint expressions produce the type specified in square brackets
|
||||
- ...
|
||||
|
||||
Mers can represent sum- and product-types:
|
||||
|
||||
- product types are tuples or objects: `(A, B)` or `{ a: A, b: B }`
|
||||
- sum types are just two types mixed together: `A/B`
|
||||
|
||||
An example of product types:
|
||||
|
||||
```
|
||||
// this is an Int
|
||||
some_number := 5
|
||||
// these are Strings
|
||||
some_string := "five"
|
||||
some_number_as_string := (some_number).concat
|
||||
// this is a { base10: String, human: String }
|
||||
some_object := { base10: some_number_as_string, human: some_string }
|
||||
// this is a (Int, { base10: String, human: String })
|
||||
some_tuple := (some_number, some_object)
|
||||
```
|
||||
|
||||
An example of a sum type:
|
||||
|
||||
```
|
||||
some_number := 5
|
||||
some_string := "five"
|
||||
some_number_as_string := (some_number).concat
|
||||
// this is an Int/String
|
||||
some_value := if some_string.eq(some_number_as_string) { some_number } else { some_string }
|
||||
```
|
||||
|
||||
## simplicity
|
||||
|
||||
mers only has a few different expressions:
|
||||
|
||||
- literals: `4`, `-1.5`, `"hello"`
|
||||
- tuples and objects: `(a, b, c)`, `{ a: 1, b: 2 }`
|
||||
- variable declarations: `var :=`
|
||||
- variables: `var` (get the value) or `&var` (get a reference to the value)
|
||||
- reference assignments: `ref =` (usually used as `&var =`)
|
||||
- blocks: `{ a, b, c }`
|
||||
- functions: `arg -> expression`
|
||||
- function calls: `arg.func` or `a.func(b, c)`, which becomes `(a, b, c).func`
|
||||
- `if condition expression` and `if condition expression_1 else expression_2`
|
||||
- `loop expression`
|
||||
- type hints `[Int] 5`
|
||||
- type definitions `[[Number] Int/Float]` or `[[TypeOfX] := x]`, which can also be used as a type check: `[[_] := expression]` checks that the expression is type-correct
|
||||
- try: mers' switch/match: `x.try(num -> num.div(2), _ -> 0)`
|
||||
|
||||
mers treats everything as call-by-value by default:
|
||||
|
||||
```
|
||||
modify := list -> {
|
||||
&list.insert(1, "new value")
|
||||
list.debug
|
||||
}
|
||||
|
||||
list := ("a", "b").as_list
|
||||
list.modify
|
||||
list.debug
|
||||
```
|
||||
|
||||
When `modify` is called, it changes its copy of `list` to be `[a, new value, b]`.
|
||||
But when `modify` is done, the original `list` is still `[a, b]`.
|
||||
|
||||
If you wanted list to be changed, you would have return the new list
|
||||
|
||||
```
|
||||
modify := list -> {
|
||||
&list.insert(1, "new value")
|
||||
list.debug
|
||||
}
|
||||
|
||||
list := ("a", "b").as_list
|
||||
&list = list.modify
|
||||
list.debug
|
||||
```
|
||||
|
||||
or give `modify` a reference to your list
|
||||
|
||||
```
|
||||
modify := list -> {
|
||||
list.insert(1, "new value")
|
||||
list.deref.debug
|
||||
}
|
||||
|
||||
list := ("a", "b").as_list
|
||||
&list.modify
|
||||
list.debug
|
||||
```
|
||||
|
||||
<small>To make this slightly less inefficient, mers
|
||||
uses a copy-on-write system, so that you
|
||||
can give copies of large values to functions
|
||||
without copying the entire value.
|
||||
When a copy of a value is changed, it is (at
|
||||
least partially) copied before mers changes it.</small>
|
||||
|
||||
# examples
|
||||
|
||||
## Hello, World!
|
||||
|
||||

|
||||
```
|
||||
"Hello, World!".println
|
||||
```
|
||||
|
||||
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!*,
|
||||
In this case, our argument is the string containing `Hello, World!`.
|
||||
|
||||
## Variables
|
||||
---
|
||||
|
||||

|
||||
```
|
||||
greeting := "Hello, World!"
|
||||
greeting.println
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```js
|
||||
var result
|
||||
if (condition) {
|
||||
result = "Yay"
|
||||
} else {
|
||||
result = "Nay"
|
||||
}
|
||||
```
|
||||
say_hello := () -> "Hello, World!".println
|
||||
().say_hello
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```rs
|
||||
if true {
|
||||
"Yep"
|
||||
} else {
|
||||
"Nay"
|
||||
}
|
||||
```
|
||||
|
||||
But as soon as we mix two different types, it no longer compiles:
|
||||
|
||||
```rs
|
||||
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
|
||||
|
||||
(...)
|
||||
We create a function using the `->` syntax, then assign it
|
||||
to the `say_hello` variable.
|
||||
We then call the function with the `()` argument.
|
||||
|
||||
---
|
||||
|
||||
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`.
|
||||
```
|
||||
if "a".eq("b") {
|
||||
"what?".println
|
||||
}
|
||||
|
||||
response := if "a".eq("b") {
|
||||
"what?"
|
||||
} else {
|
||||
"ok :)"
|
||||
}
|
||||
response.println
|
||||
```
|
||||
|
||||
An `if` is used to conditionally execute code.
|
||||
It can also produce values.
|
||||
|
||||
---
|
||||
|
||||
```
|
||||
val := loop {
|
||||
"> ".print
|
||||
().read_line.trim.parse_float
|
||||
}
|
||||
val.println
|
||||
```
|
||||
|
||||
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`.
|
||||
|
||||
---
|
||||
|
||||
```
|
||||
val := if "a".eq("a") {
|
||||
5
|
||||
} else {
|
||||
"five"
|
||||
}
|
||||
val.try(
|
||||
// if the value is a number, print half of it
|
||||
num -> num.div(2).println
|
||||
// for any other value, print it directly
|
||||
other -> other.println
|
||||
)
|
||||
```
|
||||
|
||||
A `try` expression uses the first type-correct branch for the given value.
|
||||
In this case, for a number, we can do `num.div(2)`, so the first branch is taken.
|
||||
For non-number values, `.div(2)` would be a type error, so the second branch has to be taken.
|
||||
|
||||
---
|
||||
|
||||
```
|
||||
add_one := x -> x.add(1)
|
||||
do_twice := func -> x -> x.func.func
|
||||
add_two := add_one.do_twice
|
||||
2.add_two
|
||||
```
|
||||
|
Loading…
Reference in New Issue
Block a user