previous latest addition here

An Introduction to Programming Languages

Introduction

Target Audience

This is an attempt to summarize some of the basic ideas behind programming languages. It was originally written for "people who know one language and are wondering about learning another", and hasn't strayed far from that aim.

It is not a detailed, scholarly exploration of all programming languages, nor does it describe the latest developments (or even cover all the basics) in computer science - I simply do not know enough to attempt that. Instead, I have tried to write something that is clear, unbiased, and useful to someone with no knowledge of the theory of computing.

If you're not that interested in actually writing code and are looking for a more general article, focusing on the "philosophical" aspects of programming languages, then you will be much happier with this book review.

Initial Questions

Route Map

I don't try to answer the questions above directly. Instead I look at the various different (and rather arbitrary) families of languages, discuss their main features, and then try to compare them with others.

The examples I give focus on the languages I know best, but I try to cover a wide range of different topics.

Finally, I include some links so that you can continue your own research.

Disclaimer

Be warned: I am no expert. I don't have a formal education in computing, so this is restricted to what I have picked up "along the way". The links at the end give other sources of information. If you spot an error, or have an improvement, please contact me. Thanks.

I'm currently (late 2004) editing and revising this document in an attempt to improve it a little. Please bear with me.

Language Features

Introduction

This section looks at different "features" or "tricks" that languages provide. Presenting them in this way is misleading, because it can give the impression that language design involves selecting which features to implement, and then writing a compiler when, in fact, language development is a complex process, that requires balance, compromise, and is affected by the environment and history in which it is implemented.

But we have to break the subject up somehow, and this seems a reasonable way to start.

Types

Different pieces of information in a program may have different types. For example, a language may treat a string of characters and number in different ways (dividing a string by 3.14 may not be possible, even if the string, when printed, is "200"). A language like this has at least two types - one for strings and one for numbers.

I can think of one language, Tcl, where everything is a string, but most languages have many types.

At first, types seem fairly simple. They are a way of dividing up things into different groups. But it can soon get much more complicated - what if one group is "like" another group in certain ways? What is the type of a function?

Types will reappear throughout this section.

Static and Dynamic Typing

Types can be static or dynamic. Languages like Lisp or Python have many different types, but when you look at a piece of code there is nothing that forces a variables to "be" (or to "point to") a piece of data of a particular type. In other languages, like ML or Eiffel, a certain variable will always be connected with a value of a certain, fixed type. The first group of languages (where the type of everything is unknown when the program is compiled) has dynamic types, the second group has static types.

Dynamic types are good because the program source can be more flexible and compact (which might be particularly useful for prototyping a system, for example). Static types are good because they allow certain errors in programs to be detected earlier (a compiler for a statically typed language may also be able to make extra optimisations using the extra information available, but this depends on details of particular languages and compilers).

My own view is that at computing projects become larger, static typing becomes more important. I would not like to work on a project with many other programmers using a dynamically typed language, and I choose to use dynamically typed languages, usually, when doing projects of my own.

In some languages (e.g. ML) the interpreter or compiler can often work out the type associated with a variable by itself, which saves the programmer a lot of effort.

Strong and Weak Typing

Types can be weak or strong. The languages mentioned above are all strongly typed, which means that at any point in the program, when it is running, the type of a particular chunk of data is known.

Since a dynamically typed language does not have complete type information at compile time it must, if it is strongly typed, keep track of the type of different values as it runs. Typically values are boxed together with information about their type - value and type are then passed around the program together.

It might seem that a strong, statically typed language would not need to do this and so could save some memory (as type information is available when the program is compiled). In practice, however, I believe that they still do so - possibly because of polymorphism (see below).

Unlike the languages mentioned so far, C has weak typing - some variables can point to different types of data, or even random areas of memory, and the program cannot tell what type of object is being referred to. Depending on the context within the program, the variable is assumed to point to some particular type, but it is quite possible - and a common source of confusing bugs - for this assumption to be incorrect (some type checking is done by a C compiler, but not as much as in a language designed to have rigorous compile time checking, like those described as statically typed above).

Java is strongly, but not statically, typed - classes can be converted (cast) and, if the types are not compatible (related through inheritance - see below), a run time error will occur. Apart from this (significant) exception the Java type system can be considered static - one description is "compromised strong static typing".

When strong static typing is enforced (even if only partially, as in Java) it can be difficult to write generic algorithms - functions that can act on a range of different types. Polymorphism allows "any" to be included in the type system. For example, the types of a list of items are unimportant if we only want to know the length of the list, so in ML a function can have a type that indicates that it takes lists of "any" type and returns an integer.

