6.3.90.900
The Heresy Programming Language
source code: https://github.com/jarcane/heresy
The Heresy language is a functional Lisp/Scheme dialect implemented in Racket,
with syntax inspired by the BASIC family of programming languages. Its principle
goals are to provide a simple core language for BASIC and other programmers to
experiment with and learn how to program functionally. This document will detail
the general philosophy of the Heresy language, such as exists, as well as the
language syntax and functions.
The Heresy language was created by John S. Berry III with additional contributions
from many others in the Racket community.
Heresy and this documentation are Copyright (c) 2014 John S. Berry III and
released under the terms of the GNU LGPL.
1 The Heresy Rules
The Heresy language is developed according to a few basic "ground rules," which
the author and contributors attempt to follow in developing new features and
functions for the language. These are as follows:
Heresy is BASIC - Heresy is inspired by BASIC, and aims to be at
least somewhat easy for BASIC programmers to learn. Mostly this means we
prefer BASIC names for functions over the Lisp name, and naming
conventions like the $ for string functions.
Heresy is a Lisp - Heresy is still a Lisp, and loves simple syntax
and s-expressions. While it makes use of some sugaring like literal
keywords for certain common primitives, these are best used sparingly.
Heresy is the Diet Coke of Evil, just one calorie, not quite evil enough.
Heresy is functional - Functional, but not Haskell. It is not
intended solely as a vehicle for absolute functional purity. I love
Haskell. You love Haskell. We don’t need to write another Haskell. Think
more in terms of a lower-calorie, more intelligible Clojure.
Heresy is for learning - Heresy started as a learning project, a
chance to learn how Lisp and functional programming really work on a
practical level. I hope that, in time, it can be that for others as well,
especially those who grew up with BASIC like myself and still sometimes
struggle to get their head around the functional style. In particular,
this means the Heresy-written portions of the source are generally
written in as clear a manner as possible, as they are intended to be
self-teaching.
Heresy is an experiment - Heresy is an experimental language.
It’s very DNA is as a mad idea that came to life, and it’s development
should be ready and willing to embrace new mad ideas and run with them.
This is where carry came from, and I hope to have more mad ideas in the
future.
Heresy is for everyone - As a statement of culture, the Heresy
community welcomes the contribution of all people, who taste equally
delicious to the jaws of mighty Cthulhu. No discrimination, harassment,
or any other mistreatment of contributors on the basis of age, race,
sexuality, or gender will ever be tolerated by myself or anyone
else who wishes to be part of this project.
2 Heresy Syntax and Conventions
Generally speaking, Heresy follows standard s-expression syntax as expected from
any Lisp, being composed of parenthesized sequences of terms in Polish notation.
Each sequence thus begins with an operator or function, and any number of
arguments or additional s-expressions as needed.
There are however a few exceptions to usual expectations in the case of certain
special forms like for, def, and if. These make use
of additional literal terms as part of their syntax, to provide more clarity and
similarity to BASIC-style syntax.
In accordance with that goal, Heresy also follows certain naming conventions as
a matter of style. Functions which produce a string value are appended with $,
and in general where a naming conflict between two similar functions in
Racket/Scheme and BASIC exists, prefer BASIC.
When borrowing BASIC syntax and naming for use in Heresy, the author has
generally relied chiefly on QBASIC and ECMA BASIC for reference.
3 Heresy Reference
3.1 Declarations
Defines a new variable of name with the given value.
(def fn name args body ...+)
|
|
args | | = | | (arg ...) | | | | | | (arg ... . rest-id) | | | | | | args-id | | | | | | arg | | = | | arg-id | | | | | | [arg-id default-expr] |
|
Defines a function of name, which when called evaluates its body
expressions with the given list of arguments bound to local variables for use in
the body of the function’s definition. Note that there are a number of
additional options here for defining arguments. Default values can be ascribed
to an argument by enclosing it in additional parentheses:
Examples:
> (def fn foo (x (y 1)) (+ x y)) |
> (foo 3 4) |
7 |
> (foo 5) |
6 |
Two patterns as well exist for taking an arbitrary number of values. The
argument names list can be forgone entirely and substituted with a single name
(generally args* by convention), which will then contain a list of any and all
values passed to the function. The second method is the use of the dot (.) in
the body of the arguments list followed by a single variable (usually called rest).
Examples:
> (def fn foo args* args*) |
> (foo) |
'() |
> (foo 3 4 5) |
'(3 4 5) |
> (def fn bar (x y . rest) (join (+ x y) rest)) |
> (bar 3 4) |
'(7) |
> (bar 3 4 5 6 7 8) |
'(7 5 6 7 8) |
(def macro name (pattern ...) template)
|
Defines a new macro with
name. A macro can best be thought of as a
function which is not evaluated, but rather returns syntax to be evaluated in
the form of a template. Each name described in the
pattern defines a
"pattern variable" which can then be used in the body of the
template
and will pass any syntax contained in that portion of the
pattern in
the appropriate location matched in the
template. The elipsis
... can be used in a pattern to indicate repeatable values.
(def macroset name [(name pattern ...) template] ...)
|
Similar to
def macro, except that multiple matching patterns can be defined
allowing for macros with variable syntax. Like
def macro, the
...
symbol can be used to indicate repeating values.
(let ((name value) ...) body ...+)
|
Binds the given name-value pairs for use in the local context created by the
body of the expression. This is used to define local variables, such as are
needed within a function. Note that local functions can potentially be assigned
this way by storing anonymous functions, but there is a built-in syntax for
defining a single such function, like so:
(let proc-id ((name value) ...) body ...)
|
When let is called this way, it defines a local function proc (conventionally
called recur), which can then be called from within the body of the let in order
to perform local recursion; the name-value pairs thus act as arguments to the
function proc.
Creates an anonymous function with the given arguments, that evaluates its body
when called. This is the lambda expression from other Lisps and functional
languages, and a given fn can be passed as a value (as can named functions, for
that matter) wherever called for. An anonymous function can also be evaluated
directly in place by using it as the operator in an expression, like so:
Example:
> ((fn (x y) (* x y)) 4 5) |
20 |
3.2 Conditionals and Loops
(if test then texpr else fexpr)
|
Evalutes
test and, if
test is
True, evaluates
texpr, otherwise it evaluates
fexpr. Note that only a single
expression can be placed in each "slot" in the syntax; if you need to do
multiple things, use a
do block.
Given a list of test-expression pairs, evaluates the tests in order until it
finds one which is
True, and evaluates the matching expression. The
else expression is always true: if an else is found at the end of the
select statement, its matching
fexpr will be evaluated. If no test in
select is true, returns
#<void>.
(select case texpr ((val ...) rexpr) ...)
|
(select case texpr ((val ...) rexpr) ... (else fexpr)) |
Evaluates
texpr and compares it to each
val in turn until it
finds a value that is
eq? to the result of
texpr. If one is
found, it evaluates the matching
rexpr. Like with
select,
else is always considered True, and will therefore always evaluate its
fexpr. If no matching
val is found,
select case
evaluates to
#<void>. Note also that the
(val ...) is a list, and
can contain as many values as is needed, such as in the following example:
(for (var in list) body ...)
|
(for (var in list with cry) body ...) |
Iterates over list evaluating its body with the head of list assigned to var,
then recurs with the tail of list until it returns
Null.
for
loops declare an implicit variable
cry which can be passed a value with
carry. They may also be interrupted with
break. See below for
more details.
Evaluates its bodys in order, returning the result of the final body
expression.
(do loop body ...)
|
(do loop with cry body ...) |
Evaluates body repeatedly until a
break statement is encountered.
Declares the implicit variable
cry, which can be reassigned with the
carry operator.
Breaks the continuation of a
for or
do loop evaluation. If
provided a value, returns that value as the result of the loop.
When called in the body of a
for or
do loop expression,
immediately begins the next iteration of the loop, and passes the given value to
the implicit variable
cry.
Loops declare an internal variable called
cry, which defaults to
Null, and which is passed automatically to the next iteration of the
loop, and is returned when the loop concludes. The value of
cry can be
specified at the beginning of the loop with the optional
with
parameter, and
carry can be used to pass a new value of
cry to
the next iteration.
3.3 Predicates and Logic
(list? v) → boolean?
|
v : any |
Returns True if v is a list.
(null? v) → boolean?
|
v : any |
Returns True if
v is
Null, where Null is defined as the empty list
'().
Returns True if v is a number.
(zero? v) → boolean?
|
v : any |
Returns True if v = 0.
(one? v) → boolean?
|
v : any |
Returns True if v = 1.
(eq? x y) → boolean?
|
x : any |
y : any |
Returns True if x and y are the same object.
(equal? x y) → boolean?
|
x : any |
y : any |
Returns True if x and y are equal.
Returns True if v is a string.
(fn? v) → boolean?
|
v : any |
Returns True if v is a function.
(atom? v) → boolean?
|
v : any |
Returns True if v is an atom: ie. a number, symbol, or function,
rather than a list or Null.
(lat? l) → boolean?
|
l : any |
Returns True if l is a list composed solely of atoms.
Returns True only if all given expressions are True.
Returns True if any given expression is True.
(not v) → boolean?
|
v : any |
Returns True if v is False, else returns False.
A special keyword for True, used as a literal in conditional statements.
The boolean truth value. Actually an alias for #t in the Racket
implementation. Note that, in Heresy, as in Racket, anything not explicitly
False is considered True.
The boolean false value. Actually an alias for #f in the Racket
implementation.
An alias for the empty list '().
3.4 Syntax and Evaluation
"Quotes" the given
v, without evaluating its contents. A quoted list is
passed merely as data, a quoted atom is a "symbol" as per
symbol?. Can
be shortened to
'.
When encountered within a a
quasiquoted block, evaluates
v and
quotes its value instead. Can be shortened to
,.
Similar to
unquote, but splices the result of evaluating
v in
place. Can be shortened to
,@.
Halts the program, returning an error of symbol: message where
symbol is a quoted value (customarily the name of the current function)
and message is a string.
(run form) → any
|
form : any |
Evaluates the given form. Usage is not recommended.
Ignores its arguments and returns void. Useful for block comments.
Applies
fun to the given arguments, as if it had been called with
(fun v ... x ...) where the
xs are the elements in
lst.
3.5 Input and Output
Prints the given
v to the current output, or stdout if not otherwise
specified, followed by a newline.
(print & v) outputs without a
newline, while
(print lit v) outputs as a literal form that can be
directly read back by
(input stx ....) as code. A bare
(print)
merely prints a newline to the current output.
A shortened macro for print.
Reads a line of input from the current input, or stdin if not otherwise
specified, and returns the value read as a string.
(input stx ....)
instead reads a value using the standard reader, thus providing syntax which can
be evaluated with
run. If additionally provided with a string, this
will be output as a prompt to the current output.
Evaluates the body, with input/ouptut redirected to the given io-port. Only the
file port is supported at this time.
Opens the file name as the new target for input or output, depending on
the mode provided. mode is a symbol, of one of the following:
'output | | Opens file as the current output port. Will fail if
file already exists. |
'rewrite | | Opens file as the current output port, rewriting its
contents if the file exists. |
'input | | Opens file as the current input port. |
3.6 Lists
Returns a list containing the given values.
(join a b) → pair?
|
a : any |
b : any |
Joins
a and
b into a pair. If
b is
Null, a
list is created containing
a. If
b is a list,
a is
joined to the head of the list.
Returns the head (first element) of the list l.
Returns the remainder of list
l after the head. If the list has only
one element, returns
Null.
(range start to finish)
|
(range start to finish step n) |
Generates a list of numbers, incrementing from
start to
finish
by
n. If no
n is provided, defaults to 1. Note that, unlike
BASIC’s
for x = y to z, descending sequences where
start is
greater than
finish can only be declared by including a negative n.
Otherwise only
Null will be returned.
Given a single-argument function fun, returns a list with fun
applied to each item in l.
Given a predicate fun, returns a new list containing only those
elements of l for which fun returns True.
Returns the number of items in l.
Given a function fun with two arguments, returns the cumulative result
of fun being applied to consecutive pairs, starting from base
and the rightmost element of l.
Similar to
foldr, except that it combines pairs from the left, starting
with the head of
l and
base.
Returns a list with the order of l reversed.
Returns the nth entry of l, indexed from 1.
Walks through nested lists according to the given
dims, essentially
finding index recursively for an arbitrary number of dimensions. For example,
given a nested list three lists deep,
(index* l 2 3 1) would return the
1st element of the third element of the 2nd lst, like so:
Examples:
> (def dave '(1 (2 3 (4 5)) 6)) |
> (index* dave 2 3 1) |
4 |
Also,
(l dims ...) can be used as a shorthand for
index*:
Examples:
> (def dave '(1 (2 3 (4 5)) 6)) |
> (dave 2 3 1) |
4 |
Searches
l for
item, returning the index of
item if
found, or
False if not.
Returns a list of the leftmost n elements of l.
Returns a list of the rightmost n elements of l.
Returns n entries of l starting from index idx.
Returns a slice of l, starting at first and ending at
last. If last is not provided, it defaults to the end of the
list.
Returns a list with l2 appended to the end of l1.
Returns a list of the given ls appended together in order.
(assoc tgt l) → list-or-false?
|
tgt : any |
l : list? |
Searches the heads of a list of lists
l and returns the first matching
list or
False.
(subst tgt new l) → list-or-false?
|
tgt : any |
new : any |
l : list? |
Searches the heads of a list of lists
l, and if it finds
tgt,
returns a new list with the tail of tgt substituted for
new. Otherwise,
returns
False.
Returns a list of the heads of a list of lists.
Sorts list l according to the comparison function fun.
Returns a new list of lists combining l1 and l2. Excess length
of either list is dropped.
Returns a new list, combining the matching pairs of each list with fun.
Excess length of either list is dropped.
3.7 Strings
Returns True if the two strings are equal.
Concatenates its arguments into a single string.
Returns a list of one-character strings from the given string.
Converts a value n to a string.
Returns True if the string is empty ("").
Returns the length of the string, indexed from 1.
Given a list of strings, returns a single concatenated string.
Returns the head (first character) of the string.
Returns the tail (remaining characters) of the string, unless str is
empty, in which case it returns the empty string.
Returns a string of the leftmost n characters of str.
Returns a string of the rightmost n characters of str.
Returns a section of str, len characters long, beginning at
idx.
Returns a slice of str beginning at start and ending at
finish. If not specified, finish defaults to the length of the
string.
Returns the index of the first instance of search in str, or
False if not found.
Returns a list of string sections split at the given delimiters. If
delimiters is not specified, defaults to space (" ").
Given a string template, returns a new string with instances of glyph "#_" replaced in order, starting with the first value given following the string.
3.8 Math
Adds the given numbers left to right and returns the result. If only one argument is given, returns the argument. If no arguments are provided, returns 0.
Subtracts the given numbers left to right and returns the result. If only one
argument is given, returns
(- 0 x).
Divides the numbers from left to right and returns the result. If only one
argument is given, returns
(/ 1 x).
Multiplies the numbers given from left to right and returns the result. If no
argument is given, returns one. If one argument is given, returns the argument.
Returns True if all the numbers are numerically equal.
Returns True if all arguments are greater than the one previous going right
(ie, x < y < z, etc.)
Returns True if all arguments are less than the one previous going right
(ie, x > y > z, etc.)
A bound variable containing the 64-bit floating point value of pi.
A bound variable containing the 64-bit floating point value of Euler’s number.
Returns the modulus of x divided by y.
Returns the absolute value of n.
Returns True if n is even.
Returns True if n is odd.
Returns -1 if n is negative, 0 if n is zero,
and 1 if n is positive.
Returns the value of
(+ n 1).
Returns the value of
(- n 1).
Returns the sine of x as a floating point value.
Returns the cosine of x as a floating point value.
Returns the tangent of x as a floating point value.
3.9 Random Numbers
Heresy’s random number generator operates slightly differently to traditional
BASIC’s, in order to offer a more functional approach. Rather than defining a
single global seed which the RND function then refers to, Heresy’s
randomize returns a "generator" function with a given seed, allowing
one to name and seed as many generators as one needs, though for practical
purposes a default RND is still provided which is automatically created and
seeded with a derivation of the current time in milliseconds.
Heresy’s RNG employs a fairly strong 64ish-bit Xorshift* algorithm, though no
guarantees are offered as to its cryptographic security.
Returns a new generator function initialized with
seed, which is first
passed through
equal-hash-code. If no
seed is provided, defaults to
timer.
A pre-defined generator which returns a random number between
0 and
1, exclusive, seeded by
timer.
A special internal variable which contains the current time in milliseconds.
3.10 Things
Things are Heresy’s definable data structures. Unlike the objects of most
object-oriented languages, which often exist to hold and carry mutable state and
actions with which to change that state, Things are immutable. A Thing, once
sprung to life, cannot itself be altered, but can be called with the correct
syntax to return a new Thing with different internal values for its internal
data fields.
Things are essentially functions, lambdas specifically, with predefined syntax
arguments. They are first class values, and can be passed freely just as any
other data, but can also be passed arguments to either return the values of
their fields, return a new Thing, or to employ any functions contained within
the thing.
Defines a new type of Thing, given
Name. By convention, Things are
generally named in uppercase, though this is not required by the syntax. Each
field is an internal name and external symbol, which is mapped to the given
value. Anonymous functions (
fn) can be assigned as values to Thing
fields, and those functions can access the fields of the Thing by name.
If the extends option is provided, the new Thing extends
super-thing, inheriting it’s fields and methods (unless they are
overridden). If the inherit option is provided with it, then the
ids are available as bindings within the method expressions.
Just like
fn produces an anonamous function,
thing produces an
anonamous Thing.
If there is a Thing defined as
Name:
(Name)
|
(Name symbol) |
(Name 'fields) |
(Name pattern) |
Once a Thing has been described or bound to a name by other means, that Name is
bound locally as a function, and can thus be called with special syntax to
return its contents or to return a new copied Thing. In more detail, these
syntaxes are as follows:
Returns an association list containing the contents of the Thing, ie. a list in
the form of: '((field value) ...)
Returns a list of symbols for the fields contained within the Thing. Note that
the symbol 'fields takes precedent over the field names within, in
order to prevent overwriting this syntax.
Returns the value of the field associated with symbol, the quoted
form of the field name described when the Thing type was first declared. Will
return an error if no such named field is found. If the value associated with
symbol is a function, this expression can be used as the operating function of
a further expression like so:
Examples:
> (describe Lord-Cthulhu (eat (fn (x) (print (& "Devours " x))))) |
> ((Lord-Cthulhu 'eat) "Dave") |
Devours Dave |
(Name pattern)
|
|
pattern | | = | | `(sub-pat ...) | | | | | | sub-pat | | = | | * | | | | | | value |
|
Returns a copy of the Thing, with new values according to the pattern passed to
the original Thing. pattern must be a quoted list of either
'*s or values, in order according to the fields of the Thing as
originally defined (so the first sub-pat matches the first
field, the second to the second field, and so on). A '* in a field
indicates that the value is copied in-tact, while a value becomes the new value
of the field in that position. For example:
Examples:
> (describe Santa | (size 'fat) | (sleigh 'ready) | (sack 'full)) |
|
> (def Santa-after-Christmas (Santa `(* * empty))) |
> (Santa-after-Christmas) |
'((size fat) (sleigh ready) (sack empty)) |
(send Thing symbol arg ...) → any
|
Thing : thing? |
symbol : symbol? |
arg : any |
An alternate syntax for accessing functions within Things, send calls the
function named by (Thing symbol) with the given arguments and returns
the result.
Self is the self-referring identifier for a Thing, allowing for
functions within Things to call the Thing itself. Note that if it is only the
values of the other fields, this is not necessary, as fields are defined as
local names within the context of the Thing, and thus can be referred to simply
by name.
3.11 Theory
The strict Y fixed-point combinator. Allows for recursion of anonymous
functions. Given a fn1 which contains a single named argument, and
within which is an additional single-argument fn2, the innermost
fn2 can call the named argument of fn1 as if it were a
function name in order to recur on itself. For example, the factorial function
can be defined thusly, using the Y-combinator:
Example:
> (def Fact | (Y | (fn (fact) | (fn (n) | (if (zero? n) | then 1 | else (* n (fact (- n 1)))))))) |
|
Note however that usage of the Y-combinator for recursion is not especially
efficient, and the more traditional recursive approach is generally recommended
whenever possible (which is most of the time).
A generalization of the Y-combinator that allows the function to take any number
of arguments.
(fnlet name args body ...+)
|
Equivalent to
(Y* (fn (name) (fn args body ...))).
For example, to map the Fibonacci sequence without
defining a named function to do it:
Example:
|
'(0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765) |
Returns a function with the args partially applied to fun,
which can then be passed the remaining arguments, as many as needed to complete
the calculation. For example:
Returns a new function which is a composition of fn1 and fn2.
This function evaluates fn2 with its arguments, and then applies
fn1 to the result of fn2.
3.12 Pipe/Threading Operators
(:> initial-value fns ...) → any
|
initial-value : any |
fns : fn? |
The forward pipe operator. Given a value and a series of single-argument functions,
applies them in order from left to right and returns the resulting value.
A currying macro. Expands into an anonymous function that takes a single argument,
and inserts it as the first argument of fun, followed by the remaining
args*.
The inverse of
f>. Returns a function whose argument is placed as the last
argument to the given
fun.
(-> initial-value (fun args* ...) ...)
|
The first-argument threading macro. Works similarly to
:>, except that it
automatically applies
f> to each listed form following the initial value.
(->> initial-value (fun args* ...) ...)
|
The last-argument (as in
l>) version of
->.
3.13 State Notation
Heresy by design contains no mutable values. Variables once set cannot be reset or altered,
only shadowed by local scope. The following operators however provide a notation for performing
functions with a limited local state that is mutable using a set of special operators.
The empty State object. Used to store and retrieve values, essentially acting as a namespace for
do>.
Beginning with a fresh State object, passes that State object through the subsequent forms, then
returns it, unless
return is called in the final position with a specific value from the state.
Equivalent to
(:> State ...).
(:= name value)
|
(:= (vars ...) name value) |
The bind operator. Expands to a function that takes the current State and binds a value to
name
in the current State object. If the
(vars ...) form is provided, the listed vars are lifted from the local state and bound for use in the value clause.
(:_ fun args* ...)
|
(:_ (vars ...) fun args ...) |
The pass operator. Expands to a function that takes the current State, executes fun and ignores
its result, then returns State unchanged. If the vars clause is provided with names, those names
are available for use in fun’s arguments.
The return operator. Expands to a function that takes the present State, and returns the value associated
with
var. Note that use of this will thus terminate State, so it is best used as the final form
in a
do> block.