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.