Another solution to the problem of over-restrictive types is to use inheritance from OOP (see below) to group data together. Yet another approach, used in C++, is templates - a way of describing generic routines which are then automatically specialised for particular data types (generic programming and parameterised classes).

Object Oriented Programming

Many language support object oriented programming. In OOP data and functions are grouped together in objects (encapsulation). An object is a particular instance of a class. Each object can contain different data, but all objects belonging to a class have the same functions (called methods). So you could have a program with many email objects, containing different messages, but they would all have the same functionality, fixed by the email class. Objects often restrict access to the data (data hiding).

Classes are a lot like types - the exact relationship between types and classes can be complicated and varies from language to language.

Via inheritance, hierarchies of objects can share and modify particular functions. You may have code in one class that describes the features all emails have (a sender and a date, for example) and then, in a sub-class for email containing pictures, add functions that display images. Often in the program you will refer to an email object as if it was the parent (super-class) because it will not matter whether the email contains a picture, or sound, or just text. This code will not need to be altered when you add another sub-class of email objects, containing (say) electronic cash.

Sometimes you may want an action on a super-class to produce a result that depends on what sub-class it "really is". For example, you may want to display a list of email objects and want each sub-class (text, image, etc) to display in a different colour. In many languages it is possible for the super-class to have functions that sub-classes change to suit their own purposes (polymorphism, implemented by the compiler using a technique called dynamic binding). So each email sub-class may supply an alternative to the default, printing function, with its own colour.

In many OO languages it is possible to find out what class an object is (run time type information) and even what functions are connected with it (introspection / reflection). Others, like C++ have little run time information available (at least in the standard language - individual libraries of objects can support RTTI with their own conventions).

There are at least three approaches to OO languages:

Methods in Classes

Many languages follow Smalltalk in associating functions (methods) with classes. The methods form part of a class definition and the language implementation will have (this is a low-level detail hidden from the programmer) a vtable for each class which links methods to their implementations. This indirection is necessary to allow polymorphism, but introduces a performance penalty. In some languages (C++, at least), only some methods, marked as virtual by the programmer, are treated in this way.

Multi-Methods Separate from Classes

Some languages (e.g. common Lisp / CLOS) allow functions to specialise on the class of any variable that they are passed (multi-methods). Functions cannot be associated with one class because different versions of the function may exist for many different combinations of classes.

Prototypes

Other OO languages do away with classes completely (e.g. Self). Prototype-based languages create new objects using an existing object as an example (prototype). Apart from solving some problems with dynamic object creation, this approach also encourages delegation (function calls are passed to other objects) rather than inheritance.

More Object Oriented Programming

Inheritance may be restricted to a single parent (a car is a type of vehicle, and can use methods defined for vehicles - single inheritance (e.g. Modula 3)) or not (a car is a type of vehicle and a type of polluter, and can use methods defined for both parents - multiple inheritance (e.g. C++)).

Java has a compromise between single and multiple inheritance - it allows multiple inheritance of interfaces (something like class types - classes can be written to implement an interface), but only single inheritance of classes.

When sub-classing objects, it is possible to redefine methods. In most (contravariant) languages the argument to a more specialised functions must be the same, or a more general class. This allows methods on objects to be called whether the object is the declared class or a subclass (e.g. C++, Java, Sather). But in other (covariant) languages (e.g. Eiffel) the argument to the subclass must be more specialised than the argument to the method in the parent. This implies run-time errors or compiler checking, but may be more useful - specialised classes often require specialised arguments.

One approach to software design involves specifying certain conditions that objects must satisfy (design by contract). Conditions might be specified before (pre-conditions), throughout (invariants) or after (post-conditions) invoking a method. Some languages (Eiffel led the way here) provide support for this within the language.

Declarative Languages

Languages can be classified as functional, procedural or logical.

In procedural languages it is common for the same variable to keep changing value as the program runs. These are a common source of errors and are avoided in declarative (functional or logical) languages.

If you are not used to declarative programming, it is difficult to see how a program can work without variables that change value, so the statement above sounds suspiciously like "programs are a common source of errors". Part of the problem is that so many different ideas are covered by the word "variables" and only some of these are eliminated from declarative programs.

First, I am not saying that variables are not used at all - giving a name to a value is very useful in all languages.

