Course overview information is available here.
A large number of programming languages has been developed over time. Here are a few examples:
General purpose programming: Fortran, Cobol, Lisp, Basic, C, Pascal, C++, Java, Prolog, etc.
Scripting: Visual Basic, awk, sed, perl, tcl, sh, csh, bash, REXX, Scheme, etc.
Search: regular expressions, browser queries, SQL, etc.
Display and rendering: PostScript, HTML, XML, VRML, etc.
Hardware: CCS, VHDL.
Theorem proving and mathematics: Mathematica, Maple, Matlab, NuPRL.
By now, you are probably familiar with at least a few of the languages above. Languages evolve over time, in parallel with the development of relevant theories, but also in direct relation to the increased computational and communication performance brought forth by technological advance. New needs lead to old languages being extended (say, by adding object-oriented features to a language that did not have them before), or new languages being created. In fact, if you will stick long enough with the field of Computer Science, you will likely develop your own small languages. The existence of so many programming languages clearly shows that there is no one "best" language.
While there are a few general-purpose languages (like Java, or C/C++), most of them are specialized. If you have a mathematical problem to solve, it is likely that you will prefer Matlab or Mathematica to plain C, for example. SQL is much more suited to solve database problems than Basic. The latter statement is likely to be perceived as trivially obvious by those who know these two languages - and this illustrates how vastly different the features and the domain of use of various languages can be. Choosing the right programming language (or languages) for the right problem or project is a critical step on the path to success.
While there are so many of them, programming languages can be studied and broadly grouped together based on the programming paradigms and models that they implement.
There are three main programming paradigms in use today:
Imperative languages are, probably, the languages that you are most used to; good examples are Java, C/C++, Basic. In an imperative language one must "tell" the computer what to compute and how to compute it. In an imperative programming language one uses variables to store values. As their names indicate, the values stored in variables will change during the computation. We can say that the computation is specified in terms of a program state (think of this, loosely, as the collection of values of all variables at a given moment of time), and the instructions that modify that program state.
Logic programming is illustrated by languages like Prolog. In such a language, one describes the universe of the problem and the conditions that the solution must satisfy to be acceptable. This is done using formulas and notations borrowed from formal logic. The programmer does specify how the solution must be found - the computer will do that automatically. If several - but a finite number - of solutions are possible, the program is able to find all of them.
Functional programming is a programming paradigm in which computations are specified as function evaluations. In the pure functional paradigm, one can name values by associating them with identifiers, but these associations can not be changed - we have no variables! On the other hand, functions are seen as first-order data, one can pass them as arguments, and one can even compute - and evaluate - new functions on the fly. While at the beginning they might seem a little odd, functional programming languages are very powerful, and they often make it possible to solve complicated problems in a few lines of code. In addition, it is typically easier to reason formally about functional programs, when compared to imperative programs. Lisp, Scheme, Miranda, Hope, Haskell are among the better known functional languages.
SML is a modern general purpose functional programming language. SML is a statically-typed, type-safe language.
Every entity in SML has a type. This is not unusual in modern programming languages; for example, Java requires the programmer to declare a type for every variable, function argument, return value, etc.
SML does not require explicit type specifications for all identifiers (e.g. function arguments), but it uses the information implicitly present in the program to unambiguously infer the type of all identifiers before the program is run. At that time the only information available is the text of the program. We say that SML is statically-typed.
Take a look at this - admittedly contrived - example:
fun bad(isString, x):int = if isString
then size(x)
else x
Function bad declares that it returns an integer. Because of its use in the if statement, SML is able to infer that isString must be of type bool. Now if isString is true, then we apply the size operator to x. This operator can be applied only to strings, and returns the length of the string, an integer. If isString is false, then the function returns its second argument, x. Because the function must return an integer, if isString is false, then x must be an integer. Thus, depending on the value of isString argument x could be both a string or an int. No other types are acceptable for x.
At run time, when this function is called, the system will know both the value of x and that of isString. Type checking could be done at that time, dynamically, to catch any illegal values, operations, or results. Obviously, this provides a lot of flexibility. On the other hand, the checks must be performed at every call, since there is no guarantee that the arguments will be of the right type for any of these calls. Some languages do just this. SML, being statically-typed, will reject this function because of the ambiguities, and it will not even attempt to run the program.
Unfortunately, it is not possible to determine in general whether a program contains type errors using only static type-checking. All statically-typed languages deal with this conservatively, by rejecting some programs that are, in fact, correct.
SML is type-safe: once it accepts a program as being correct, it guarantees that no type-wise illegal operations will be performed during the execution of that program (e.g. it guarantees that not two strings will be multiplied). Correctness here refers only to types, it is possible to perform the wrong operations due to logic errors (e.g. you can still add 1 to an integer, rather than subtract 1 from it - but SML guarantees that a program that starts to run will never attempt to add 1 to a string).
SML (and SML/NJ in particular) supports a number of advanced features:
Garbage collection: as in Java, the automatic memory management of SML lifts the burden of having to worry about memory management -- a common source of bugs in languages such as C or C++.
Type inference: you do not have to write type information down everywhere. The compiler automatically figures out most types. This makes the code a bit more terse which can make it easier to read and maintain. (But this is a double-edged sword. Too little type information can make code harder to read.)
Parametric polymorphism: ML lets you write functions and data structures that can be used with any type. This is crucial for being able to re-use code. Java provides a form of subtype polymorphism which also lets you re-use code. We'll learn more about parametric and subtype polymorphism and their relative strengths and weaknesses in class.
Algebraic datatypes: you can build sophisticated data structures in ML very easily, without fussing with pointers and memory management. Pattern matching makes them even more convenient.
Exceptions, threads, and continuations: as in Java, SML/NJ supports exceptions and threads, which are crucial for building real systems. The thread model of SML/NJ is radically different from that of Java, however. In addition, SML/NJ supports continuations, which are an advanced control construct out of which you can build things like loops, exceptions, and threads.
Advanced modules: SML makes it easy to structure large systems through the use of modules. Modules (called structures) are used to encapsulate implementations behind interfaces (called signatures). SML goes well beyond the functionality of most languages with modules by providing functions that manipulate modules (functors), module variables, multiple interfaces per module, and nested modules.
Theorem provers (e.g., NuPRL, HOL, Coq, etc.);
Compilers (e.g., SML/NJ, O'caml, C-kit, Twelf, Lambda-Prolog, Pict, etc.);
Mathematics;
Hardware verification;
Advanced protocols (Ensemble, Fox, PLAN);
Financial systems;
Genealogical database;
Signal processing;
Bioinformatics;
Scripting;
Latex to HTML translation;
Smartcards.
In truth, not a lot when compared to something like C, C++, or Java. ML's real strength lies in language manipulation (i.e., compilers, analyzers, verifiers, provers, etc.) This is not surprising since ML evolved from the domain of theorem proving.