|
|
This is the homepage for the mushroom language wiki.
|
|
|
Here you'll mainly find tutorials on the language, which will be updated as the language grows. |
|
|
\ No newline at end of file |
|
|
# Brief Tutorial
|
|
|
Here's a helpful tutorial for the mushroom programming language.
|
|
|
Note that some parts may be out of date as the language grows and changes.
|
|
|
|
|
|
## Type Signatures
|
|
|
Type signatures describe a value's type, like in Java, C, Haskell, or any other language with static typing. You'll only need to use them when defining custom types (like records or variants). It's also useful to understand them since the REPL prints out type signatures for values, which can aid you in debugging. The REPL prints out type signatures in the form
|
|
|
`value : type`. Each type of value (also called _term_ in type theory jargon) in mushroom has a unique syntax to denote its type, which will be explained throughout the rest of this tutorial.
|
|
|
|
|
|
## Comments
|
|
|
Comments work like in C-style languages, which means that `//` begins single-line comments and
|
|
|
`/* ... */` is used for multi-line comments.
|
|
|
|
|
|
## Builtin Data Types and Operations
|
|
|
Mushroom comes with a few builtin functions and data types.
|
|
|
Types: `int` (integers), `str` (strings)
|
|
|
Operators: `+`, `-`, `/`, `*`, `print`, `hd`, `tl`, `cons`
|
|
|
These work similarly in mushroom as they do in other languages:
|
|
|
```
|
|
|
1 // an integer
|
|
|
(1 + 2) * 3 // integer operators are infix and can be used with parentheses
|
|
|
"hello, world" // a string
|
|
|
print("hello, world\n") // `print' prints out a string and has the type (str) -> <>
|
|
|
/* putting an infix operator in parentheses allows it to be passed like any other value
|
|
|
(like in Haskell). This is especially useful when you have to pass operators as arguments. */
|
|
|
(+)
|
|
|
```
|
|
|
|
|
|
## Variables
|
|
|
Variables are declared with the `let` keyword. You can't modify them, but you can redefine them:
|
|
|
```
|
|
|
let x = 2;
|
|
|
let z = x + 5;
|
|
|
let x = 6;
|
|
|
```
|
|
|
|
|
|
## Collection Types
|
|
|
Mushroom has two collection types: products and lists.
|
|
|
Products are essentially just groupings of terms with a fixed size, like pairs or triples in math. The type of a product is just a list of the types of the values inside it inside pointy brackets:
|
|
|
```
|
|
|
<1, "two", 3> : <int, str, int>
|
|
|
<"my location", <0, -1>> : <str, <int, int>>
|
|
|
```
|
|
|
Products are useful when you want to keep certain values together. You can also have empty products, which have the _unit type_ of <>. I/O functions like `print` often return them, and they are similar to the `void` type in other languages.
|
|
|
Lists are a collection of terms with the same type, and they can have an arbitrary length. Their type is denoted by the type of the list elements placed in brackets:
|
|
|
```
|
|
|
[] : [#a]
|
|
|
[1, 2, 3] : [int]
|
|
|
[1, 2, "hi"] // TYPE ERROR
|
|
|
```
|
|
|
You can deconstruct lists using the builtin operators `cons`, `hd`, and `tl`, which construct or deconstruct lists:
|
|
|
```
|
|
|
/* `hd` returns the first element of a list, provided that the list is nonempty */
|
|
|
hd([1, 2, 3]) // => 1
|
|
|
/* `tl` gets the second, third, etc. elements of a list, provided that the list is nonempty */
|
|
|
tl([1, 2, 3]) // => [2, 3]
|
|
|
/* `cons` sticks the first argument onto the head of the second argument (which should be a list) */
|
|
|
cons(1, [2, 3]) // => [1, 2, 3]
|
|
|
```
|
|
|
|
|
|
## Functions
|
|
|
Functions take multiple input values and return an output value. They also cannot change their input values (save for fibers, which will be explained later), but they can make local declarations and recursive calls. Multiple statements in a function are separated by semicolons, and the last statement is the value that's returned. Functions can be declared with the `fn` keyword (and referred to by name thereafter) or used anonymously:
|
|
|
```
|
|
|
// named declaration
|
|
|
fn add_three(x, y, z) {
|
|
|
x + y + z
|
|
|
};
|
|
|
|
|
|
// named invocation
|
|
|
add_three(1, 2, 3);
|
|
|
// anonymous functions look like Ruby-style blocks
|
|
|
{|x, y, z| x + y + z}(1, 2, 3)
|
|
|
```
|
|
|
|
|
|
Functions can take and return other functions, and also work as closures (meaning that they capture the environment that they were declared in):
|
|
|
```
|
|
|
fn higher_order_add(x) {
|
|
|
{|y| x + y}
|
|
|
};
|
|
|
|
|
|
let increment = higher_order_add(1);
|
|
|
increment(2); // => 3
|
|
|
```
|
|
|
|
|
|
You can separate declarations and print output in functions via semicolons:
|
|
|
```
|
|
|
fn foo(x, y, msg) {
|
|
|
print(msg);
|
|
|
print("\n");
|
|
|
let result = <y, x + y>;
|
|
|
result
|
|
|
};
|
|
|
```
|
|
|
|
|
|
Abstraction type signatures are just the type of their parameters in a parenthesized list, an arrow, and then the return type:
|
|
|
```
|
|
|
(+) : (int, int) -> int
|
|
|
print : (str) -> <>
|
|
|
cons : (#a, [#a]) -> [#a]
|
|
|
```
|
|
|
|
|
|
### Universal Types
|
|
|
Some functions or structures are universally valid for all types. For example, the function `{|x| x}` just returns its argument, so it could work for all types. We'd write its type as `(#a) -> #a`, which means that for any argument of an arbitrary type `#a`, the function returns a value of that same type. These types are kind of like parameters for universal types (which can be seen as functions from types to other types...), and in mushroom they're prefixed with a `#`.
|
|
|
You'll often see the REPL deduce values to have some sort of universal type (like `[] : [#!3]`). The names of these types are strange-looking, but the `!3` part is essentially just a unique identifier for that type that's internally used for unification (by the way, you can't put exclamation points in your own types -- this is so your own types don't come into conflict with the variables that the typechecker makes up).
|
|
|
|
|
|
## Record Types
|
|
|
Sometimes, you'll want to name the members of product values in order to denote what they represent. For example, what if you want a datatype that represents a person (with an age and a name)?
|
|
|
It's really convenient to define a record type, which is essentially just a product type with names attached to the fields. Another bonus, though, is that accessor functions for the product's fields are automatically generated:
|
|
|
```
|
|
|
record Person { name: str, age: int } // declaring a record type
|
|
|
let me = %Person{ name: "Jack Chandlerham", age: 10005 }; // constructing a record value
|
|
|
print(name(jack)); // using a field accessor for the name
|
|
|
age(jack); // using a field accessor for the age
|
|
|
```
|
|
|
|
|
|
## Variant Types
|
|
|
Variant types represent an option between multiple types (hence the name). An example of variants would be types like `Maybe` in Haskell or `Option` in Rust.
|
|
|
In mushroom, each variant type has one or more possible tags, each of which hold zero or more values of certain types.
|
|
|
```
|
|
|
/* A very simple variant type -- it can either be the term @t or @f */
|
|
|
variant boolean { @t, @f };
|
|
|
@t // here's how you'd use a value in this variant
|
|
|
/* This type is either the @just tag with a value of an arbitrary type associated with it,
|
|
|
or a @nothing that that holds no associated values. */
|
|
|
variant maybe { @just[#a], @nothing };
|
|
|
@just[1]
|
|
|
@just[<1, "hi">]
|
|
|
@nothing
|
|
|
@just[@nothing]
|
|
|
```
|
|
|
|
|
|
The type signature of variants consists of their tags (with parameter types in brackets), separated by a plus sign:
|
|
|
```
|
|
|
@t : @t[] + @f[]
|
|
|
@nothing : @just[#a] + @nothing
|
|
|
@just[2] : @just[int] + @nothing
|
|
|
```
|
|
|
|
|
|
When a variant type has some type parameters (e.g. the #a in maybe), you can pass it concrete type arguments when you make other types, so you can have more specific instances of that type:
|
|
|
```
|
|
|
variant foo { @possible_int[maybe<int>], @possible_str[maybe<str>] };
|
|
|
```
|
|
|
|
|
|
To actually use variant types, you'll need to perform pattern matching.
|
|
|
|
|
|
## Pattern Matching
|
|
|
You can take apart values and perform conditional branching in mushroom with the all-powerful `match` statement. Pattern matching works very similar to `case` statements in Haskell, with the added benefit of pin statements (like in Erlang and Elixir).
|
|
|
Basically, a match statement goes from the top down to see which _pattern_ first matches the value given to it. Patterns are like normal values, but they can include variables that succeed matching and bind to certain parts of the matched value. An example should hopefully be a better explanation:
|
|
|
```
|
|
|
/* note that match patterns must have the same type, and the match branches must return the name type */
|
|
|
match <1, 2> {
|
|
|
<3, 4> => 0, // fails -- <1, 2> != <3, 4>
|
|
|
<1, n> => n, // succeeds, returning 2 (1 matches 1, and 2 binds to n)
|
|
|
_ => -1 // a catch-all match at the bottom which always succeeds
|
|
|
}
|
|
|
|
|
|
/* matches are often useful with variants, and can be used as values */
|
|
|
let x = match @just[2] { @just[n] => n + 1, @nothing => 0 }; // x is 3
|
|
|
|
|
|
/* you can have multiple statements in the result of a match branch */
|
|
|
match %Person{ name: "Voldemort", age: 70 } {
|
|
|
%Person{ name: "Voldemort", age: n } => {
|
|
|
print("Yikes!\n");
|
|
|
"He-who-must-not-be-named"
|
|
|
},
|
|
|
|
|
|
%Person{ name: theName, age: n} => theName
|
|
|
}
|
|
|
```
|
|
|
|
|
|
You can also _pin_ values in match statements. This means that values are evaluated and _then_ matched against. Pinning is done with a ^:
|
|
|
```
|
|
|
/* Who needs a built-in equality function when there's match statements? */
|
|
|
fn equal(x, y) {
|
|
|
match x {
|
|
|
^y => @t,
|
|
|
_ => @f
|
|
|
}
|
|
|
};
|
|
|
|
|
|
equal(2 + 2, 4); // => @t
|
|
|
equal(<1, 2>, <1, 2>) // => @t
|
|
|
equal(["hi", "bye"], []) // => @f
|
|
|
```
|
|
|
|
|
|
Note that mushroom will throw a run-time error if no patterns in a match statement succeed.
|
|
|
|
|
|
## Fibers
|
|
|
Fibers are a feature that allow you to interleave parts of program execution. Fibers are somewhat like functions, except they return values at multiple points (using `yield`) and can be called multiple times with new arguments (using `resume`):
|
|
|
```
|
|
|
fiber foo(x, y) {
|
|
|
print("Adding\n");
|
|
|
yield x + y;
|
|
|
print("Multiplying\n");
|
|
|
yield x * y
|
|
|
};
|
|
|
|
|
|
resume foo(1, 2) // => 3
|
|
|
/* More code ... */
|
|
|
resume foo(3, 4) // => 12
|
|
|
resume foo(5, 20) // => 100
|
|
|
```
|
|
|
|
|
|
Fibers _must_ end in a `yield` statement. When all of a fiber's body has already been executed, subsequent calls to `resume` will just return the value of the last `yield` statement. |
|
|
\ No newline at end of file |