Second, I am not saying that a variable as it appears statically in the source code always has one value. The return value of a function can be different each time it is called, for example. Such variables are created each time the function is used and destroyed again when the function ends (the value that they return is not destroyed, but it is not possible to assign a value to the variable inside the function definition from outside).

Returning to declarative languages, we can now say more precisely what they avoid - variables do not change value inside their dynamic scope. This means no global variables that are updated to reflect how the program has progressed, and no loop counters, but still allows named values and different values inside different scopes (e.g. each time a function is called, it can accept different arguments and return different values).

One way of avoiding state variables is by passing functions around. Instead of evaluating a certain function and then passing the result to another part of the program, the function itself is passed for evaluation when required. Functions that can be passed like data are called first-class functions; a related term is higher order functions which describes functions that take functions as arguments, or return functions as results.

For practical reasons many functional languages do allow for some static data (especially for input/output - languages which are strictly functional are termed pure).

Logical languages (Prolog, Mercury) are collections of logical statements and questions about the relationships they describe (although at first sight it may not seem possible to program in this way). I'm afraid I can't say much more about them because I've not used them much.

The advantage of declarative languages, apart from avoiding problems with state variables, is that programs written in them are closer to the program specification. Programming, therefore, is at a higher level than in the imperative languages.

Functional Programming

One facility that enables functional programming is closure - "wrapping up some state". For example, to make a function that add 2 to numbers it is possible to make a (nameless) function that contains the addition function and the value (state) 2 (the nameless function is called a lambda expression) - this can then be used to add 2 to other numbers. Although that example is trivial, it is also possible to encapsulate more complex actions.

When defining a closure you must worry about the scope of variables. If a closure includes a variable, is the value of that variable set when the closure is used, or when it is defined? Most modern languages store the value when the closure is defined (static scope). In contrast, early versions of Lisp, for example, were dynamically scoped - variables took values that depended on where the closure was used.

To avoid having variables that count loop iterations functional languages encourage recursion - where a function calls itself repeatedly with (hopefully) simpler arguments. The most efficient way of doing this (which also avoids running out of memory if the routine recurses many times) is tail recursion - the routine is called again only at the very end of the function, when everything has been calculated, allowing the routine to be restarted without worrying about over-writing variables.

Other features of some functional languages include currying (a function with some arguments already supplied), functions that return several values at once, and pattern matching (to assign values from multiple returns).

Functional languages can be divided into two groups, depending on whether or not they use lazy evaluation. If a language is lazy, then functions are not evaluated until they are actually needed inside an expression, while non-lazy (eager/strict evaluation) languages evaluate functions as soon as they are passed as arguments. Laziness leads to some elegant solutions for problems involving infinite series and seems (to me, at least) more consistent with the idea of functional programming. However, because the order in which functions are used is not very clear to the programmer, the speed and efficiency of programs written in lazy languages can be difficult to understand.

Without permanent state there is less need for objects in functional languages, but types are still important - new types can be defined (constructed types) and are operated on using pattern matching (you might define a complex number type as having two real numbers, pattern matching lets you refer to the numbers separately). Haskell (which is the "standard" functional language in some respects) has a concept of type classes that allows functions to operate on related types.

Flexible Syntax

Some languages allow operator overloading where functions can be defined that correspond to operators (e.g. +, -). This allows operations on different types to be expressed using a natural syntax (addition of matrices is an obvious example).

In languages like Tcl and Lisp, the distinction between language and data is very flexible (dynamic). It is easy for the input to the program to be a program, or a program fragment. In other languages the division is rigid (static). When language and data are similar it is often possible to change the syntax of the language quite easily, while stricter languages only allow extensions in certain pre-defined ways.

Where code and data are strongly separated it may still be possible (in strongly typed languages) for code to enquire about the structure of the program, and even to create further code (Java, Python).

Compilation

A computer's CPU does not process a high level language directly - the program must be translated to machine code. This can be done once, as a distinct step before the program is run (compilation) to produce an executable, or the translation be done dynamically, when the program is run (interpretation). It is also common for a program to be compiled to an intermediate representation (byte code or p-code) which can be interpreted more efficiently. More compilation before the program is run implies less overhead and greater speed later; the compiler can also analyse more of the code and so perform static optimisations. Some interpreters are sophisticated enough to analyse the running program and make dynamic optimisations - these use information not available to a static compiler, but static compilation still gives the best performance in most cases.

