Bard

A programming language

version 0.3.6

copyright 2013 by mikel evins

Introduction

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 version 0.3.6

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.

Changes from version 0.3.5

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.

Bard features

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.

Future features

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.

Running Bard

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.

Example code

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.

Syntax

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.

Values and literals

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:

Boolean

  true
  false

Character

  #\C
  #\space

Float

  2.3

Function

  (function Integer Integer -> Ratio)

Keyword

  next:

Method

  (^ (x y) (* x y))

Null

  nothing
  ()
  []
  ""
  {}

Pair

  '(left . right)

Ratio

  2/3

Text

  "Hello, world!"

Table

  {a: 1 b: 2 c: 3}

Programs

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.

Control structure

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.

Conditionals

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.

Sequencing

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.

Loops

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.

Definitions

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

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.

Types

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.

Actors

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.

Language reference

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.

Constants

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

Schemas

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.

Classes

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.

Special forms

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:

(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:

(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 (# 2 3); 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.

< /div>

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.

Protocols

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.

Addressing

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.

Applying

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.

Calculating

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.

Comparing

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.

Converting

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.

Creating

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.

Generating

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.

Listing

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.

Mapping

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.

Ordering

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.

Pairing

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.

Reading

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.

Streaming

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.

System

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.

TextProcessing

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.

Typing

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.

Writing

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) -> OutputStream
yes

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.