trash: planning, iteration 2
----------------------------
I put all my plans for trash on this
tilde site, so when tilde.club was down
recently I temporarily lost all my plans.
impatiently, I just wrote down some new
ideas in my notebook and I like them
better than what I had previously.
so here's the 2nd iteration of plans for
trash:
- lots of braces
- no commas
- comments use // and /* ... */
- ridiculous symbol/variant stuff
- no more sigils
- there are no types
.. can a language actually have no types?
.. well, you know what I mean by this
- design philosophy: be as fun and
.. ridiculous as possible, so that readers
.. of hackernews will never want to use
.. or discuss or criticize the language
trash uses the following names for keyboard
symbols, because it makes discussing the
language a whole lot cuter:
- # = crunch
- ' = spark
- \ = hack
- @ = strudel
- & = pretzel
- ` = tick
- < = tic
- > = tac
- % = cherries
- ~ = worm
- * = splat, sparkle
- " = snakebite
- ? = pop
- | = spike
- [ = square
- ] = unsquare
- ^ = hat
variable declarations:
| foo = 2;
nothing much to this, except it's crucial
to note that spacing around the equals
sign just doesn't matter. variable names
are symbols.
symbols:
| foo = bar;
normally, symbols evaluate to themselves
| foo = 2;
| baz = #foo; // baz = 2
use a crunch to dereference a symbol's
value. if the symbol doesn't refer to anything,
it will evaluate to ~nil:
| #x // => ~nil, if x is undeclared
symbols can contain underscores, letters,
dashes, and digits, but cannot begin with a digit.
deferred evaluation:
| foo = 2;
| bar = 'foo;
| #bar // => foo
| ##bar // => 2
| #'''x // => ''x
| ##'''x // => 'x
| ###'''x // => x
adding a spark makes a symbol evaluate
to itself, but with one less spark (it's still
equal to itself with any number of sparks).
this is like an atom (see next) but you can
eventually get a value out of it.
atoms:
| foo = ~nil;
| #foo // => ~nil
| ##foo // => ~nil
| #~nil // => ~nil
beginning a symbol with a worm makes
it _atomic_, so it always evaluates to itself
(bools also work like this, with ~t and ~f)
more crunch stuff
| name = 2;
| "name: #name" // => "name: 2"
| "sum: #(add 1 2)" // => "sum: 3"
| "name: \#name" // => name: #name
putting crunch inside a string evaluates
and (possibly dereferences, if it's a symbol)
the expression that follows it. it can be escaped
using a hack.
more spark stuff
| foo = '(echo 2); // => no output
| #foo; // => outputs 2
the spark from earlier can also defer evaluation
of entire expressions, so you can save function
calls for later evaluation via the crunch.
cherries
| foo = %(echo 2); // => a pid
| bar = %firefox; // => a pid
| baz = echo 2;
| quux = %#baz;
cherries are used to get the PID of a process.
this operator can accept an expression, an
actual process name, or a dereferenced variable
name that refers to a process (the latter
operation requires a %# combination, which is
called _cherry pie_).
pretzel
| &echo 2;
| &for {1 2 3} |n| { echo #n };
the pretzel just runs the subsequent process
in the background, so control flow won't block on it.
notice how you can do this with loops too.
square
| xs = {1 2 3};
| xs[0] // => 1
| xs[-1] // => 3
| xs[0 2] // => {1 2 3}
the square operator is for indexing and slicing
lists (0-indexed and inclusively sliced). you
can also use negative indices. note how the square
implicitly dereferences symbols. it also works on
hashes, but without the range/negative indexing.
strudel
| xs = {1 2 3};
| @xs // => 3
| {1 2 @} // => 3
| xs[sub @ 1] // => 3
the strudel finds the length of a list (or hash).
it also implicitly dereferences symbols.
the very cool part of the strudel is that in a
list expression and in an indexing expression,
the list's length is saved so that any occurence
of @ inside will evaluate to the length.
splat
| names = {foo bar baz};
| echo *names; // echoes foo, bar, baz separately
| // the above macroexpands to this:
| // echo foo;
| // echo bar;
| // echo baz;
the splat operator is very treacherous and needs
to be used wisely. it operates on lists and hashes,
and copies the outer expression onto each element
of the list/hash, basically working like some sort
of macro.
tictac
| x = ~foo;
| #x <1> // => ~foo<1>
| #x <1 2> // => ~foo<{1 2}>
| #x <1 2> haha <"yeah">
| // ... => ~foo<{1 2}>haha<"yeah">
| <1> // => 1
the tictac operator can stick values next to atoms,
symbols, and other tictacs. if you space out values
inside a tictac, it's desugared into a list. tictac
structures ("dragons") are kind of like variants in
statically typed languages, eg haskell. anything
enclosed by tictacs on its own will evaluate to the
inner value.
hat and tick
| foo = ~just<2>~and<3 4>;
| ^foo // => ~just
| `foo // => <2>~and<3 4>
| ^`foo // => 2
| ```foo // => [3 4]
| ````foo // => ~nil
the hat and tick operators implicitly dereference
symbols. these are used for destructuring dragons;
the hat gets the first element of a dragon (the head)
and the tick gets all _but_ the first (the tail).
other operations
there are some other operations for arithmetic,
string manipulation, etc but those all look like
ordinary functions.
conditionals:
| if ~t {
| echo "makes sense";
| } elsif ~f {
| echo "what???";
| } else {
| echo "emergency!!!";
| }
|
| if `dragon |tail| {
| echo "dragon's tail: #tail";
| }
conditionals work as expected. truthy values are
~t and any other value that isn't falsy; falsy values
are ~f and ~nil. notice how you can bind the result
of conditionals using spikes.
loops:
| my-hash = { a: "bon" b: "appetit" };
| my-list = { "bon" "appetit" };
| for my-hash |k, v| { echo "#k: #v"; }
| for my-list |i, v| { echo "list[#i] = #v"; }
| for my-list |v| { echo "#v"; }
|
| x = 0;
| for {
| if (eq x 3) { brk; };
| if (eq x 4) { nxt; };
| x = add x 1;
| }
the for loop has four forms: it can iterate over
the key-value pairs of a hash, the index-value pairs
of a hash, and just the values of a list, providing
variables between the spikes. the final form is just
a blank `for`, which loops forever. the `brk` statement
breaks out of the enclosing loop, and `nxt` iterates again.
is it asinine to name this loop `for` when none of
these forms read like normal `for` loops? yes -- and
that's the point.
functions:
| greet name-1 name-2 {
| echo "hello, #name-1 and #name-2!";
| }
|
| greet "apple" "orange";
normal function definitions list the name, parameters,
and then provide a body within braces. they're invoked
like all the other command-line commands, and the first
element of these commands (ie the function name) is
automatically dereferenced.
| greet ?name {
| if #name {
| echo "hello, #name!";
| } else {
| echo "who are you?";
| }
| }
putting a pop before a parameter makes it optional,
and it's set to ~nil if none is provided. remember that
optional parameters can only occur at the end of the
parameter list, because that wouldn't make sense otherwise.
| greet name *names {
| echo "hello, #name!";
| echo "also these people: #names";
| }
|
| greet "ralph";
| greet "coral" "christina" "ralph"
adding a sparkle (not a splat!) before the final
parameter means that the function can take
any number of arguments at the end, and they all get
collected in a list with that parameter's name.
|
|
just kidding! I bet you were expecting keyword
arguments, right? well, I really hate keyword
arguments, because they're ugly! so this language
doesn't have them.
| {|name| "hello #name!"} "liselot";
lambdas are just like functions, but they don't have
a name and the parameter list goes inside the body
in between some spikes.