Some languages must be compiled, others are interpreted. Many offer both options (it can be useful to develop and test using an interpreter, then compile for maximum performance once the program is working, for example). Some languages produce separate programs while "programming" others means extending the (monolithic) language / interpreter until it does what you require (Smalltalk, Forth). This last approach can be flexible enough to allow programs to be modified with new code while they are still running.

Threads

In a multi-threaded program the same code is "run" more than once at the "same" time. If there is only a single processor then in practice the different threads takes turns to execute.

Having several threads running at once can be useful. For example, a graphical display can remain responsive (using one thread) while the program is doing some calculations (using another thread). But it can also introduce problems. For example, two threads may update data at "the same time". With complex data it is possible for corruption to occur. Mechanisms are needed to prevent this, but the same mechanisms can introduce further problems (in my experience with multi-threaded Java code, thread problems are the main source of run-time errors).

Taking the idea of threads further, it's possible for programs to be distributed - separate parts of the process running on different machines, communicating across the network.

Some languages have no explicit support for threads (C, Common Lisp), or include only basic features (Java). Others have multiple threads as a key idea (Oz, Occam, Erlang - the designer of Erlang argues that concurrency oriented programming is an important concept, for more information see the talk via the previous link.).

Error Handling

When an error occurs in a program, how is it handled? Many languages implement various forms of exception handling - an error forces a function to return (and the function from which it is called to return...) until a special section of code is met that deals with the error.

This can cause subtle errors with programs that do not have automatic memory management (see below) - especially in C++.

Data Structures

