A programming language
version 0.3.6
Bard is a small, high-level, general-purpose programming language. It is interpreted, dynamic, and impurely functional. It has a unique object system influenced by, but different from, CLOS.
Bard is a dialect of Lisp, because Lisp is the family of languages that offer me the most pleasure in programming. Bard is designed primarily to make me happy--to make my work more pleasant. If someone else likes it or finds it interesting that's a good thing, but it is not a goal.
Bard is a work in progress. This document describes the current release, which is version 0.3.6. The Bard 0.2 series was used for about a year in product development, but not released for general use. Bard 0.4 is currently in development, and will offer improved performance and several new features.
Bard 0.3.6 is an interpreter for most of the Bard language. It runs on OSX, Windows, and Linux, and it exhibits most of the unique features of Bard. It's not quite complete; a few features, such as Actors and messages, are not implemented in the 0.3.6 interpreter. Version 0.4, which is already in development, will provide the features that are missing in 0.3.6.
Version 0.4 will also be substantially faster than 0.3.6. This release is a slow interpreter. It's not too slow to use; it's much faster than the 0.2 interpreter was, and I used Bard 0.2 for a year in game development. But don't expect Bard 0.3.6 to be fast; it isn't.
Bard 0.2 ran on iPhones and iPads. The current release does not, though I plan to bring it back to that platform in the near future, and also to port it to additional platforms, such as Android. One of my goals for the language is to be able to work in Bard on every platform I find interesting.
I have other interesting plans for Bard's future, including many new tools and features, but for now, Bard 0.3.6 is a simple command-line interpreter that runs on desktop computers. Its feature set is small, but it has already proven itself useful to me in my work. I hope others can get as much pleasure out of it as I have.
Version 0.3.6 is a bugfix release which fixes an error in method
dispatch that could cause methods specializing
on Anything
to be missed.
Like other Lisps, Bard is designed to be used interactively. It's distributed as a single executable file named "bard"; you run it by typing "bard" at the command line. When you do, Bard starts an interactive session that displays a prompt:
bard>
You can type Bard expressions at the prompt and the interpreter will evaluate them and return the results:
bard> (+ 2 3) 5 bard> (values 2 3) 2 3
Bard is a functional language, but it's not pure. You can use non-functional features if you really want to.
It's an object-oriented language, but its object system is unique. It may take some getting used to, but it's simple, and it rewards the time taken to learn it.
Bard organizes most of its features into protocols, which are collections of related functions. Classes are used in protocols to define the roles of values.
Bard functions are polymorphic. A function can execute different code depending on the values passed to it as arguments. Functions dispatch on all their arguments to choose the code to run. In most cases, functions examine the types of values in order to choose methods to run, but you can also write functions that pay attention to the actual values of arguments in selecting methods.
Bard values that support the Mapping
protocol
can be applied as if they're functions that accept a single
argument. When you apply a collection like this, it returns the
element stored on the key equal to the argument, or
nothing
if there is no such key. Sequences and
series support the Mapping
protocol, behaving as
tables whose keys are integers.
Bard encourages working at a high level of abstraction. Most of its values are instances of abstract types, and you normally don't need to know or care which specific concrete type a value has. Recursion and mapping are more natural than iteration and looping in Bard, though the language has support for both styles. Bard also provides generators as another way to write iterative code. Using a generator you can represent an iterative process as a lazy list of values, taking only as many of them as you need.
Bard has two kinds of types and two kinds of procedures. In each case, one is abstract and the other concrete. Abstract types are called classes; concrete types are called schemas. Abstract procedures are called functions; concrete procedures are called methods.
Bard provides a traditional Lisp-style macro system for extending the language syntax, and several built-in features of the language are implemented as macros.
You can define your own types using records and tuples. A record is a type that consists of named fields; a tuple is a type that consists of fields indexed by integers.
Bard provides a few powerful control constructs, including upward continuations for nonlocal exits, and dynamic stack guards that can guarantee that certain code executes even if an error or other abnormal exit is taken.
Bard supports returning multiple values from functions, and binding multiple values to lexical variables.
Bard 0.3.6 implements most of the language, but not all; a few important features are not yet implemented.
Pattern matching provides a powerful and
convenient way to destructure complex values and bind parts to
lexical variables. Bard's match
special form will
provide this feature in the 0.4 release.
Multiple inheritance provides a way to conveniently reuse functions. It's not implemented in Bard 0.3.6, but will be in 0.4.
Union types consist of collections of alternative type definitions. You'll be able to define them and type synonyms in release 0.4. Synonyms are new names for existing types, which can help your code express more clearly the intended roles of your types.
First-class continuations provide a powerful general-purpose tool for constructing custom control structures. I plan to implement them in release 0.4.
Series are unbounded data structures that make it easy to express iterative programs without using explicit loops. Implementation of series is planned for 0.4.
In Bard, an actor represents a Bard process. One Bard process can communicate with another by obtaining a reference to the actor that represents the process and sending it a message. Bard actors may run in the same process, or in different processes on the same machine, or on different machines. Any Bard data structure may be sent to an actor in a message. Implementation of actors is planned for release 0.4.
Predicate dispatch is a powerful generalization of method dispatch that enables you to specify rules for selecting methods to run. For example, using predicate dispatch you can write a method that runs only if its argument is greater than five, or only if its second argument is greater than the first. Bard 0.4 will support predicate dispatch.
Bard is designed to support image-based development. In this style of programming, the language interpreter can write a copy of its state to a file, called an image, and resume in the same state later by loading the image file. This feature is not implemented in version 0.3.6, but is planned for 0.4.
You can run Bard by putting the Bard executable in your
system's PATH and then typing "bard" at the command prompt. The
Bard interpreter starts up ready to execute Bard expressions.
You can load a Bard program using the load
function:
bard> (load "examples/namer.bard") Loading examples/namer.bard... read-names triples valid-name-part? valid-name-part? filter-name-parts filter-names parse-names match? end-segment? end-segment? next-segment merge-segments merge-segments build-name name-builder names bard>
After load
returns, you can use any of the
functions defined by the file you loaded.
Many Lisp programmers are used to using Emacs together with a Lisp interpreter. A simple Emacs mode is provided with Bard 0.3.6. See the text of "bard-mode.el" for instructions on installing and using it with the Bard interpreter.
To exit the Bard interpreter, type q:
at the
"bard" prompt.
A few example programs are supplied with the Bard 0.3.6 interpreter, distributed in the "examples" directory. You can load and run the code by starting the Bard interpreter and using the load
special form to load the examples, as shown in the previous section. Note that if you start the interpreter from some working directory other than the examples directory then you must provide a complete absolute path to the example you want to load.
Comments in the example programs show some examples of executing Bard code defined by the example sources.
Bard's syntax resembles that of other Lisps: programs are made up of expressions. There are two types of expression: atoms and lists. Taken together, atoms and lists are called s-expressions.
Here are a few examples of atoms:
nothing true false 12 2.2 next:
In Bard, unlike other Lisps, lists are not a specific
concrete datatype; rather, List
is an abstract
type that may be represented by any of several concrete types.
That's a theme that comes up over and over in Bard: values are
instances of abstract types, and it's usually more helpful to
know the abstraction a type represents than to know which
specific concrete type it belongs to. That's not
always the case, and you can always find out the
concrete type of a value if you really want to know, but more
often it's the abstract type that is interesting.
There are several types of lists in Bard. Here are a few examples:
() (0 1 2 3) (left . right) {name: "Fred" size: 'large friend: "Barney"} "Now is the time for all good men..."
These are all examples of lists, but remember that
List
is not a concrete type in Bard. Each of these
examples may have a different representation. For example, the
first one might be represented as an instance of
<null>
and the last as an instance of
<string>
. Don't assume too much about a
value from its abstract type; for example, it's true that the
last example value is a list, but it's not true that it's
represented as an inefficient linked list of characters. List
is an abstract type that tells you only the family of functions
that work with it, not how they work or how the value is
represented.
Bard's model of execution is that an evaluator reads each expression one after another, performing any computation represented by the expressions, and returning the resulting values. All the expressions must be s-expressions—that is, either atoms or lists. As in other Lisps, Bard treats a certain type of list as the application of an operator to some arguments.
Here's an example; the first element of the following list is the addition operator, and the other two are numbers. Bard sees the list and interprets it as a function call. It applies the addition operator to the number to compute a result, which it then returns and prints:
bard> (+ 2 3) 5
All expressions that you write as lists in parentheses are like this: Bard interprets them as operators being applied to arguments. If you want to create a list instead of calling a function, you can use the quote to tell Bard not to evaluate the expression:
bard> '(+ 2 3) (+ 2 3)
There's also one kind of atom that Bard evalutes to return a value. When it sees a symbol, Bard treats it as the name of a variable. It looks up the value of the variable and returns it:
bard> + #<primitive +>
The value of the symbol +
is the addition
function, whose name is "+". That function is a
primitive—that is, it's a function that
is built into the interpreter.
These two kinds of values—symbols and parenthesized lists—are special. Every other value in Bard is just a value; the evaluator sees the value and returns it. That kind of value—the kind that returns itself when evaluated—is naturally called a self-evaluting value. Almost all Bard values are self-evaluating.
That's pretty much all there is to Bard syntax: expressions made of atoms and lists. There are a few special bits of syntax, mostly to make it easy to write various kinds of data. We'll cover those details in the next section.
Most Bard values are instances of abstract types that provide literal syntax. The purpose of the literal syntax is to make it easy and convenient to write commonly-used expressions. (In fact, my goal is to arrange for every Bard value to have a literal syntax that both a human being and the Bard interpreter can read easily.)
Here are examples of the literal syntaxes for the most important built-in types:
true false
#\C #\space
2.3
(function Integer Integer -> Ratio)
next:
(^ (x y) (* x y))
nothing () [] "" {}
'(left . right)
2/3
"Hello, world!"
{a: 1 b: 2 c: 3}
A Bard program consists of a series of s-expressions. Some expressions are evaluated to produce results or to cause the machine to produce side-effects like printing some values or making a network connection. Other expressions—in fact, most of them—define data structures or operations you'd like Bard to perform.
The way to think about a Bard program is that you enter each s-expression at the Bard prompt and the evaluator computes a resulting value. If computing the result requires the evaluator to print something or open a network connection or create a window on the screen, then that's what happens.
If you ask Bard to load a program from a file, much the same thing happens: it opens the file and reads s-expressions from it one-by-one, performing the computations required by the expressions.
If you build a program using Bard, then when the program starts it behaves the same way; that is, it behaves as if it's reading and executing each of the expressions that made up the source code of the program when it was under development. A delivered program may not include any source code, and it may not actually read any files when it starts up, but it still behaves as if it's reading s-expressions and evaluating them.
In Bard 0.3.6, the only way to run a program is to start the interpreter and tell it to load a file. If the file contains only definitions, then you can start the program running by typing an expression at the "bard" prompt.
That won't always be the only way to run a program. Bard 0.2 could be deployed as a library that executed built-in code, and Bard 0.4 will provide tools for building a program and delivering it as a compiled binary. It will also provide the ability to run as a script interpreter, loading and running a program file passed to it from the command line.
In version 0.3.6, though, Bard is more limited. If you want to run a program you must first launch the interpreter and then tell it to load your program's source file.
The most basic control structure in Bard is evaluating a form:
bard> 1 1
In a way, that's all a Bard program is—evaluating a form—even if it's a complex application. Most Bard programs consist of a single simple expression that gets evaluated at the start. What makes it a full-featured program is that the simple expression evaluates one or more forms, and each of those evaluates more forms, and those evaluate more, and so on, until a whole complex tree of evaluations runs, resulting in a program that comes to life.
To compute something in Bard, you evalute an application. An application is an expression that applies an operator to some arguments. A simple example is adding two numbers:
bard> (+ 2 3) 5
As you can see, we write an application as a list. Why does the "+" come first, instead of going in the middle as we all learn in school? Because in Bard, as in other Lisps, the operation always comes first, and its arguments always come after. It takes a little getting used to, but it's very simple, and it means there are hardly any rules to learn about how to write expressions. They're pretty much all the same: operator first, then arguments.
The arguments are expressions, too, and they don't have to be atoms. They can be more applications:
bard> (+ (* 2 3) (* 4 5)) 26
Here, Bard multiples the 2 and 3 to get 6, and multiples the 4 and 5 to get 20, then adds them together. Many expressions in Bard are like this one: some inner expressions to compute intermediate results, and an outer expression to combine them.
Bard wouldn't be much of a programming language without a
way to decide between alternatives. The basic tool for making
such decision is a special operator called if
:
bard> (if (odd? (* 3 3)) (display "odd") (display "even")) odd
if
evaluates its first argument, called the
test. If the test returns a true value then it evaluates the
second argument; otherwise it evaluates the third. It's a
simple but effective way to choose between two different
alternatives.
What if you want to choose from more than two alternatives?
cond
is a conditional form that can choose from
any number of alternatives:
bard> (cond ((sunday?) "go to meeting") ((monday?) "I hate mondays") ((tuesday?) "weld") ((wednesday?) "up and Addam") ((thursday?) "no thanks, just had a drink") ((friday?) "thank God") (else: "rest")) "thank God"
cond
takes each of its arguments—called
clauses—in turn, one at a time. It evaluates the first
expression in each clause and, if it returns a true value, then
evaluates the rest of the expressions in the clause.
cond
evaluates at most one complete clause; once
it has chosen a clause to evaluate, and then evaluated all of
its expressions, it returns whatever the last expression in
that clause returned. In the example above,
(friday?)
must have returned true, because the
value returned from the cond
was the expression in
that clause. The other text values in the other clauses were
not evaluated.
What if you want to evaluate more than one expression in one
of the alternatives of an if
? The
begin
form is for that precise purpose:
(if (approved? purchase) (begin (compute-sales-tax subtotal) (debit account) (display "Approved")) (begin (cancel-purchase) (display "Declined")))
begin
is useful only when you want to evaluate
expressions that have side-effects like printing a message or
asking a service to do something. Expressions that perform pure
calculations have no use for begin
because it
discards all the values produced except for the last one.
begin
is an example of an
imperative operator. Imperative operators
exist mainly to command the computer to do something; it's not
important whether they return a result. Often they do in Bard,
but that's just because most Bard operators are functions that
compute and return results. The important effect of an
imperative operator is the task it commands the computer to
perform.
By contrast, most Bard operators are functional; that is, most Bard operators exist primarily to compute and return a result. Bard is a functional language, meaning that its design caters primarily to writing programs made of functions that return results.
Unlike some functional languages, though, Bard doesn't
prevent you from evaluating expressions purely for their side
effects on the computer, and it provides forms like
begin
to make that kind of effect easy to
achieve.
Another common imperative idiom is looping. Imperative programs typically comprise several iterative loops that perform a series of actions repeatedly. You can create that kind of loop very easily in Bard:
bard> (repeat (newline) (display "Hello!")) Hello Hello Hello Hello Hello Hello Hello Hello Hello Hello ...
This example will go on printing greetings forever, or until
you stop it by killing the program. So how do you tell
repeat
when to stop?
You don't. Repeat always repeats forever.
So how do you use repeat
without getting stuck
in an infinite loop?
You use with-exit
:
bard> (def $x 0) $x bard> (with-exit (return) (repeat (begin (set! $x (+ $x 1)) (if (> $x 9) (return 'done!))))) done!
with-exit
creates a procedure that returns from
the with-exit
form, no matter where in the body of
the expression it's called. In this example, we define a
variable named $x
, initialize it to zero, then
execute a repeat
form that repeatedly increments
$x
. As soon as $x
is greater than 9,
the return
procedure, which was created by the
with-exit
form, returns the value
done
.
These two simple control structures make it easy to construct a wide variety of iterative processes.
Bard offers a more functional alternative for looping,
though. You can achieve the same effect using
loop
:
bard> (loop again ((x 0)) (if (> x 9) 'done! (again (+ x 1)))) done!
Why do I call this a "more functional alternative?" Because
loop
doesn't require us to assign values to a
mutable variable. Instead, it creates a local procedure (in
this case named again
) and calls it recursively
with whatever value we supply to it. Loop looks like an
iterative construct, and acts like one, but it runs without
side effects; each time through the loop the variable
x
is a new variable in a new lexical
environment.
Isn't that more costly that the pure iteration of
repeat
? It is in Bard 0.3.6, but the language
design provides a solution: Bard is meant to support
tail recursion elimination, a technique that
enables recursive function calls to run in constant space. I
expect to implement that optimization in release 0.4.
In one of the examples above we saw a simple example of a definition:
bard> (def $x 0) $x
This example defines the variable $x
and binds
it to the value 0
. A variable defined this way is
visible everywhere in a Bard program; it's a global
binding. All definitions in Bard establish global
bindings.
Bard provides several different kinds of definitions, but
all of them establish global bindings, and all can in principle
be performed by def
. In most cases, though, you'll
use more specialized defining forms to create
the more complex definitions.
As an example, one of the most commonly-used defining forms
is define method
:
bard> (define method (swap x y) (values y x)) swap bard> (swap 1 2) 2 1
define method
creates a method and adds it to a
function. In this example, the function is bound to the
variable swap
. If the function already exists then
define method
adds the method to it. If it doesn't
exist, define method
creates it and binds it to
the variable.
Bard's defining forms include:
def define class define macro define method define protocol define record define tuple define variable
Most Bard programs consist mainly of defining forms, plus one or a few expressions that are evaluated to start the computations created by the defining forms.
Functions are the heart of Bard. A function is a value that can be applied to other values to compute one or more results. Bard's functions are polymorphic; in other words, the same function can do two or more different things, depending on the arguments you pass to it.
A function chooses the code to run by examining the argument values you pass to it. You control the code that functions run by defining methods for different cases. Here's a simple example:
(define method (recognize x) "I don't know what that is") (define method (recognize x) with: ((x <fixnum>)) "That's a fixed-size integer.") (define method (recognize x) with: ((x <string>)) "That's a text string.") bard> (recognize 3) "That's a fixed-size integer." bard> (recognize "Hello!") "That's a text string." bard> (recognize 'foo) "I don't know what that is"
In this example we define three methods on
recognize
, then apply the function to three
different values. It returns a different result in each case,
because the types of the arguments passed to it are
different.
If the function didn't exist when the first define
method
was evaluated, then Bard created it. Here's what
the swap
function looks like when Bard prints
it:
bard> swap (function (swap Anything Anything -> Anything))
A function by itself doesn't know how to do anything but
look for methods that match its argument types. The printed
form of swap
tells us that it's a function that
accepts two arguments of type Anything
and returns
one argument of type Anything
.
Anything
is a class—an
abstract type. It's the class of all Bard values; every value
is an instance of Anything
, so swap
works on any value we give it.
This feature—Bard's polymorphism—enables the same function to execute different code to handle different types of arguments. It means that a single function can operate usefully on a variety of different argument types. That in turn means that Bard can provide generic functions that implement similar operations over many different representations of data.
The most obvious example of the use of generic functions is in implementing arithmetic: numbers are represented in many different ways in computing, but the meaning of addition, subtraction, multiplication, and division doesn't change for different kinds of numbers; the strategy for combining numbers changes, but the meaning remains the same. Polymorphic functions make it simple to provide arithmetic operators that work across all the different types of numbers that Bard supports. It doesn't need special versions of addition for different kinds of numbers, and if a new kind of number is added to an implementation of the language, supporting it is a simple matter of writing new methods for the existing functions.
Arithmetic isn't the only use for generic functions, though.
All of Bard is organized into collections of generic functions
called protocols. Each protocol provides a set
of related functions that operates on a family of types to
provide useful computing features. Bard provides protocols with
names like Calculating
, Comparing
,
Mapping
, Ordering
, and more. Each
protocol implements a useful library of functions for practical
purposes, and each function—because it's
polymorphic—can be extended to support new types
as-needed.
We've already seen examples of Bard's classes. Classes in Bard are purely abstract types; that is, there are no direct instances of Bard classes.
Bard's classes differ from the classes in most other object-oriented languages, in that, as pure abstractions, they have no data members and no internal structure. In fact, a class is nothing more than a name: it names a role played by a type in a protocol.
Let's take a look at a very simple protocol, so that we can see more concretely what these concepts mean. Here's the protocol definition:
(define protocol Rational (numerator Ratio -> Integer) (denominator Ratio -> Integer))
This definition establishes a new protocol bound to the
global variable Rational
. It has two functions,
numerator
and denominator
. Each of
the two functions accepts one argument of type
Ratio
and returns a result of type
Integer
.
These types, and the functions as well, are purely abstract. We cannot successfully call either function because we haven't yet defined any methods for them.
Ratio
and Integer
are also purely
abstract. It so happens that they are built into Bard, but if
they weren't, if we had just created them, then they would have
no members; there would be no instances of either Ratio or
Integer.
They also have no internal structure and no concrete
representation. Ratio
and Integer
are
just names—variables, if you like, whose values are
collections of other types.
So how do other types become members of classes? By being bound in method definitions. Let's see how we can make a concrete type become a member of a class:
(define method (numerator r) with: ((r <fixnum>)) r) (define method (denomenator r) with: ((r <fixnum>)) 1)
That's all it takes; now values of type
<fixnum>
are instances of
Ratio
, and <fixnum>
itself is a
member of Ratio
because we told Bard how to
execute the protocol's functions when values of that concrete
type are passed to it.
Protocols are the connection between Bard's abstract types (called classes) and its concrete representations of data (called schemas). Any schema can become a member of any class; you just have to define suitable methods for a protocol that defines the class.
An actor in Bard represents a running Bard
process. Any Bard process can obtain a reference to the
Actor
value that represents itself by calling
(this)
.
Bard can also create new actors by applying the
make
function to an actor schema. If the call
succeeds, it returns a new instance of Actor
that
represents a new Bard process. The new actor's process may
share the creating process, or it may inhabit a newly-created
process on the same computer, or, when called under the right
circumstances, may represent a newly-created Bard process on a
remote computer.
Once a Bard process has an actor, it can send messages to it
using the send
function. send
accepts
any Bard value and copies it to the receiving actor in the form
of a message. Bard can even send actors to
other actors, enabling it to dynamically construct networks of
communicating processes.
Messages appear in a queue on the receiving actor in
arbitrary order. The receiving actor can collect inbound
messages at any time using the receive
special
form. receive
supports a pattern-matching syntax
that enables it to selectively retrieve incoming messages and
destructure them, binding their parts to lexical variables.
Actors are not implemented in Bard 0.3.6. I expect to have them implemented in the 0.4 release.
The previous sections provided a brief overview of the Bard language. This section presents a comprehensive reference to all the constants, variables, types, and protocols implemented in Bard 0.3.6.
Items in this section are named constants built into the Bard runtime.
false
— the Boolean false value
nothing
— the absent value; the empty
collection
true
— the Boolean true value
undefined
— no value at all
This section lists schemas. Schemas are concrete types; that is, they're types that describe the layout of bits and bytes that make up families of values.
Types are themselves values in Bard; you can pass them as
parameters to functions, return them as values, and store them
in variables. Some of the schemas listed below are the types of
types; for example, <base-schema>
is the
type of standard Bard types, and
<primitive-schema>
is the type of the
lowest-level built-in types used to build the rest of the Bard
type system.
<alist-table>
Tables represented as lists of key/value pairs
<base-schema>
Standard types that are built into Bard
<bignum>
Unlimited precision integers
<boolean>
True and false values
<character>
Text characters
<class>
Bard classes
<fixnum>
Compactly-represented integers
<flonum>
Single-precision floating-point numbers
<foreign-schema>
Values defined outside of Bard (for example, C data)
<function>
Polymorphic applicable objects
<generator>
Values that are partly like functions and partly like series. A
generator is an iterative function that returns values one at a
time. You can easily take one value at a time, or a certain
number of values. Generators can return an infinite number of
values, and provide an easy and efficient way to process
unbounded streams of data.
<interpreted-method>
Concrete procedures
<iostream>
Objects that represent data channels. Bard code can read data
from input streams and write it to output streams.
<keyword>
Keywords are like symbols that never represent variables. A
keyword is a self-evaluating name; evaluating it always
produces the keyword itself. Bard uses keywords for, among
other things, labeling fields in tables and arguments in
functions to help make code more easily understandable.
<null>
The only instance of <null>
is
nothing
. nothing
is the absent value,
and also the empty collection. It's the only Bard value that is
both an atom and a sequence.
<pair>
A collection with two elements, named left
and
right
. <pair>
is also used as
one representation of a list: the left
element
serves as the head of the list and the right
serves as the tail.
<primitive-procedure>
A Bard procedure provided by the core virtual-machine code.
<primitive-schema>
A built-in concrete datatype supplied by the Bard runtime.
<primitive>
A virtual-machine primitive. VM primitives are relatively
simple bits of code implementing specific functions or other
procedures. Bard initializes its working set of primitives from
a table at startup. Primitives are modular pieces of Bard
semantics. A <primitive-procedure> object, on the other
hand, is built into the core logic of the VM.
<protocol>
A collection of related functions representing an API that
defines a set of types. Examples include the
Comparing
and Ordering
protocols.
<ratnum>
A ratio of integers.
<record>
A user-defined concrete datatype consisting of named
fields.
<string>
A text string.
<structure-schema>
A concrete, built-in datatype represented by a record
structure.
<symbol>
A name object used to identify variables, among other
things.
<tuple>
A user-defined datatype consisting of a set of fields accessed
by index.
<undefined>
The type of the unique value that represents the inability to
compute a meaningful value. The value undefined
is, for example, the value of a variable that has not been
initialized.
<union>
A user-defined concrete datatype that consists of a collection
of one or more other datatypes. An example of a union type
might be, for example, an identifier type defined as either an
integer or a symbol.
<url>
A concrete datatype that represents a Uniform Resource Locator.
Bard uses URLs as a standard way of referring to files and
other resources stored either locally or in some
network-accessible filesystem.
In Bard, a class is an abstract type that may be represented by any number of concrete types. You can think of a Bard class as a variable that is bound to any type that meets the requirements set by a protocol.
Bard classes are quite different from classes in most other programming languages. In most languages, a class combines a name, a definition of some number of data fields, a set of subtype/supertype relationships with other classes, and a set of methods.
Bard's system of types works differently. A class is nothing
more than the name of a role as defined by a protocol. A
protocol is a defined collection of functions. Each function is
polymorphic—it can accept many different types of
arguments. A function gives names to the types of arguments it
accepts. For example, the numerator
function shown
in the Types section called its input
argument Ratio
and its output
Integer
:
(numerator Ratio -> Integer)
This definition means that any type that
numerator
accepts is, by definition, a
Ratio
. When we defined a method for
numerator
that specialized on
<fixnum>
, we were also implicitly asserting
that <fixnum>
was a member of
Ratio
.
Classes, therefore, are extremely simple. A class is just a name.
This section lists the classes that are built into Bard 0.3.6.
Anything
The class of all values.
Applicable
The class of values that can be applied to arguments.
Boolean
The class of values that are true or false.
Character
The class of values that are elements of Text objects.
Class
The class of values that are classes.
File
The class of values that represent files in a filesystem.
Float
The class of floating-point numbers.
Fraction
The class of fractional numbers.
Function
The class of polymorphic functions.
Generator
The class of objects that generate sequences of values.
InputStream
The class of values that represent data inputs.
Integer
The class of whole numbers.
IODirection
The class of values that signify input or output.
IOMode
The class of values that signify file-access modes (append, overwrite, replace, and so on).
IOType
The class of values that signify the types of values that IOStreams can produce or accept.
Keyword
The class of names that evaluate to themselves.
List
The class of bounded, ordered collections of values.
Method
The class of monomorphic procedures.
Null
The class of empty collections and absent values.
Number
The class of numeric values.
Orderable
The class of values that can be sorted into stable order.
OutputStream
The class of values that represent data outputs.
Pair
The class of associations between one value and another.
Protocol
The class of Bard protocols; protocols are lists of related functions that define classes.
Ratio
The class of rational numbers.
Schema
The class of values that represent concrete types (that is, types that can have direct instances).
Stream
The class of objects that can consume or produce unbounded numbers of values.
Symbol
The class of names that represent variables.
Table
The class of objects that represent sets of key/value mappings.
Text
The class of text values.
Type
The class of values that represent abstract or concrete datatypes.
Undefined
The class of things that are not values, or, if you prefer, the class of values that cannot be dealt with coherently.
URL
The class of values that represent Universal Resource Locators.
Bard's special forms and built-in macros provide its basic program
structure and flow of control. Bard 0.3.6 has 27 special forms,
counting all the variations of define
as a single special
form, of which a few are not yet implemented.
Special form
add-method!
(add-method! fn (type ...) method)
Adds the method method to the function fn as a match for the types given in type ... Subsequent calls to fn with arguments matching the supplied types will apply method.
Special form
and
(and expr ...)
Evaluates the first expression expr. If the result is a true value, evaluates the next expression, and continues in the same way until either an expression returns a false value, or the last expression is evaluated. Returns the last value produced.
Special form
begin
(begin expr ...)
Evaluates the expressions expr ... from left to right, returning the value returned by the rightmost expression. Other values computed in the process are discarded.
Special form
cond
(cond ((test) expr ...) ... [(else: expr ...)])
Evalutes test; if the value returned is true, evalutes the
associated expressions expr ... as if in the body of
a begin
form. The the value returned is
false, cond
skips the associated expressions and moves to
the next clause, repeating the process for each clause in
the cond
form. If an else:
clause is
present, its associated expressions are guaranteed to be executed if
no previous clause is, because the else:
keyword is
logically true. If there's no else:
clause and
no test form returns true, then cond
returns
nothing.
Special form
def
(def var val)
Assigns val to the global variable var, creating the variable if it does not already exist.
Special form
define
(define variable-type var body)
Assigns a new value to the global variable var, creating
the variable if it does not already exist. The value created or
modified by define
depends on variable-type as
follows:
(define class cname class-name)
Defines a class named cname and binds it to cname.
(define macro (macro-name arg ...) body)
Defines a macro named macro-name. When macro-name is called, Bard expands the macro call, replacing it with the expression constructed by body and evaluating the result in place of the original macro call.
(define method (function-name (arg...)) with: (type-spec...) body)
Creates a method and adds it to the
function function-name, matching arguments whose types are
given by type-spec .... If the function does not exist,
then define method
creates it.
A type-spec make take any of the following forms:
Anything
.(var type)
- var matches
values of type type.(var (exactly expr))
- var matches
values equal to expr.(define protocol var (class ... -> class ...) ...)
Creates a new protocol and binds it to the variable var,
creating the variable if it doesn't already exist. In the
process, define protocol
defines the classes given
by class ..., if they don't already exist. The protocol may
define any number of functions. Each function may accept and return
any number of values. define protocol
does not create
methods for the functions it defines.
(define record record-name field-spec ...)
Defines a new record type and binds the type object to the global variable record-name.
A field-spec make take either of the following forms:
var
- the record has a field named var.(var default: val)
- the record has
a field named var that is initialized with the
value val, if no initial value is supplied when an instance
is created.(define tuple tuple-name slot-count: count [default: expr])
Defines a new tuple type with count slots, and binds the
type object to the global
variable tuple-name. If expr is present then slots
in newly-created instance of the tuple type will contain the value
of expr, unless initial values are supplied at creation time;
otherwise, uninitualized slots will contain nothing
.
(define variable var val)
Assigns val to the global variable var, creating the variable if it does not already exist.
define variable
is a synonym for def
that
is useful when you want to make variable definitions stand our more
prominently.
Special form
ensure
(ensure before-form during-form after-form)
Evaluates the
expressions before-form, during-form,
and after-form, ensuring that all three execute, even
if during-form fails to complete because of an error, a
nonlocal exit, or some other exceptional
condition. ensure
is useful for ensuring that important
code executes, even when parts of a procedure are at risk of
failure. For example, you can use it to make sure that cleanup code
runs after a risky procedure call, even if the risky procedure
fails.
Special form
function
(function class ... -> class ...)
Returns a new function that accepts and returns values as specified by class ... -> class ...
. The new function has no methods, belongs to no protocol, and is not necessarily bound to any name.Special form
generate
(generate generator-name ((var val) ...) expr ... [(yield return-val)] expr ... [(generator-name expr')] ...)
Returns a new generator whose behavior is given by
the generate
expression. generate
binds
generator-name to a local function whose parameters and
initial inputs are given by ((var val) ...). The
generator can be executed by calling the function next
,
but it's more common to use the functions of the List protocol to
retrieve values from a generator.
Each time next
is called, whether directly, or because
a List function was applied, the generator enters its body, evaluating
the expressions defined there. If it reaches a yield
expression, it immediately stops execution and returns the arguments
passed to yield
. If next
is called again,
the generator resumes execution from where it last returned.
If an expression is encountered that calls generator-name as a function, then control resumes from the beginning, binding the generator's parameters to the arguments of the generator-name call.
generate
thus establishes a looping function that can
return multiple times—once for each time a yield
expression is encountered; and it returns the captured state of the
looping function as a generator object.
Generators provide a convenient way to represent unbounded iterative processes and infinite data structures.
Special form
if
(if test then-clause [else-clause])
Evaluates test. If test returns true
then if evaluates and returns the value
of then-clause; otherwise, it evaluates and returns the value
of else-clause. If no else-clause is supplied then
when test returns false, if
returns nothing
.
Special form
let
(let ((var ... vals) ...) expr ...)
Establishes a new lexical environment, binding the variables var... to initial values vals, then evaluates the expressions in expr ... in the resulting environment.
A simple use of let
looks like this:
(let ((x 2) (y (+ x 1))) (* x y))
This example returns 6; first it binds x to 2, then binds y to x+1, then it multiples x and y, yielding 6.
let
can also bind multiple variables to the results of
a function call that returns multiple values:
(let ((x y (values 2 3))) (* x y))
This example also returns 6. Note that the last expression in a variable-binding form must always be an expression that produces values.
Special form
loop
(loop loop-name ((var val) ...) expr ... [(loop-name expr')])
Creates a new lexical environment in which loop-name is
bound to a looping function whose parameters and initial inputs are
given by ((var val) ...), then evaluates the
expressions in expr .... If a call to the
function loop-name is encountered, control resumes at the
start of the loop
form, but with the parameters bound to
the values passed in the call. loop
makes it convenient
to express an iterative process as a recursive local function.
Special form
match
(match ((pattern expr) ...) expr ...)
NOTE: match
is not yet implemented in Bard
0.3.6
match
desconstructs expr using
pattern-matching, matching variables given in pattern to
corresponding values in expr, then evaluates the expressions
in the body of the match
in the resulting
environment.
Special form
method
(method ([var] ...) expr ...)
Returns a new method whose input parameters are given by var ... and whose behavior is given by expr ....
Special form
not
(not expr)
Returns false
if expr returns a true value,
and true
otherwise.
Special form
protocol
(protocol (class ... -> class ...) ...)
Returns a new anonymous protocol whose member functions are given by (class ... -> class ...) ....
Special form
quasiquote
`expr
Normally written as "`", quasiquote
tells Bard not to
evaluate its argument expression, but in a way that permits the
programmer to selectively unquote parts of the expression.
quasiquote
is easiest to explain by example:
`(+ 2 3)
returns the list (+ 2 3)
; the input expression is
returned unchanged.
`(,+ 2 3)
returns the list (#
; the first
element of the expression was unquoted, and so, instead of the
symbole +
, the output contains the value of the variable
it names.
Special form
quote
'expr
Normally written as "'", quote
tells Bard not to
evaluate its argument expression.
quote
is easiest to explain by example:
'(+ 2 3)
returns the list (+ 2 3)
; the input expression is
returned unchanged. Without the quote, Bard will interpret the
expression as a function call, and will evaluate it to produce 5.
Special form
receive
(receive [pattern])
NOTE: receive
is not yet implemented in Bard
0.3.6
Returns the next message sent to the current Bard process, or,
if pattern is present, the next message
matching pattern. Messages are data transferred from one Bard
process to another (or from a process to itself) using
the send
special form.
Special form
remove-method!
(remove-method! fn (type ...))
Removes the method matching the types given by type ... from the function fn. Subsequent calls to fn will not match type ....
Special form
repeat
(repeat expr)
Repeatedly evaluates the
expression expr. repeat
always creates an
infinite loop. The body of expr can terminate the loop by
wrapping the repeat
with a with-exit
form
and calling the bound exit procedure.
Special form
send
(send actor message)
NOTE: send
is not yet implemented in Bard
0.3.6
Sends the value message to the Bard process represented by the actor actor. actor may represent a Bard process on the same host as the sending process, or on the remote host, or may even represent the sending process itself. message may be any Bard value, subject to some platform-specific restrictions on the values that can be serialized. Notably, actors can be sent as messages, and Bard processes can use messages to dynaically establish communication networks.
Special form
set!
(set! var val)
Changes the value bound to var, replacing it with val.
NOTE: set!
defines some features that are not
fully implemented in Bard 0.3.6. These features will be added in 0.4.x
releases, and include support for general assignment (that is,
assignment to bindings other than simple variables), thread-safety
guarantees, and support for immutable variables.
Special form
time
(time expr)
Evaluates expr and returns the result, printing information about the time taken by the evaluation.
Special form
undefine
(undefine var)
Retracts the definition
of var. After undefine
returns, var is
no longer defined as a global variable.
Special form
unless
(unless test expr ...)
Evaluates test. If it returns a false value
then unless
evaluates expr ... as if they form
the body of a begin
form, returning the last value
produced; otherwise, unless
returns nothing
.
Special form
values
(values expr ...)
Evaluates the expressions in expr ... from left to right,
returning all values produced. Bard supports multiple-value returns;
each value produced is returned as a separate value, rather than as an
element of a collection. You can use let
to bind multiple
returned values.
Special form
when
(when test expr ...)
Evaluates test. If it returns a true value
then when
evaluates expr ... as if they form
the body of a begin
form, returning the last value
produced; otherwise, when
returns nothing
.
Special form
with-open-file
(with-open-file (var path [direction: iodirection]) expr ...)
Creates a new lexical environment in which var is bound to
a stream created by opening the file at path, then evaluates
the expressions given by expr ... in that environment. When
control leaves the with-open-file
form, whether normally
or because of an error or nonlocal exit, the file is
closed. iodirection may be either 'input
or 'output
.
A Bard protocol describes a set of related functions and defines the classes of their parameters. This section describes the protocols built into Bard 0.3.6 and documents the functions they define.
The Addressing
protocol defines functions used
to locate files and other resources, both in the local
filesystem and in the larger world of network-accessible
systems.
Function
url
(url {scheme: Name domain: Name port: Name path: Name query: Name }) -> URL
Constructs and returns a new URL
value whose
fields are initialized using the values supplied with the
keyword parameters shown.
Function
url-domain
(url-domain URL) -> Name
Returns the domain element of the URL.
Function
url-path
(url-path URL) -> Name
Returns the path element of the URL.
Function
url-port
(url-port URL) -> Name
Returns the port element of a URL.
Function
url-query
(url-query URL) -> Name
Returns the query element of a URL.
Function
url-scheme
(url-scheme URL) -> Name
Returns the scheme element of URL.
The Applying
protocol defines functions used in
manipulating other functions—for example, in applying
functions to arguments, or creating new functions by combining
existing ones.
Primitive
apply
(apply Applicable List) -> Anything &
Returns the result of applying the Applicable to the List.
Primitive
complement
(complement Applicable) -> Applicable
Returns a method of one argument that returns a Boolean. The Applicable argument should be a function or method of one argument that returns a Boolean. The newly created method returns true when the input returns false, and vice versa.
Primitive
compose
(compose Applicable &) -> Applicable
Returns a new method that has the same effect as applying the input
Applicables one after another. In other words, if you
call ((compose a b c) x)
, the result is the same as
calling (a (b (c x)))
.
Primitive
constantly
(constantly Anything) -> Applicable
Returns a new method that accepts any number of arguments, and always returns the input value.
Primitive
eval
(eval Anything) -> identity &
Evaluates the input expression and returns the resulting value.
Primitive
flip
(flip Applicable) -> Applicable
Accepts an argument that is a method of two arguments. Returns a
new method that behaves identically to the input, except that it
accepts its arguments in reverse order. flip
is often
useful when composing or partially applying methods.
Primitive
identity
(identity Anything) -> Anything
Returns its input argument. identity
is often useful
in mapping and reducing idioms.
Primitive
partial
(partial Applicable Anything &) -> Applicable
Returns a new method that represents the input Applicable partially
applied. As an example, calling (partial + 1)
returns a
method that adds one to its argument; calling (partial *
2)
returns a function that doubles its argument.
The Calculating
protocol defines functions used
in arithmetic and other ways of combining numbers.
Primitive
*
(* Number Number &) -> Number
Returns the product of its arguments.
Primitive
+
(+ Number Number &) -> Number
Returns the sum of its arguments.
Primitive
/
(/ Number Number &) -> Number
Returns the quotient of its arguments.
Primitive
-
(- Number Number &) -> Number
Returns the difference of its arguments.
Primitive
even?
(even? Integer) -> Boolean
Returns true if its argument is an even integer.
Primitive
max
(max Number &) -> Number
Returns the greatest of its arguments.
Primitive
min
(min Number &) -> Number
Returns the least of its arguments.
Primitive
odd?
(odd? Integer) -> Boolean
Returns true if its argument is an odd integer.
Primitive
random
(random Integer) -> Number
Returns a psuedo-randomly-chosen integer in the range from zero to one less than its argument, where the Integer argument must be a nonnegative integer.
Primitive
quotient
(quotient Integer Integer) -> Integer
Returns the integer quotient of its arguments.
Primitive
remainder
(remainder Integer Integer) -> Integer
Returns the integer remainder of its arguments.
The Comparing
protocol defines functions that
test whether one value is equivalent to another.
Function
=
(= Anything Anything &) -> Boolean
Returns true if its arguments are equal. =
is a polymorphic function that may be extended by adding methods.
The Converting
protocol defines functions that
convert one type to another by creating new values from old
ones.
Function
as
(as Type Anything) -> Anything
Converts its second argument to a value of the type given in its first. If the Type argument is identical to the Anything argument's concrete type then the value may be returned as if by identity
; otherwise, a new value that is an instance of Type, and which is equivalent to the input value, is constructed and returned. as
is a polymoprhic function that is intended to be extended by adding methods, in order to provide a convenient utility for converting values from one type to another. Bard provides numerous built-in methods for as
on its built-in types.
Function
make
(make Type & {}) -> Anything
Constructs and returns a new instance of Type. The table argument signifies that make
accepts many different keyword arguments, which vary according to Type, and which are used to initialize the newly-constructed value.
NOTE: the implementation of make
is incomplete in Bard 0.3.6; releases in the 0.4.x series will add additional features providing more extensive control over how values are constructed and initialized.
The Creating
protocol defines functions used to
construct new values.
The Generating
protocol defines functions that
can turn functions into sequences of values. A
generator is an object that behaves like a
stream or sequence, producing values by repeatedly executing a
function.
Generators cache the values produced, so that retrieving the first N values produced by a generator incurs the cost of computing them only the first time.
Primitive
cycle
(cycle Anything) -> Generator
Returns a generator that produces the argument to cycle
on every yield
. You can think of the value constructed by cycle
as an infinite list of values identical to its argument.
Primitive
generated-count
(generated-count Generator) -> Integer
Returns the count of values computed and cached so far by the generator.
Primitive
generated-values
(generated-values Generator) -> List
Returns the cached list of values produced so far by the generator.
Primitive
iterate
(iterate Applicable Anything) -> Generator
Given an Applicable argument fn and an argument val, iterate
first applies fn to val, then to (fn val)
, then to (fn (fn val))
, and so on without limit. Each time next
is applied to the returned generator, the next value in this sequence is produced.
Primitive
next
(next Generator) -> Anything
Returns the next value from the generator.
Primitive
next-n
(next-n Generator Integer) -> List
Returns a list of the next N values from the generator.
Primitive
range-from
(range-from Integer) -> Generator
Returns a new generator that produces integers beginning with the Integer argument, and increasing in value by one on each call to next
.
The Listing
protocol defines functions that
arrange values into ordered sequences, and that manipulate such
sequences.
Function
add-first
(add-first Anything List) -> List
Returns a new list whose tail is the same as the second argument, and whose head is the first argument.
Function
add-last
(add-last List Anything) -> List
Returns a new list containing the elements of the first argument, plus the second argument added at the end.
Function
any
(any List) -> Anything
Returns an arbitrary element from the List argument. Generally speaking, the returned element is chosen psuedo-randomly, but other options are allowed for cases where a random choice is impossible or inconvenient (for example, in the case of an unbounded list or stream).
Function
append
(append List List) -> List
Returns a new list whose elements are those of the first argument followed by those of the second.
Function
by
(by Integer List) -> List
Returns a list of lists, where each sublist is composed of an number of elements from the second argument. The number of elements in each sublist is equal to the first argument, except possibly the last, which may hold fewer elements.
Function
drop
(drop Integer List) -> List
Returns the elements of the second argument less the first N, where N equals the first argument.
Function
element
(element List Integer) -> Anything
Returns the element of the list at the index given by the integer argument. The index is zero-based.
Function
empty?
(empty? List) -> Boolean
Returns true if the count of elements in the list is zero.
Function
filter
(filter Applicable List) -> List
Returns a new list containing zero or more elements of the second argument, in the same order that they appear in the input list. The elements of the new list are chosen by applying the applicable argument to each element; when the applicable returns true, the element is added to the output; when it returns false, the element is discarded.
Function
first
(first List) -> Anything
Returns the head of the list (that is, element zero).
Function
last
(last List) -> Anything
Returns the last element of the list.
Function
length
(length List) -> Integer
Returns a count of elements in the list.
Primitive
list
(list &) -> List
Constructs a new list whose elements are the arguments to list
, in the same order as their appearance in the argument list. The concrete type of the new list is not specified.
Primitive
map
(map Applicable List &) -> List
Returns a new list containing one element for each element in the input lists. The number of input lists must be equal to the number of arguments accepted by the Applicable argument. The output list is constructed by taking an element from each of the input lists and applying the applicable argument to them, then repeating with the tails of the input lists until at least one of them is empty.
For example:
bard> (map odd? [0 1 2 3]) (false true false true) bard> (map + [1 1 1] [2 3 4]) (3 4 5)
Function
member?
(member? Anything List) -> Boolean
Returns true if the list argument contains an element equal to the first argument.
Function
next-last
(next-last List) -> Anything
Returns the element just before the last element in the list.
Primitive
partition
(partition [Applicable &] List) -> List &
Applies zero or more functions to the list argument, yielding zero or more values as results. If no function arguments are passed, then the list argument is returned unchanged. If a single function is supplied then the result of partition
is the same as the result of map
.
If more than one function is supplied then the number of outputs is the same as the number of functions. Each output is a list produced by applying the corresponding function to each element of the input list.
For example:
bard> (partition [1 2 3]) (1 2 3) bard> (partition odd? [1 2 3]) (true false true) bard> (partition odd? even? [1 2 3]) (true false true) (false true false)
Function
position
(position Anything List) -> (union Integer nothing)
Returns the index of the first element of the list argument equal to the first argument.
Function
position-if
(position-if Applicable List) -> (union Integer nothing)
Returns the index of the first argument of the list for which applying the Applicable argument returns true.
Primitive
range
(range Integer Integer) -> List
Returns a list of integers beginning with the first argument and ending with one less than the second.
Primitive
reduce
(reduce Applicable List) -> Anything
Combines the elements of the List argument using the Applicable argument. The Applicable is applied to the first and second arguments to produce a value; that value is then combined in the same way with the third argument; that process is repeated with the fourth and fifth and so on, until all elements of the List argument have been consumed. The last value produced is returned.
Function
rest
(rest List) -> List
Returns the tail of the List argument—that is, all elements except the first.
Function
reverse
(reverse List) -> List
Returns the elements of the input list in revers order.
Function
second
(second List) -> Anything
Returns the second element of the list.
Function
some?
(some? Applicable List) -> Anything
Returns the first element of the list for which applying the Applicable to the element produces a true value.
Function
take
(take Integer List) -> List
Returns the first N elements of the list, where N equals the Integer argument.
Function
take-by
(take-by Integer Integer List) -> List
Returns a list of lists, each of which contains elements from the input list. Each sublist of the output contains N elements, where N equals the first argument, except possibly the last sublist, which may contain fewer. Each sublist is taken from a tail of the input list obtained by dropping K elements, where K equals the second argument. Thus (take-by 1 1 [0 1 2 3])
yields ((1)(2)(3))
, but (take-by 2 1 [0 1 2 3])
yields ((1 2)(2 3)(3))
Function
take-one
(take-one List) -> Anything
Returns a list containing the first element of the list.
The Mapping
protocol defines functions that
arrange values into tables of key/value pairs, or
mappings.
In Bard, everything is a table; that is, every value except undefined
is an instance of the Table
class. In other words, the Mapping
protocol's functions can be used with every value. Some values are instances of concrete types specifically designed to represent sets of key/value mappings.
Using get-key
or put-key
on these values does exactly what you might expect: it returns or updates the value associated with the supplied key. The same goes for other Mapping
protocol functions: they behave just as we expect them to behave with finite maps such as hash tables or association lists.
Using get-key
or put-key
with a sequence or a stream behaves as if the object is a set of key/value mappings where the values are the elements of the sequence, and the keys are zero-based indexes into the sequence.
Using Mapping
protocol functions with other values behaves a little differently: get-key
and other retrieval functions behave as if the value is an empty table, unless you use the key value:
; the value associated with the key value:
is always the object itself. Using update functions, such as put-key
, creates a new Table
value whose mappings are the supplied key and value, plus the key value:
mapped to the object itself.
Any Bard value except undefined
may be the value associated with a key. Any Bard value except undefined
or nothing
may be a key.
Function
get-key
(get-key Table Anything) -> Anything
Returns the value associated in T with the key K, where T is the Table argument and K is the Anything argument.
Function
keys
(keys Table) -> List
Returns all keys in the Table. If the Table is an unbounded sequence, such as a generator or a stream, then the returned List may also be unbounded.
Function
merge
(merge Table Table) -> Table
Returns a newly-create table whose mappings are those of the first argument combined with those of the second. For each key/value mapping in the second table whose key is equal to a mapping in the first, a single mapping is placed in the output, mapped to the value from the second table; in other words, mappings in the second table override or shadow those in the first.
Function
put-key
(put-key Table Anything Anything) -> Table
Returns a new table whose mappings consist of those from the first argument, plus a new mapping whose key is the second argument and whose value is the third. If the key already exists in the input table then its value is replaced in the output by the third argument.
Primitive
table
(table Anything Anything &) -> Table
Returns a newly-create table value constructed from the input arguments. The first argument is a key; the second is the value associated with that key; the third argument is another key; and so on, converting all arguments to keys and associated values. If the number of arguments is not even, Bard signals an error.
Function
vals
(vals Table) -> List
Returns the values from the table. If the table argument is a List or Stream, then the returned value may be identical to the input.
The Ordering
protocol defines functions used to
sort values into stable orders.
Function
<
(< Orderable Orderable) -> Boolean
Returns true if the first argument is less than the second.
Function
<=
(<= Orderable Orderable) -> Boolean
Returns true if the first argument is less than or equal to the second.
Function
>
(> Orderable Orderable) -> Boolean
Returns true if the first argument is greater than the second.
Function
>=
(>= Orderable Orderable) -> Boolean
Returns true if the first argument is greater than or equal to the second.
The Pairing
protocol defines functions for
pairing one object with another, and for extracting the objects
from such pairs.
Function
left
(left Pair) -> Anything
Returns the left (first) argument of the pair.
Primitive
pair
(pair Anything Anything) -> Pair
Returns a new Pair
instance whose left element is the first argument, and whose right is the second.
Function
right
(right Pair) -> Anything
Returns the right (second) argument of the pair.
The Reading
protocol defines functions for
taking values from streams. That includes, for example, reading
text or binary data from files or from network connections.
Primitive
current-input
(current-input) -> InputStream
Returns the InputStream
value representing the Bard process' current input.
Primitive
load
(load URL) -> Anything &
Opens an InputStream
on the argument and reads Bard expressions from it, evaluating each expression as it is read, until end-of-file is reached.
Primitive
read
(read InputStream) -> Anything &
Reads a Bard value from the input stream.
Primitive
read-file
(read-file URL) -> List
Reads the entire contents of the file named by the argument, returning the data as a List. The returned List may be any of several concrete types, including an array of bytes, a text string, or a list of Bard values.
Primitive
read-line
(read-line InputStream) -> Text
Reads one line of text from the argument.
Primitive
read-lines
(read-lines (union InputStream URL)) -> List
Reads all lines of text from the argument, which may be either an input stream or the name of a file. The produced lines are returned as a list of Text
values.
Primitive
read-text
(read-text (union InputStream URL)) -> Text
Returns the entire contents of the input as a text string. The input may be an input stream or the name of a file.
The Streaming
protocol defines functions for
creating streams—objects similar to sequences, but which
deliver their values one at a time, and that may be of
unbounded length.
Primitive
contents
(contents InputStream) -> List
Returns a generator that yields the contents of the input stream.
Primitive
lines
(lines InputStream) -> Generator
Returns a generator that yields all lines of text in the input stream.
Primitive
put
(put Anything OutputStream) ->
Serializes the first argument and writes it to the output stream.
Primitive
stream-direction
(stream-direction Stream) -> IODirection
Returns 'input
if the stream is an InputStream
, and 'output
if it's an OutputStream
; signals an error otherwise.
Primitive
stream-type
(stream-type Stream) -> IOType
Returns the type of values produced or accepted by the stream.
The System
protocol defines functions for
getting and setting system parameters, for collecting
information about the host system, and for other system-related
tasks.
Primitive
error
(error Anything) ->
Signals an error. Any value may be passed as an argument.
NOTE: an extended error- and condition-handling system will be added in the 0.4.x series of releases. The error-reporting system in 0.3.6 is rudimentary.
Primitive
exit
(exit) ->
Terminates Bard.
Primitive
gc
(gc) ->
Triggers Bard's garbage collector to reclaim unused storage.
Primitive
gensym
(gensym) -> Symbol
Returns a newly-created symbol whose name is unique in the currently-running process.
Primitive
quit
(quit) ->
Terminates Bard.
Primitive
room
(room) ->
Triggers Bard's garbage collector and reports statistics about memory used.
Primitive
uuid
(uuid) -> Symbol
Returns a newly-create symbol that is reasonably likely to be universally unique.
Primitive
version
(version) -> Text
Returns the version string of the Bard interpreter.
The TextProcessing
protocol defines functions
used in processing text.
Function
join-text
(join-text Text List) -> Text
Returns a new Text
value created by joining the elements of the list, using the first argument as a cupola between elements.
Function
split-text
(split-text Character Text) -> List
Splits the Text
argument at each occurrence of the Character argument
, returning the resulting pieces in a list.
The Typing
protocol defines functions used to
discriminate values according to their types.
Primitive
applicable?
(applicable?) -> Boolean
Returns true if the argument is an instance of Applicable
.
Primitive
boolean?
(boolean?) -> Boolean
Returns true if the argument is an instance of Boolean
.
Primitive
char?
(char?) -> Boolean
Returns true if the argument is an instance of Character
.
Primitive
false?
(false?) -> Boolean
Returns true if the argument is logically false. Logically false values include false
and nothing
.
Primitive
float?
(float?) -> Boolean
Returns true if the argument is an instance of Float
.
Primitive
foreign-value?
(foreign-value?) -> Boolean
Returns true if the argument is a foreign value (that is, a value allocated on the C heap).
Primitive
function?
(function?) -> Boolean
Returns true if the argument is an instance of Function
.
Primitive
input-stream?
(input-stream?) -> Boolean
Returns true if the argument is an input stream.
Primitive
integer?
(integer?) -> Boolean
Returns true if the argument is an instance of Integer
.
Primitive
interpreted-method?
(interpreted-method?) -> Boolean
Returns true if the argument is an instance of <interpreted-method>
.
Primitive
iostream?
(iostream?) -> Boolean
Returns true if the argument is an input or output stream.
Primitive
list?
(list?) -> Boolean
Returns true if the argument is an instance of List
.
Primitive
list-protocols
(list-protocols) -> List
Returns a list of the names of all currently-defined protocols.
Primitive
output-stream?
(output-stream?) -> Boolean
Returns true if the argument is an output stream.
Primitive
protocols
(protocols) -> List
Returns a list of all currently-defined protocols.
Primitive
singleton
(singleton Anything) -> Singleton
Returns a Singleton
object that represents the singleton type of the input value.
Primitive
true?
(true? Anything) -> Boolean
Returns true if the argument is logically true. Logically true values include all Bard values that are not logically false, except for undefined
.
The Writing
protocol defines functions for
inserting values into streams. That includes, for example,
writing data to files or network connections.
Primitive
current-output
(current-output) -> OutputStreamyes
Returns the OutputStream
value representing the Bard process' current output.
Primitive
display
(display Anything) ->
Writes a human-readable representation of the input value to the current output.
Primitive
newline
(newline) ->
Primitive
show
(show Anything) -> Text
Writes a new line to the current output.
Primitive
write
(write Anything OutputStream) ->
Writes a Bard-readable representation of the input value to the supplied output stream.