Tupilled
Paradigm(s) | Functional, High-Level |
---|---|
Designed by | User:Proxxa, User:Ashli Katt |
Appeared in | Category:2023 |
Type system | Dynamic |
Computational class | Unknown |
Reference implementation | Unimplemented |
File extension(s) | .tup , .tuple |
Tupilled is a high-level, functional programming language based on tuples and lambdas by User:Ashli Katt and User:Proxxa. All values are tuples or lambdas, so there are no true types, and thus there is no compile-time type checking. Tupilled is highly inspired by Haskell.
The name comes from a shortening of the word "Tuple-pilled."
Syntax Outline
Reserved Tokens
Tupilled has a small set of reserved tokens.
()
, for tuples.[]
, as a grouping symbol. They also indicate an evaluation when used as a parameter.{}
, for lambda definitions or parameters.=
, for assigning functions.,
, for separating tuple elements and function parameters.//
indicates a comment.
The following tokens are not reserved but instead hold special meaning.
main
is an entry point. It must be defined in every Tupilled program.panic
will abruptly stop the program.unique
every instance is replaced with a unique tuple during parsing. This functionality is useful for user-defined "types."
The grouping symbols and commas are completely disallowed in identifiers. =
can appear in an identifier so long as the identifier is not only =
. Similarly, .
may appear, but ..
cannot. a//
is an identifier, but a //
is an identifier followed by a comment.
Tuples
Tuples are a list of comma-delimited elements wrapped in a set of parentheses, ()
. As such, ()
is a valid tuple as is ((), (()))
. Given there are no numbers in Tupilled, a simple approach to an unsigned integer is creating Peano numbers where ()
is 0, (())
is 1, ((()))
is 2, and so on. Tuples may also contain lambdas.
Functions
Functions follow a similar definition scheme to very simple functions in Haskell.
ident paramA, paramB = body
Identifiers may be any string of characters which are not reserved. Parameters are comma-delimited and may match values as explained later. All function bodies consist of a single statement.
Functions are all equivalent to giving a lambda value to a top-level constant.
Pattern Matching
Function parameters may be pattern-matched in order to destructure arguments or set specific requirements. The parameter name (a)
will match a tuple with one inner element and provide that element to the identifier a
. Similarly, (a, b)
will match a tuple with two inner elements and provide those elements to the identifiers a
and b
, respectively. An identifier on its own, such as a
, will simply match any tuple.
For example: snd (a, b) = b
f{a}
matches any lambda that accepts one tuple parameter and names it f
. a
has no meaning here and is used simply as a pattern to match any tuple; it is not usable by the function's body.
In a similar way, f{}
accepts a lambda with no arguments. f{{}}
accepts a lambda that takes another lambda (which takes nothing). {}
matches a lambda with 0 arguments, and binds it to nothing.
For example: map_pair f{z} (a, b) = (f a, f b)
An underscore may be used to ignore a value, so (_, a)
will get the pair's second element, and ignore the first. An underscore can take the place of anything that would otherwise be a name.
For example: first a, _ = a
accepts two parameters, but only binds the first one.
Brackets have special meaning inside of a pattern, they may be used to match the return value of an expression. [expr]
will evaluate expr
and match the returned value.
For example: first ([Pair], (a, _)) = a
will destructure a 2-tuple, its first value must match the return value of Pair
(A function), and the rest is destructed as usual.
Piecewise Functions
Functions can be given multiple definitions when all those definitions accept the same number of arguments. This is done by appending a comma to a definition and writing new parameters and a new body.
false = () true = (false) isPeano () = true, (n) = isPeano n, _ = false
In this example, we check if a tuple is a peano number. ie: ()
, (())
, ((()))
, etc.
When isPeano
is called, it will attempt to match the supplied arguments to its patterns first-to-last.
It will first check if the argument is the empty tuple, which is Peano 0.
If that fails (not a 0-tuple), it will attempt to match the argument to the pattern (n)
, a 1-tuple, and recursively check if the inner element is a peano.
If both of those fail, (2+ tuple), it is not a peano number.
Infixing
A function may be infixed if it takes in two arguments and the function identifier is wrapped in parentheses. As such, a definition of a &&
operator may be made as such:
false = () true = (false) (&&) [false], _ = false, _ , a = a
Infix operations have lower precedence than regular prefix functions. You may need to use []
to group expressions when working with infix functions.
The parser will, however, greedily match infix operations.
Ex:
square 2 + 3
is treated as square [2 + 3]
NOT [square 2] + 3
Lambdas
Lambdas, much like they are in other languages, are like nameless functions. In reality, though, functions are named lambdas. They are defined by wrapping a function without its identifier in braces, {}
. A function can be defined by providing no arguments before the =
and returning a lambda.
wrap = { a = (a) }
Input/Output
All input and output are defined within the main
function, which must have either 0 or 1 arguments. When the main function returns, its value is read and the following steps are taken:
- Create a bitstring,
""
- Match
(a, b)
. Ifa
is()
, push a 0 to the output bitstring, otherwise push a 1. - Repeat the previous process using
b
as the new output tuple. Ifb
's length is not 2, stop early. - When finished, interpret the bitstring as UTF-8 text, padding the end with
0
s as necessary.
Here is an example of output being parsed:
Tuple | Bitstring -------------------------------------- ((()), ((), ((()), ()))) | ((), ((()), ())) | 1 ((()), ()) | 10 () | 101 | 00000101
The input is given the same format and is done in reverse. So, if the input is 101
, then the data input to main
is ((()), ((), ((()), ())))
. In practice, this input would be padded with 0
s to become ((), ((), ((), ((), ((), ((()), ((), ((()), ()))))))))
There is no mid-program printing.
Examples
Hello World
A program that outputs Hello, World!
main = ((),((()),((),((),((()),((),((),((),((),((()),((()),((),((),((()),((),((()),((), ((()),((()),((),((()),((()),((),((),((),((()),((()),((),((()),((()),((),((),((), ((()),((()),((),((()),((()),((()),((()),((),((),((()),((),((()),((()),((),((), ((),((),((()),((),((),((),((),((),((),((()),((),((()),((),((()),((()),((()),((), ((()),((()),((),((()),((()),((()),((()),((),((()),((()),((()),((),((),((()),((), ((),((()),((()),((),((()),((()),((),((),((),((()),((()),((),((),((()),((),((), ((),((),((()),((),((),((),((),((())))))))))))))))))))))))))))))))))))))))))) )))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))
or, more readable:
0 a = ((), a) 1 a = ((()), a) . = () main = [ 0 1 0 0 1 0 0 0 0 1 1 0 0 1 0 1 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 1 1 0 0 1 0 1 1 0 0 0 0 1 0 0 0 0 0 0 1 0 1 0 1 1 1 0 1 1 0 1 1 1 1 0 1 1 1 0 0 1 0 0 1 1 0 1 1 0 0 0 1 1 0 0 1 0 0 0 0 1 0 0 0 0 1 . ]
Panic
A program that will always panic.
main = panic
Cat
A cat program will output its input.
main a = a
Reverse Cat
A program that will output the reverse of its input.
main a = reverse a reverse bitstring = #reverse bitstring () #reverse (0, (1, (2, (3, (4, (5, (6, (7, next)))))))) last = #reverse next (0, (1, (2, (3, (4, (5, (6, (7, last)))))))) _, last = last
Boolean Operations
Implementation of simple boolean operations.
false = () true = (false) bool [false] = false, = true (&) [true], [true] = true, = false (|) [false], [false] = false, = true ! [false] = true, = false (^) [false], x = bool x, _, x = !x