Some languages include complex structures (lists, tables, etc) within the core language, others have standard libraries that are separate from the core language but considered part of the standard language implementation. Recently, libraries that support Internet applications have been popular (following Java's success - Rebol is an extreme example). Many languages also include libraries to help implement graphical user interfaces (GUI).

Some libraries are provided as closed, read-only packages. others have source available; often the code can be modified and re-used (for more information check out open source, FSF, gnu).

When the program is stopped, how are data stored? Does the language have persistent storage (some kind of database that keeps information a program is not running)?

In a related vein, how is memory for data structures managed? Memory used to store a particular piece of data must not be recycled if the contents are still being used (but leaving the contents alone when they are not needed can quickly use all the available memory). This may not be a difficult problem in functional languages, but in procedural languages the extent (in time) of a piece of data is not always obvious. Some languages avoid this problem by having the size of all data items known at run-time (Fortran 77), others provide routines that automatically manage the memory (garbage collection - GC), and the rest leave it to the programmer to explicitly request and release memory (C, C++).

Development Environment

What is the development environment like? Is it command-line based or is there a graphical interface? Is there a builder to create your own GUI? If so, how does it structure the interface - is it hierarchical or flat, do you have to modify the generated code? Does the environment support a particular development methodology?

Learning Languages

Why Learn a new Language?

Although there is some evidence that things are changing, it is still fair to say that the selection of languages used in software development is fairly static. So there is little pressure to learn a new language to keep your job.

But I believe learning a new language can make you a better programmer.

Ignoring the fun involved, and the practical advantages of having more than one tool available for the next problem, there are two basic reasons why you might want to learn a new language:

Moving Up

As an example of the first case, let's look at the arguments for learning Java if you program in C or C++.

It is very difficult to write a program in Java without beginning to understand how to use classes and objects - this is in contrast to C++, where you can continue using the same style of programming you used with C. So learning Java will teach you about OOP. Aside from your CV, understanding the principles behind OOP will help you become a better programmer.

It is possible to carry back what you have learnt and write C code in an OO manner (of course, if you have already been doing this, much of OOP seems little more than hype). This usually means using structures which contain pointers to functions that take the structure as an argument. If you have met this style, you'll understand what I mean; if you haven't, you should once you have used an OO language (Python's syntax is particularly useful in laying bare the bones of OOP).

Even if you use C++ as it was intended, you can still learn from Java - a more complete class hierarchy, extensive libraries, introspection, and platform independence are all areas of the language that can change your perspective.

A similar case can be made for learning C++ if you know C, but you must discipline yourself to do things the "OOP way". And of course, if you know only Java you can learn a lot by looking at C (not just understanding, from your errors, why Java is designed as it is, but also learning more about machine level details).

Moving Across

Now the second case, "moving across". Looking at programming from a new angle gives insights that remain useful when you return to your original viewpoint. Different languages express the same patterns (algorithms, approaches, solutions) in different ways: by looking at a pattern with a different emphasis it is possible to understand the pattern more completely. Sometimes a pattern is much clearer in one language than another, but once it has been seen in its simple form the more version can become much clearer. Patterns rely partly on language features and partly on convention - the balance shifts between languages and can illuminate the use of apparently arbitrary conventions.

As a simple example, consider recursion. As a new programmer in C, in my first job, I knew nothing about recursion (except that the concept existed). In my spare time I was playing with ML, where it is normal to write recursive function calls (and difficult to loop in the "usual" - procedural - way). I understood the arguments against recursion in C and understood why recursion (at least tail recursion) was more efficient in other languages. At first I found it difficult to write in ML, but slowly, with practice, I learnt to recognise where recursion naturally worked. Not long afterwards I had a problem at work, writing in C, that was difficult to solve neatly. My first attempt was ugly and contained a bug. But I recognised that it would be much simpler with recursion. The recursive code was clear, as simple as possible, and worked perfectly.

A slightly more complex example might be covariant and contravariant OO languages. When I have the time, I'll be trying out Eiffel because as I wrote this, and tried to understand the difference (see what I wrote here), I realised that it changed a whole pile of assumptions I had unconsciously made about OOP.

As a final example, look at the emphasis in functional languages on moving programs closer to specifications. Learning to use such a language will raise questions about the development process.

How to Learn a New Language

How do you learn a new language? The fastest way is when you are forced to do so (I learnt the only other natural language I know apart from English - Spanish - when i had to live with my Chilean mother-in-law for three months). But if you're lucky enough to be learning by choice, you are probably doing it in your spare time and you won't do that unless you are enjoying yourself - so choose an interesting project.

Choosing what you are going to write in your new language is more important than choosing the language. Choose the language to suit the project or, better, choose both together. For example, if you want to write something that will look good then don't choose a language with no support for graphics.

Learn a little about the language before you start and try and find a solution that will play to the language's new features. If you are using OOP for the first time, for example, try and think how your project can be split into objects (I can remember doing this and wondering how all these objects would actually turn into a working program; where was the mysterious transition into running code? - in retrospect, it just, well, happens...). If you are looking at functional programming, maybe a numerical project would be a good start (I chose cryptography) (this suggestion does not imply that functional languages are only useful for numerical code, just that most textbooks seem to feature numerical examples - in my limited experience - making it easier to start in that direction).

At the same time, be honest with yourself. Don't be too ambitious - don't pick too difficult a project and (maybe) don't pick too exotic a language. The second point is debatable. With any language you will learn something new: it doesn't have to be a huge intellectual leap into the unknown. You are going to be more productive in a language that has some familiar ideas, and you can lean on that part of the language to get going. On the other hand, if you enjoy new ideas, maybe you will be happier with something completely different.

It should be clear now that i don't agree with a poster to comp.lang.Lisp who claimed good programmers can learn a new language in a week. Of course, it is possible to understand the basic concepts - when i tried ML (see the example above) i quickly understood how to write recursive routines, but it took more than a week of practice before i could look at a problem and feel the solution.

Support is also important. If you intend to post questions to Usenet, is there an appropriate newsgroup? And is it tolerant of newbie questions? Personally, i like books - the best impetus for me is finding a good book on computing that uses a particular language in the examples (Abelson and Sussman, or Norvig, are two clear examples).

A note about asking for information on newsgroups: people seem to vary widely in how precisely they talk about languages. At one end of the spectrum there are people who tend to rely on a "subconscious" (or at least "sub-language") intuition and happily mis-use terminology to "get the idea across". At the other end are people who are very precise. Both, no doubt, will give conflicting advice on how to learn and, sometimes, apparently conflicting answers to questions. You have to learn to recognise different styles and read them in the context of the poster.

I am at the "sub-language" / relaxed terminology end of this spectrum, so don't trust everything you read here (I wrote it partly to find out just what I knew, and to make myself learn more of the terminology). From my writing style and the introduction I hope that it is clear that my aim is to convey general ideas, not precise details (I think this is useful, but it probably annoys the hell out of language-lawyer pedants :-).

Finally, don't be afraid to change direction. I've stuck with a few languages much more than with others. Sometimes I have given up in frustration. But even when you only play around a little, you learn something - my argument is not that you must stay with a language a long time to learn anything, but that the learning continues. Stay for a while and you'll learn something. Stay longer and you'll learn more - there's no magic moment when you know everything (which is what makes programming such a rewarding profession).

Do Something Else Instead

Learning a new language isn't the only (or necessarily the best) way to learn about programming languages in general. An alternative approach is to explore the common concepts. The Oz book attempts to do this - it uses a single language to introduce many of the different concepts described above.

Conclusions

I have tried to avoid labelling features as "good" or "bad" unless the evidence is particularly clear (the case against C's weak types, for example - although even this can be useful in the kind of low-level coding for which C is particularly suited).

More important than absolute declarations about particular languages are the lessons we can learn from each. The variety gives us a chance to view old problems from new angles. The change of view is not just refreshing, it also widens our understanding and suggests alternative solutions.

Despite this healthy catholicism, it is still interesting to guess the future. Which good bits will win out? This is a difficult questions when good ideas contradict each other: both static and dynamic typing have their advantages but are, by definition, mutually exclusive. Well, at least when implemented "purely" - some languages allow you to add some static declarations to an otherwise dynamic language (the CMUCL Lisp implementation, for example).

Many languages are heading in the same direction: towards reducing the dangers of persistent state and shifting programs closer to specifications. OOP recognises state and attempts to control how it can be changed, but has little to do with specifications. Functional languages try to banish state completely. Pragmatic combinations exist - many have existed for a long time - but the mainstream future will be decided by economics, management, and inertia.

Performance remains a persistent excuse for inertia. For years people have been arguing that automatic garbage collection is too slow when sophisticated, efficient GC algorithms exist. Perhaps the success of Java (current byte-code interpreters are not fast) will convince people that a "slow" language can be useful.

But things do change. Faster processors move the emphasis away from language speed and raise the importance of programmer efficiency and code maintenance. We are seeing a shift from "does this language feature cost too much CPU time?" to "does using a language without this feature cost too much in development time?".

I program for a living in both Java and C and can write an initial solution for most "unusual" problems slightly more quickly in Java (assuming there is no suitable library solution - there often is for Java). If the problem is similar to other work, the Java solution will be finished much faster as it easier to re-use code, but in either case the C code will contain more bugs, they will be harder to find, and debugging will take much longer. The C code will run more quickly, but is more likely to not run at all, while the Java code will be fast enough. These are not arguments for Java in particular (I am aware of several areas where it "could do better" compared to similar languages), but for modern, expressive, "high level" languages in general.

More Languages

After writing this, people have contacted me pointing out that I have omitted various languages. Sometimes I modify the main text, but often I know so little about the language that I don't feel able to give any extra insight. Instead, I extend this section. For more information on "unlinked" languages in this section, use the general references below.

One language I never mentioned was Pascal. This is a procedural language like C, but with a cleaner syntax. It was popular as a teaching language for a while and was developed by Borland into Delphi, which is used to write commercial applications. These days, I believe Delphi has OO features, as does Modula-3, another Pascal derivative.

Teaching languages have come and gone. Apart from Pascal, I can think of Basic (remember the ZX Spectrum or the old Commodores/Pets?), ABC (which I have never used), Logo (which, if I remember correctly, was a functional language), and Toontalk (where you "program" using cartoon-like images). Python, incidentally, was designed at least partly as a teaching language.

Other languages I skipped because I knew so little about them included Ada, designed for writing large applications, and Rexx, a scripting language used on a range of different OS.

Erlang is interesting because it is a functional language that is used commercially.

Yet another family of languages I missed focuses on array manipulation (I think all - certainly J - have very compact syntax, with many single letter commands). APL ("a programming language") is the first I know of - the tradition has continued with J (used to win a recent ICFP programming contest) and K (less intimidating than J, I am assured). Despite the emphasis on arrays these are general languages (just as Lisp is not only restricted to list processing).

Cecil is a new language that i somehow missed - from what I have read it seems like a very nice modern language, along similar lines to Dylan, Sather etc.

Further Information

In no particular order. These are links I have used (suggestions welcome, but i would like to restrict each language to major implementations, good books, and a page containing further links).

Please email if any links are broken so that I can keep this up-to-date.

Functional Languages

OOP / Functional

OO Languages

[oops - something happened here; i've lost/deleted this section. sorry. eventually i will fix this, but i have many other things to do that are more important at the moment.]

Logical Languages

Scripting Languages

Others

Various

Contact the Author

Please email me any comments (visit my home pages at http://www.acooke.org/index.html for the latest version). I am particularly interested in pointers to new languages and features (I haven't mentioned every language I know of, but the list of features pretty much exhausts my knowledge) and similar links. Corrections for factual errors and suggestions for improvements are always welcome.

This document is copyright 1999-2003 Andrew Cooke. you may copy it, link to it and alter it providing:

Modifications