[ | smalltalk dot org ].
Community and industry meet inventing the future.
Getting The Message: The Essentials of Message-Oriented Programming with Smalltalk
By Alan Lovejoy

Smalltalk: Getting The Message
The Essentials of Message-Oriented Programming with Smalltalk

© Copyright 2007 by Alan L. Lovejoy. Licensing terms at end of document.

About Smalltalk

Smalltalk is a foundational programming language that is based on pervasive message passing, pervasive dynamic and strong typing, pervasive reflection and pervasive object orientation.

Message passing: Almost all computation in Smalltalk happens via the sending of messages. The only way to invoke a method is to send a message—which necessarily involves dynamic binding (by name) of message to method at runtime (and never at compile time.) The internals of an object are not externally accessible, ever—the only way to access or modify an object's internal state is to send it a message. So function and data abstraction are both complete and universal. Pervasive message passing is Smalltalk's most important feature—a point that was lost on most of those who have tried to emulate Smalltalk when designing other programming languages.

Dynamic and strong typing: Although any object can be assigned to any variable, the only way to access or modify the internal state of an object is to send it a message—and the sending of any invalid message is detected and prevented at run time. So, even though Smalltalk's pervasive use of dynamic typing enables the programmer to define highly polymorphic abstractions with an extremely high degree of applicability and reusability, it is impossible to apply a function to a value for which there is no valid, defined behavior.

Reflection: In most programming languages, the specifications of types, classes, functions and subroutines exist only in the source code, and so are not accessible at runtime. But in Smalltalk, all specifications of all program constructs (classes, methods, etc.) are live objects that exist both at compile time and at runtime—and those objects are fully accessible to a running program, and can be queried or modified by sending them messages. So a Smalltalk program can not only fully introspect on itself, it has full power to change itself.

Object-orientation: In Smalltalk, all values are objects—even integers and other numbers, characters, strings, classes and blocks of code. Smalltalk is one of the first object-oriented programming languages. Its design was influenced by Lisp, Logo, Sketchpad, Flex and Simula. Smalltalk was developed as a research project at Xerox PARC in the 1970s by a team whose members included Dr. Alan Kay, Dan Ingalls, Adele Goldberg, Ted Kaehler, [Dianna Merry-Shipiro], Scott Wallace and others.

Warning: Terms such as "object," "class," "type," "method" and hence "object-oriented programming" itself, as used in the context of Smalltalk, do not have the same meanings as they do when used in the context of other programming languages. The term object-oriented programming ("OOP") was coined by Dr. Alan Kay, the inventor of Smalltalk. He intended the term to describe the essential nature of Smalltalk. Unlike Smalltalk, most of the programming languages that market themselves as "object oriented" do not satisfy Dr. Kay's definition of object oriented programming:

"OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things. It can be done in Smalltalk and in LISP. There are possibly other systems in which this is possible, but I'm not aware of them."

The full import of "object-oriented programming" as originally defined by Dr. Kay—and how and why the meaning of "OOP" as it applies to Smalltalk differs from the meaning of "OOP" as commonly understood outside of a Smalltalk context, is fully explained in the sections that follow. In addition, Dr. Kay's article "The Early History of Smalltalk" is highly recommended reading for anyone who wants to gain an even deeper insight into why and how Smalltalk came to be what it is, and why it is so different from the mainstream programming languages.

This is a living document whose contents will be corrected, enhanced, improved and expanded over time. Constructive criticism, suggested rewordings to improve clarity, and requests that additional information be included or that additional topics be covered, are encouraged. Such requests should be sent to the author, Alan L. Lovejoy, at the following e-mail address: smalltalk-tutorial (at) alan-lovejoy (dot) net

Why Smalltalk?

Smalltalk is a worldview, a way of thinking, a completely different programming paradigm. The concepts at the heart of Smalltalk are so profound, and so foundational, that all serious programmers should learn the language[1], even if they don't ever expect to actually use it professionally. Learning C++, Java or C# is not any sort of substitute for learning the real thing. None of those languages satisfy Dr. Kay's definition of OOP.

Smalltalk is considerably simpler than most other languages, both conceptually and syntactically. But Smalltalk is also considerably more powerful and expressive than most other languages, due in part to its greater simplicity, uniformity and symmetry. Smalltalk is comprehensively uniform and consistent, and comprehensively symmetrical—which is an important part of the reason why it can be both simple and powerful at the same time. The flexibility provided by Smalltalk's high degree of symmetry and extreme late binding have proven to help programmer productivity and creativity more than any theoretical benefits that might be derived from the use of static type checking, symmetry-breaking primitive data types or symmetry-breaking syntactic sugar.

Smalltalk is also pervasively dynamic, pervasively reflective, and pervasively open. Its metalevel is completely symmetric with its base level: All the same things can be done at the metalevel as at the base level, using the same syntax.

Stated concisely, the symmetry, the uniformity, the consistency and the simplicity of Smalltalk lie in the following: All values are objects, all behavior is invoked by sending messages, and almost nothing is done by syntax when it can instead be done by sending messages to objects.

But most importantly, Smalltalk is fun. It's fun because its programming tools make use of Smalltalk's superior reflective capabilities to provide a development environment unlike any other. You have to use it to believe it.

Smalltalk is also fun because defining and using domain specific languages isn't an afterthought, it's the only way Smalltalk works at all. As a result, Smalltalk code lets both the reader and the writer focus on the problem domain, using language and notation that is natural to that domain.

And last but not least, Smalltalk is fun because of its exceptional productivity. Unless you are also using Smalltalk, using Lisp (or one of its variants,) or using Erlang (or some reasonably similar language,) it would be wise to avoid challenging a Smalltalk programmer to a coding contest where the winner is the first to finish the assigned task. Unless you choose a language that is optimized for the problem domain of the assigned task, or where there's a library of code available that largely solves the problem, you'll almost certainly lose (e.g., Doing matrix algebra with APL, parsing with Perl, or astronomical calculations using FORTRAN libraries specific to that domain.)

Programmers of the world, throw off your chains. You owe it to yourselves to take a trip to Smalltalk country, where the native optimistic typing paradigm assumes that the programmer is innocent until proven guilty. The mainstream programming languages are too complex, are too static, bind too early, and have too little symmetry. It's time for a "Copernican Revolution," where programmers recognize that the curly-braced languages are based on overly-complex "epicycles," and that there's a much simpler, more effective and less arduous approach.

  1. There are other languages, in addition to Smalltalk, whose ideas are so profound and foundational that all serious programmers should learn them. Some good examples of such languages that most programmers don't already know would be Lisp, Scheme, Haskell and Erlang.

The Smalltalk Computational Model

NOTE: This document mostly limits its description of Smalltalk to ANSI Standard Smalltalk. However, there are many dialects of Smalltalk in the Smalltalk language family (which is similar to the situation with Lisp,) not all of which fully conform to the ANSI standard for Smalltalk, and most of which have features/capabilities that go beyond both the ANSI standard and the description in this document.

Key points

  • In Smalltalk, all values are objects.
  • Almost all computation is done by sending messages to objects.

Messages and Methods

A message can be thought of as a request to invoke or perform a computation. When a message is sent to an object, the receiving object (usually referred to as the "receiver of the message," or simply as "the receiver,") is responsible for deciding how to fulfill the request to invoke (perform) the computation named by the message—or in Smalltalk jargon, how to "respond to the message." An object that receives a message responds to the message by (dynamically, at runtime) finding a method in its method namespace whose name matches that of the message, and then executing that method.

A method is an object that embodies a computational algorithm that, when executed, computes a value—possibly with side effects. To execute a method is to execute the algorithm embodied by the method. Conceptually, a method is analogous to a function (or subroutine), and a message is analogous to a function/subroutine call (a function or subroutine "call" represents a request for it to be executed.) But the analogy between methods and functions (subroutines) is only that—a conceptual analogy—for reasons that are explained below.

The name of a message or method is known as its selector. The selector of a message or method identifies its intentional semantics (logical meaning,) and is what distinguishes one message or method from another. In addition to its selector, a message may have zero or more arguments (parameters.) And a method may accept zero or more arguments—although any particular method requires a particular number of arguments. Each argument in a message becomes an argument to whatever method is executed in response to the message.

A method namespace operates as a mapping (functional predicate) that, for each unique method selector (method name) in its domain, associates that method selector (method name) with a particular method. It is used to lookup a method by its selector (name.)

Dynamic message dispatch is the term used to refer to the process of using a method namespace to (dynamically, at runtime) find the method associated with a particular method selector (name.) It should be noted that Dr. Kay (and Smalltalk) use the term "method" to mean a function or subroutine that is invoked in response to the sending of a message, by means of dynamic message dispatch at run time (and not by static binding at compile time.) This is one of the two differences between a method and a function/subroutine. It's also the essential difference between Smalltalk methods and the functions/subroutines in other programming languages that, in disrepect to those who coined the term, are often referred to as "methods."

Because of dynamic message dispatch, the semantics of sending a message to an object depend absolutely on the object that receives the message. The same message may be interpreted quite differently by different objects, due to the fact that each object may use a different method namespace in order to lookup a method based on the selector of a message, and so may associate a different method with the same message selector. Consequently, a message can be thought of as an abstract function call which will result in the execution of whatever concrete function (subroutine) is chosen by the object that receives the message. The idea is that a message should name the logical function to be computed, but the object receiving the message should decide how best to physically compute the desired result. The universal, pervasive application of this distinction between logical function (semantics) and physical function (implementation strategy) is the central point of object oriented programming, as defined and envisioned by Dr. Kay.

Variables

A variable is a dynamically modifiable association (binding) of either a name or an index to a value. Each distinct variable has exactly one name (or index.) Distinct variables in the same scope must have distinct (different) names (or indices.)

A variable is either a named variable, or it is an indexed variable—one or the other, not both. Named variables associate (bind) a name to a value. Indexed variables (bind) an index (strictly positive integer > 0) to a value. The value associated with ("bound to") a named variable can be accessed by simply using the name of the variable in the code. The value associated with ("bound to") an indexed instance variable can be accessed by sending a message to the object that contains it (see the section on Objects, below.)

Creating a binding of a value to the name of a variable is also called assigning a value to the variable. The same value can be assigned to different variables at the same (or different) times. A variable is initially automatically assigned a special value, known as nil, that is used to indicate that, logically speaking, the variable has no value. An assignment operation (see the section on Smalltalk syntax, below) can be used to assign a value to a variable. A variable can be assigned a new value any number of times.

When assigning a value to a named variable, the variable assignment operation necessarily references the variable's name—but does so with different semantics than applies when the name of a variable is used as a reference to the value of the variable, since the name of a variable to which a value is being assigned refers to the variable itself, and not to the variable's (pre-assignment) value.

Assigning a value to an indexed variable is not done syntactically, but by sending a message to the object that contains it (see the section on Objects, below.)

Objects

An object is a value that can be sent messages, can be assigned as the value of a variable, referenced as the value of a variable, passed as an argument in a message, or returned as the result of sending a message. In Smalltalk, all values are objects—which is not usually the case in other programming languages.

The difference between a value that is an object versus one that is not an object lies in the following:

  1. An object inseperably unifies its value with the behavior that is applicable to its value, and so binds the value it represents to the semantics of that value.
  2. An object fully encapsulates its behavior: The only way to invoke (request the execution of) any of an object's behavior is to send it a message (which results in the execution of one its methods by means of dynamic message dispatch.)
  3. An object fully encapsulates its structure and state, because the only way to navigate an object's structure, or to modify an object's state, is to send it a message.

An object is not a variable, nor is a variable an object. A variable provides a way to reference an object (either by name or by index,) but is distinct from the object it references. Defining a variable does not create an object, nor does creating an object define a variable. However, all objects are either referenced by at least one variable, or by at least one expression that is currently being evaluated, or by at least one thread that has not yet been terminated. Once an object has no remaining references, it will be deallocated by means of automatic garbage collection. The only way to "delete" an object is to remove all references to it.

Mathematically, an object is a node in a directed graph. This also applies to data values in other languages that may be called (for example) "objects," "structures" ("structs," "records,") instances of a data type, or instances of an abstract data type (ADT.) A graph node has zero or more directed arcs. It is a common convention to interpret each directed arc connecting one graph node to another as semantically equivalent to the assertion of a predicate with arity 2, where the directed arc represents the assertion of the predicate, and the two nodes are the arguments of the predicate.

For example, if A and B are nodes in a directed graph, and there is a directed arc from node A to node B whose semantic role is "has-parent," then by convention that situation is understood to be the assertion of the predicate "has-parent(A, B)"—in other words, the assertion that B is the parent of A.

Structurally, an object has zero or more instance variables, each of which may be assigned any value at any time. Of course, since all values are objects, the value of an instance variable is necessarily an object. Mathematically, each instance variable is equivalent to a directed arc, with the object that contains the instance variable and the object that is the value of the instance variable being the two graph nodes connected by the directed arc. It is essentially a universal convention in programming languages that each instance variable (directed arc) is understood to represent the assertion of an arity-2 predicate, where the predicate is identified by the instance variable, and where the two arguments to the predicate are a) the object which contains the instance variable, and b) the value of the instance variable.

For example, if A and B are objects, and object A has an instance variable whose semantic role is "has-parent," then by convention that situation is understood as being semantically equivalent to the assertion of the predicate "has-parent(A, B)"—in other words, the assertion that object B is the parent of object A.

Instance variables are completely private to the objects that contain them. Each instance variable belongs to precisely one object, although different objects may have instance variables with the same name or index. The value of an instance variable can only be accessed or changed by the object to which it belongs. If two objects both have instance variables with the same name or the same index, those instance variables are actually distinct from each other, even though they happen to have the same name or the same index. Within the context of a particular object, each instance variable must either have a unique name or a unique index.

A named instance variable should be given a name that suggests the semantics of the predicate (relation, directed arc) it is intended to model. Operationally, of course, the meaning of the predicate modelled by an instance variable depends only on a) the intentional semantics of the messages that can be sent to the object, and b) the algorithm implemented by the methods that will be executed in response to those messages.

An object with indexed instance variables encapsulates/represents an ordered sequence (ordered collection) of objects as a single value (and hence, as a single object.) For example, an array object encapsulates/represents (as a single value) an ordered sequence of the values (objects) it contains as its elements—and does so by means of indexed instance variables, one for each element in the array. Similarly, a String object encapsulates/represents (as a single value) an ordered sequence of character values—and does so by means of indexed instance variables, one for each character in the String.

An object may have zero or more named instance variables, and/or zero or more indexed instance variables. Indexed instance variables may optionally be restricted so that they can only contain unsigned 8-bit integers, instead of being able to contain any object. Indexed instance variables whose values can only be 8-bit unsigned integers are referred to as byte-valued indexed instance variables. Some dialects also support indexed instance variables whose values are restricted to being 2-byte and/or 4-byte unsigned integer values.

An object can only have one set of indexed instance variables, so it either has no indexed instance variables, or it has indexed instance variables that contain only 8-bit unsigned integers (are "byte-valued,") or it has indexed instance variables that may contain any object. In the case of byte-valued indexed instance variables, the initial value of each byte (8-bit unsigned integer) is zero.

Code Blocks

In addition to methods, there are also objects that represent blocks of code. A code block object is usually just called a "block" or "block closure." A block object represents an anonymous function (or subroutine) which can be invoked by sending a message to the block object. The argument values in the message (which is sent to the block object in order to invoke the function it represents) become the arguments to that function. A block (anonymous function) can have any number of arguments, although each block requires some specific number.

Blocks make it possible to dynamically define and invoke functions that are anonymous (have no name by which to "call" them) because they are objects. The function is referenced dynamically at run time using the identity of the block object, instead of statically by name at compile time.

Code blocks provide Smalltalk with an implementation of the lambda calculus, with full closure semantics.

Blocks are objects, and so can be assigned to any variable, passed as message arguments, and returned as the value of a message send. Blocks are almost always constructed as literal values, using a notation (syntax) unique to Smalltalk.

Blocks are used quite pervasively. Examples: To implement control structures (conventional "if statements" and "loop statements," as well as unconventional ones useful in a particular problem domain,) threads, futures, promises and continuations. They are also used as function-valued parameters to higher-order functions—for example, to apply a function to every member of a collection.

Classes

Every Smalltalk object is an instance of a class. There can be any number of objects which are all instances of the same class. The term instance is often used as a shorthand for the expression "instance of a class." An instance of a class is necessarily an object, and so the term "instance" will sometimes be used as a synonym for the term "object."

The class of an object serves as the single specification of the structure and behavior of the objects that are its instances, and so is a convenient way to specify the common behavior and structure of a set of objects once, so that the behavioral and structural specification of objects need not be repeated whenever two or more objects should have the same behavior and the same structure.

A class defines the structure of its instances because it specifies the set of instance variables they will have. A class defines the behavior of its instances because it specifies and implements the method namespace used by its instances to lookup methods in response to messages.

The methods that a class specifies for use by its instances are known as instance methods. The instance methods specified by a class define which methods will be executed in response to messages sent to its instances—and so define the meaning of those messages when sent to any of its instances. By defining the mapping of messages to methods, a class also defines the operational meaning of the instance variables of its instances.

Note, however, that there are OOP languages that don't use classes. The best known such language is Self, which uses objects and methods, but does not use classes. The fact that an OO language can be implemented without classes is rather inconvenient for those who think classes are central to the definition of OO. The truth is that classes and methods are both simply convenient implementation techniques for that which truly is central to OO: message sending. There is an analogy here with Lisp, where s-expressions are simply a convenient implementation technique for that which is truly central to Lisp: the lambda calculus.

A class is also an object. Like any other object, a class is itself an instance of a class—which is called the metaclass of the class. A class is the sole instance of its metaclass. The metaclass of a class named Foo is always referred to as "Foo class." All metaclasses are instances of the class Metaclass. The class Metaclass is an instance of the metaclass (canonically referred to as) "Metaclass class," which is then (recursively) an instance of Metaclass.

Since a class is an object, and is also an instance of a class (the metaclass of the class,) each class can have its own methods which can be executed by sending messages to the class. In fact, instances of a class are created by sending it messages.

The methods that are used by a class to respond to messages sent to the class itself (not its instances) are known as class methods. Note that the class methods of a class are one and the same as the instance methods defined by its metaclass—and that the "class instance variables" of a class are one and the same as the instance variables defined by its metaclass.

Class Inheritance

One class can inherit from another. A superclass can have any number of subclasses that inherit from it. When a subclass inherits from a superclass, the effect is that a) any and all instance variables that its superclass specifies (or inherits) are added to the set of instance variables the subclass specifies directly (so that each of its instances will have the combined set of intance variables,) and b) any instance methods its superclass defines (or inherits) whose selectors are not the same as those of any of the instance methods that the subclass defines itself, are added to the subclass' method namespace (so that the combined namespace will be used by its instances to lookup methods in response to messages.)

By default, the inheritance relation between a subclass and a superclass is mirrored between the metaclass of the subclass and the metaclass of the superclass, so that if the class Beta inherits from the class Alpha, then Beta class (the metaclass of Beta) also inherits from Alpha class (the metaclass of Alpha.) However, it is not at all required for such to be the case. In fact, for any class that has no superclass (known as a "root class,") the superclass of its metaclass is the class Class (which is a sibling of the class Metaclass, mentioned in the previous section.) Note that this is not only one case where the mirroring of inheritance relationships between classes and their metaclasses does not hold, it is also a case where the superclass of a metaclass is a class, and not a metaclass! Note also that all classes therefore inherit (usually indirectly) from the class Class.

When a class directly defines an instance method itself whose selector is the same as that of some method it would otherwise inherit, the instance method it directly defines is said to override the one defined by its superclass, and the instance method defined by its superclass is said to be overridden. Although a class does not inherit the instance methods it overrides, it is nevertheless possible for any of the instance methods it directly defines to invoke any of the methods it overrides, as though they were instance methods of the class. That can be done by sending a message whose selector is that of the overriden method to a special pseudovariable named "super."

Inheritance Example:

Class Date specifies that its instances have the instance variables "year," "month," and "dayOfMonth." It also specifies a set of instance methods, one of which has the selector (name) "secondsSinceEpoch," which computes the number of seconds between a Date (year, month, dayOfMonth) and the epoch date of some calendrical system (year=1, month=1, dayOfMonth=1.) The number of seconds between the epoch date of a calendrical system and some date (in that same calendrical system) would be computed by using the instance variables year, month and dayOfMonth (of the instance of Date to which the message is sent) to compute the number of days between the epoch date and the date specified by the Date instance, and then converting the number of days so computed into a number of seconds.

Class Timestamp is a subclass of class Date, and so inherits the three instance variables specified by Date, and also inherits any instance methods defined by class Date whose selectors are not identical to any instance methods defined directly by class Timestamp. Timestamp itself defines three additional instance variables: "hour," "minute" and "second." So an instance of class Timestamp has six instance variables: "year," "month," "dayOfMonth," "hour," "minute" and "second."

Class Date defines an instance method named "year" that answers the value of the instance variable named "year" in the instance of Date to which the message "year" is sent. Assuming the class Timestamp does not directly define any method with that same selector, all instances of Timestamp will inherit that method. Consequently, when the message "year" is sent to an instance of Timestamp, the method with selector "year" defined by class Date will execute, returning the value of that Timestamp instance's instance variable "year" as the result of the message send (this behavior is controlled by the way the method "year" is coded, and has nothing to do with the fact that the method selector and instance variable happen to have the same name.)

Class Timestamp also defines a set of additional instance methods, one of which has the selector "secondsSinceEpoch," which is identical to the selector of one of the instance methods defined by class Date. Consequently, Timestamp does not inherit the instance method "secondsSinceEpoch" from its superclass, so when an instance of Timestamp is sent the message "secondsSinceEpoch," the method that executes is the instance method with that selector defined by class Timestamp, and not the one defined by class Date.

Presumably, the implementation of the "secondsSinceEpoch" method as defined by Timestamp not only performs the same computation as does the one defined by class Date (perhaps by sending the message "secondsSinceEpoch" to the pseudovariable "super,") but also computes the number of seconds since midnight represented by the Timestamp instance to which the message is sent (using that instance's instance variables hour, minute and second,) and then adds them to the total number of seconds derived from the count of days since the epoch in order to compute the answer to the message.

The primary purpose, motivation and intent for class inheritance in Smalltalk is to factor out common code into a common class so that it can be defined once, and reused as needed by inheritance. Classes have the same motivation: to factor out common code into a class, so that it does not need to be duplicated among a set of structurally-identical objects. Although it can be used as such (with proper care,) a class is not intended to represent a type as understood and defined by type theory. Nor is a class equivalent to a concept in an ontology. A class may implement behavior in order to model a concept, but at best it is only one possible model or reflection of certain aspects of a concept (there may be others that aren't equivalent.)

Class inheritance in Smalltalk should primarity be based on what you want an object to do, and not necessarily based on what an object is (or represents, or models) in any philosophical sense. There may be very good reasons to use two or more different classes to model (represent) the same concept, in order to optimize the implementation of each class to meet different engineering goals. One might, for example, implement one Point class using cartesian coordinates, and other Point class using polar coordinates, for use in different situations. One might also implement one Vehicle class whose purpose is to draw/animate vehicles on a display screen, and another Vehicle class whose purpose is to model auto insurance coverage. The two "Vehicle" classes could even have the same name, if they reside in different namespaces (although not all Smalltalk dialects support multiple namespaces for classes.)

Variable Name Scoping

When a method executes, it does so in the context of both the object that received the message that resulted in the execution of the method, and also in the context of the class of that object[1]. Firstly, that means that the code of an executing instance method has direct access to any of the instance variables of the message receiver that are either a) defined directly by the class that defines the executing instance method, or b) inherited by the class of the message receiver. Secondly, that means that the code of an executing instance method has direct access to any of the class variables (see below) defined or inherited by the class of the message receiver.

In addition to instance variables, there are also the following types of variables:

  • Local (temporary) variables—accessible only by code within the method or code block wherein they are defined; each invocation of the method or code block (whether by recursion, or by a different thread) uses a different instance of the variable, but uses the same variable name;
  • Formal method arguments—accessible only by code within the method wherein they are defined; each invocation of the method (whether by recursion, or by a different thread) uses a different instance of the formal argument, but uses the same argument name;
  • Formal block arguments—accessible only by code within the code block wherein they are defined; each invocation of the code block (whether by recursion, or by a different thread) uses a different instance of the formal argument, but uses the same argument name;
  • Class instance variables—accessible only by the class itself, although all subclasses each have their own private instances of each variable with the same name; Note that class instance variables are actually just the instance variables specified by the metaclass of a class;
  • Class variables—accessible by all objects that are instances of the class or any of its subclasses, and by the class itself or any of its subclasses;
  • Shared pool variables—accessible by all the classes that import the shared pool, by all subclasses thereof, and by all their instances;
  • Global variables—accessible everywhere;

Global variables have the lowest variable name binding precedence. The definition of any non-global variable will take precedence over ("override") any global variable with the same name. Shared pool variables have lower variable name binding precedence than do class variables, and will be overridden by any class variable defined with the same name. A class variable defined in a subclass has higher variable name binding precedence than one defined with the same name in any superclass. They all have lower variable name binding precedence than any instance variable, local variable or formal argument.

The instance variables defined by a class are in an outer scope with respect to the local variables and formal arguments defined in any instance methods defined by the class. Variables defined in an outer scope have lower name binding precedence than any variable defined with the same name in an inner scope. Although it is legal for a variable in an inner scope to have the same name as any variable in an enclosing (outer) scope, such name conflicts are considered to be a bad programming practice.

Since code block literals can be nested, they define nested variable scopes for local variables, formal method arguments and formal block arguments. Within the method or code block literal in which they are defined, local variables, formal method arguments and formal block arguments take precedence over (i.e., "override") any variable with the same name that is defined:

  • As a global variable;
  • As a shared pool variable;
  • As a class variable;
  • As an instance variable;
  • As a local variable in either the enclosing method or in any any enclosing code block literal;
  • As a formal block argument in the same code block;
  • As a formal method argument of the defining method, or as a formal block argument of any enclosing (outer) code block literal.

Normally, the standard naming conventions prevent any name conflict between local variables, formal arguments or instance variables on the one hand, and class variables, shared pool variables or global variables on the other hand. However, variable naming conventions are not enforced by the compiler, so a naming conflict is theoretically possible (although the author has never seen such, in over 20 years of using the language.)

  1. The fact that an instance method executes in the context of the message receiver (and in the context of the class of the message receiver) is one of the two essential differences between a method and a function (or subroutine.) The other, as explained above, is that a method must be selected for execution by means of dynamic message dispatch in response to the sending of a message.

Variables, Types and Classes

Smalltalk variables have no type constraint syntactically associated with them. Any variable can have any value assigned to it—in other words, the value of a variable can be an object that is an instance of any class (all values are objects.)

It should be noted that a Smalltalk class is not a type, although it implements one (or more.) A Smalltalk type is defined as the power set of messages to which an object can meaningfully respond—a definition that differs non-trivially from that of the term type as commonly used in most other programming languages. Smalltalk essentially uses what has come to be called duck typing. The author likes to call it "optimistic typing."

Different classes can all implement the same Smalltalk ("duck") type—which is a key ingredient to the extreme polymorphic power of Smalltalk. Unlike a class, a Smalltalk type is not an object—although you can define a class whose instances model a type, and can modify Smalltalk at the metalevel so that classes can inform you of their type (or types) using one or more instances of the type-modelling class you've defined.

It is not correct to say that Smalltalk is untyped, or that it is weakly typed. Smalltalk is strongly typed by means of dynamic typing, instead of by means of static typing. Strong typing requires that type errors be detected and prevented—it does not require that type errors must be detected and prevented statically by a compiler, using syntactic analysis of source code.

The class (and hence the type) of a Smalltalk object is encapsulated as part of the value (internal structure) of an object. The type (or types) of an object is an intrinsic part of the value of an object, and is exclusively determined by the class of the object, and not at all by what variable happens to reference it. To make either the class or the type of an object in any way dependent on the variable that references it is a violation of the encapsulation principle, with consequences that are every bit as serious as any other failure to observe good information hiding protocol. In spite of that hard fact, it is a common feature of "OO" languages to actually allow the exact same "message" to have different semantics (behavior,) when sent to the exact same object, depending on the static type constraint of the variable which references the object to which the message is sent. That interpretation of "OO" and "message" statically binds the meaning of a "message" to the variable used to reference an object, and prevents the object itself from having full control over the meaning of the messages it is sent. An object that does not fully control the semantics of the messages it is sent does not fully encapsulate its behavior—hence the violation of encapsulation.

The class (and hence the type) of a value is an essential property of both the input and output data used and produced by a computational process. In other words, the class (and hence the type or types) of objects are just as much a part of the values to be computed dynamically at run time as is any other data involved in a computation. Attempting to statically restrict the set of values that may be involved in a computation, based on syntactic constraints enforced at compile time, not only artificially and unneccesarily reduces the set of valid computations that can be performed, but makes the compiler responsible for a task that should rightfully be done by the programmer.

If the programmer can be trusted to use and produce the right values in the code he writes, then he can be trusted to use and produce values of the right types—since the type of a value is no different than any other aspect of a value. Conversely, if the compiler can rightfully be assigned the task of determining which types are or are not valid, why isn't it also assigned the task of determining the validity of all other aspects of the values to be assigned to variables? Of course, even if the compiler were assigned all such responsibility, the additional constraints that would need to be provided in the code, so that the compiler could enforce those constraints on all aspects of all values, would still have to written by a programmer. There's a reason that imperative code is not written that way: There usually isn't enough information when imperative code is compiled to make such decisions—especially not optimal ones! And that's one reason why mainstream (imperative) languages with static typing tacitly admit the error in their design by permitting programmers to "cast" values from one type to another at run time, thus circumventing the static type system.

Without static type checking, some typing errors will only be detected at runtime, and not at compile time. But catching all type errors at compile time has a cost (relative to relying solely on dynamic typing,) and the cost is not worth the benefit. From an engineering perspective, dynamic typing is good enough. The fact that some academics think otherwise is nothing new.

There are two self-consistent prgramming paradigms: 1) Imperative programming, where program semantics emerge operationally as a result of behavior that is explicitly specified using imperative commands; and 2) declarative programming, where program behavior emerges operationally as a result of semantics that is explicitly specified using the rules of logic. Static type checking is a poor attempt to mix the two paradigms. Instead of trying to compute correct behavior from explicitly specified semantics, it merely tries to prove whether explicitly specified behavior conforms to constraints on the usage of types and function calls—something that it can only do by preventing correct behavior from being specified, simply because it can't prove that the usage constraints won't be violated dynamically. The "impedence mismatch" caused by static type checking, which is a natural consequence of mixing the two basic programming paradigms, imposes costs that are greater than the benefits.

In a system where behavior is explicitly specified using imperative statements, guaranteeing the semantics of a message (function call) is something that no system of static type constraint checking can provide, period. It's the implementation of the methods (functions) that operationally distinguish the semantics of "fire an employee" from that of "fire a gun." A static type checking system can guarantee that the function "fire" is applicable to an Employee or to a Gun, and can prevent the assignment of a Gun object to a variable whose static type constraint says "Employee." But it cannot guarantee what the semantics of the function (or method) that responds to the message "fire" might be—which is why it's essentially useless, from an engineering perspective.

When strong typing is provided by dynamic typing, the presence of static typing causes more damage than benefit. In practice dynamic typing is a better engineering solution. That's a conclusion that many Smalltalk programmers don't believe initially, but come to appreciate with ever greater certainty the longer they use the language. That's why, in spite of how easy it is to define objects that model or represent types, and how easy it would be to modify the Smalltalk compiler to add static type checking, and despite the fact that serious efforts to do one and/or the other of those things have been expended more than once, neither static type checking, nor reifying types as objects, have ever really caught on in the Smalltalk programming community[1].

  1. Note: Squeak Smalltalk has recently added Traits as part of its standard library, which in some ways are analogous to reified types (as defined above)—but the motivation for Traits is not to enable the enforcement of static type constraints at compile time, but rather as a more elegant way to accomplish the same goals that motivate multiple inheritance.

Modules/Packages

Unfortunately, there is a complete lack of standardization for providing or dealing with modules/packages in Smalltalk. Some dialects provide very strong, comprehensive support for modules/packages (including versioning and distributed access by programming teams,) and other dialects provide little or nothing in this regard. Some dialects provide a robust implementation of multiple, shareable namespaces, others don't. The only commonality is that, when either modules/packages or namespaces are provided, they are implemented as reified objects, in the same way that classes and methods are implemented as reified objects.

The Smalltalk Runtime/Development Environment

Smalltalk is a complete system and environment, in which there are only objects that send messages to each other. The objects live in what is called an image, which is a combination of an address space, a process (in the Unix sense) and a persistent object store. When the image is persisted, it's not just the objects that are stored, but the computational process itself. "Saving" or "snapshotting" a Smalltalk "image" is analagous to "hibernating" an operating system, so that when it is restarted, all the same threads are running, all the same windows are open, and the computational process resumes right where it was suspended, with no data lost.

In the case of Smalltalk implementations that descend from the original Smalltalk-80 system developed at Xerox, each Smalltalk image represents a continuation of a process (in the Unix sense) that began to run in 1976, and has been executing (albeit with intermittent pauses) ever since then.

Smalltalk is much more similar to an operating system than it is to a conventional programming language. Just like an operating system, a running Smalltalk image is not any single "program." It is a set of programs, each of which can be started and stopped independently of each other, and without any need to restart the image itself.

In a Smalltalk image, there is no distinction between "run time" and "compile time." For example, while the system ("image") is running:

  • Methods can be added to or removed from existing classes;
  • Existing methods can be modified;
  • Classes can be added to or removed from the system;
  • Classes can be renamed;
  • Classes can have their superclass changed;
  • Classes can have new instance variables added, or have their existing instance variables removed or renamed.

All of the above code changes can be safely performed even when there are instances of the affected classes, even when there are multiple threads, and even when a code debugger is being used to step through a thread. For example, you can modify the code of a method while stepping through its execution using the Debugger.

Conversely, while the programmer is working on the code, the classes and methods that have so far been defined are live objects to which he can send messages. The programmer can select a section of the source code of a method and execute it (or even use the debugger to step through it message-send by message send) from within the Smalltalk text editor. The code is alive.

A Smalltalk class is an object that knows its superclass and its subclasses. It knows its instance varaibles and its methods. A Smalltalk method is an object that knows its class, its selector (method name,) its formal method arguments, its local variables, its external references and its bytecodes. A method also knows its source code—or more precisely, it knows how to store and retrieve its source code from the source code repository associated with the Smalltalk image in which it lives.

The full and complete encapsulation by class and method objects of their entire state—including their source code—is the essential feature that makes the Smalltalk development environment so much more powerful than just about any other. The fact that other systems rely on files, and not on objects that exist at runtime, as the "store of record" for the specifications of the classes and methods they define is a major reason why their development environments cannot match the power of Smalltalk's development environment. Files are static and passive. Objects can be active and dynamic, and can actively and dynamically ensure that invariants are maintained—which is exactly what Smalltalk class and method objects do.

Smalltalk Syntax: By Explanation and Example

Like Lisp, and unlike most programming languages, Smalltalk is defined more by its standard library than by its syntax. All computation in Smalltalk is performed by one of four operations:

  1. Assignment of a value to a variable;
  2. Returning a value from a method (function);
  3. Testing whether two objects have the same identity;
  4. Sending a message to an object.

Other than assignment of values to variables, returning values from methods, and object identity testing, almost all computation in Smalltalk consists of sending messages to objects. There is no syntax for control structures (looping, if-then-else, etc,) no syntax for object instantiation (e.g., "new Object()",) and no syntax for defining classes, modules or packages. Control flow is effected by sending messages to objects. New objects are created by sending messages to objects. Methods, classes, modules and packages are all created by sending messages to objects. In fact, methods, classes, modules and packages are all themselves objects. [How all those things can be done by sending messages, without any special syntax, is demonstrated throughout the following examples.]


The examples that follow are grouped thematically into major and minor sections. Each minor section begins first with a thematic title, followed immediately by a parenthetical comment delimited between square brackets (and in a smaller font) where the number(s) of the production rule(s) that is (are) being demonstrated in the example(s) in that section are listed. For example, the "[Production rule: 13]" at the beginning of the section that deals with pseudo-variables indicates that production rule 13 is being demonstrated and explained in that section. The complete set of numbered production rules that specify the grammar (syntax) of Smalltalk are presented at the end of this document.

Lexical Tokens and Literal Values

Lexical tokens are usually handled by a lexical analyzer, and not by a parser. This section describes all Smalltalk lexical tokens and literal values—most of which are handled by the Smalltalk lexical analyzer.

Whitespace and Code Comments: [Production rules: 7, 8] Generally speaking, whitespace is any sequence of characters that are typographically displayed as blanks. In Smalltalk, whitespace can be any consecutive sequence of one or more space, carriage return, linefeed or horizontal tab characters. It can also be any code comment. Or it can be any combination of both. That is either identical, or very similar, to the syntax of "whitespace" in most programming languages.

Whitespace can optionally be used to separate one lexical token from another. In some cases, whitespace is absolutely required for that purpose. If you bear in mind that the production rules handled by the lexical analyzer take precedence over those handled by the parser, it should be evident when it is necessary to use whitespace to separate lexical tokens from each other in order to prevent syntactic ambiguity.

As is generally the case in any programming language, a code comment is an annotation to the code of a method or block literal. Comments in the code are usually used to document or explain the intent or purpose of the code, or explain why the code is written in a particular way. Other than its function as a token separator, a code comment has no effect on the meaning of the code itself. A code comment is delimited between two double quote characters, as in the following example:

sum := 3 + 4. "This is a comment."
product := "This is another comment" 3 * 4.

Many other programming languages use the double quote character to delimit String literals. Smalltalk doesn't. Smalltalk is not the only language that uses the double-quote character to delmit comments. English does the same—as do many natural languages.

Identifiers: [Production rule: 10] Semantically, an identifier serves the same function as it does in any other programming language: It's a name, or part of a name. In Smalltalk, it defines all or part of the syntax of variables, formal arguments, messages and Symbol literals (see below.)

The syntax of a Smalltalk identifier is quite similar to that used by most other programming languages: An idenfitier must begin with a character that is either a letter (A-Z | a-z) or the underscore character ("_"); after the first character, there may optionally be any number of characters, each of which must be one of a) a letter (A-Z | a-z),   b) a digit (0-9),   or c) the underscore character ("_").

Examples:

x"An identifier may be a single letter"
door3"Identifiers with multiple characters can have digits in any position other than the first"
the_end"An underscore character may occur in any position"
_privateName"An identifier may begin with an uderscore"

By very strong Smalltalk convention, camel casing is used instead of underscore characters in order to separate one word from another in an identifier that happens to correspond to a compound word or phrase in English. The underscore character is mostly used when an identifier needs to match a name used by an external variable, data type or function[1]. The underscore may also be (rarely) used in a message/method selector to mark it as private to the system.

  1. The underscore character was not originally legal as part of an identifier. The syntax of identifiers was changed by the ANSI Standard for Smalltalk (1998) to also allow the underscore character—partly in order to make it easier for Smalltalk to interoperate with external systems and languages—many of which, by convention, prefer to use underscores instead of camel-casing.

Constants: [Production rule: 12] A constant is a named reference to a value, such that the binding of the name to its value is immutable There are three pre-defined constants in Smalltalk: nil, false and true. The constant nil references the sole instance of the class UndefinedObject, and is used as a metavalue that signifies either "there is no value" or "there is no value defined." The constant false references the sole instance of the class False, and represents the Boolean truth value false. The constant true references the sole instance of the class True, and represents the Boolean truth value true.

NOTE: Due to the fact that nil, false and true are all syntactically-valid identifiers, they have the effect of pre-empting the definition by programmers of any identifiers having the same names as any of nil, false or true. They are reserved identifiers that have been predefined by the system so that their binding to their value is immutable.

Pseudo Variables: [Production rule: 13] A pseudo-variable is a system-defined variable whose value can only be assigned by the Smalltalk runtime environment itself, and not by the programmer. There are two ANSI-Smalltalk pseudo-variables: self and super. Many dialects of Smalltalk, including the two most widely used (VisualWorks and Squeak,) also define the pseudo-variable thisContext (as defined by the original Smalltalk-80 implementation of Smalltalk—which served as the de facto standard definition of Smalltalk before ANSI produced an official standard.)

The pseudo-variables self and super both always refer to the object that is the receiver of the message whose sending initiated the execution of any method that references either pseudo-variable. When a message is sent to self, dynamic message dispatch works as it does when a message is sent to any other object. But when a message is sent to super, dynamic message dispatch will not result in the execution of any method that is directly defined as an instance method by the class of the object receiving the message. So sending a message to super can only result in the execution of an instance method defined by some superclass of the class of the object that receives the message. Messages are usually sent to super in order to execute an instance method defined in a superclass that has been overriden (in the class of the receiver) by another method with the same selector.

If defined, the pseudo-variable thisContext references an object that either is or represents the activation frame on the call stack of the current thread's execution context for the method in which thisContext is referenced.

NOTE: Due to the fact that self, super and thisContext are all syntactically-valid identifiers, they have the effect of pre-empting the definition by programmers of any identifiers having the same names as any of self, super and thisContext. They are reserved identifiers that are defined and (re)initialized by the system as required. Their values cannot be (re)assigned by the programmer.

Keywords: [Production rule: 17] A keyword is a Smalltalk syntactical form that generally has no analog in other programming languages. Smalltalk code uses keywords quite pervasively—and gets substantial benefits from their use, as will be made clear in subsequent sections. The fact that there are no syntactic analogs of keywords in most other programming languages is by far the most important factor that makes Smalltalk code seem so syntactically impenetrable to most programmers. To solve that problem, read on.

Syntactically, a keyword is an identifier followed by a colon character (":").

Examples:

x:"A keyword may be a single letter followed by a colon"
door3:"Keywords with multiple characters can have digits in any position other than the first"
the_end:"An underscore character may occur in any position"
_privateMessage:"A keyword may begin with an uderscore"

A keyword forms all or part of a keyword selector. A keyword selector is a syntacical construct for representing message and method selectors (names) that need to be combined with one or more arguments (apart from the receiver of the message)—as is more fully discussed in the section (below) dealing with keyword message selectors. Keywords are also used as part of the syntax of Symbol literals (which are explained below.)

Binary Selector Characters: [Production rule: 19] A binary selector character is a punctuation character that can be used to compose message selectors that correspond to the tokens commonly used in programming languages for representing mathematical (and other) operators. Examples of such "operator tokens" include +, -, *, / and <=. Most, but not all, punctutation characters are binary selector characters. Here's the complete list of Smalltalk's binary selector characters:

~ ! @ % & * - + = | \ < > , ? /"A whitespace-separated list of characters"

There are also punctuation characters that cannot be used to compose message selectors:

( ) [ ] { } ` ' " . # ^ $"A whitespace-separated list of characters"

Integer Literals: [Production rule: 21] An integer literal specifies a value (and hence, an object) that represents an integer (a whole number that may be positive or negative.) Decimal integer literals are specified as they would be in most other programming languages—except that there are no length constraints. You can specify an integer literal with as many digits as you might ever need. Nor is there is ever any need to specify whether an integer literal is short, long or any other length. The syntactical representation is actually very similar to that of math or natural language—except you can't use a comma (as you might in North America) or a period (as you might in Europe) to separate thousands, millions, billions and so on.

Since class Integer inherits from class Number, integer values are all polymorphically type-compatible with general Smalltalk Number protocol.

Examples (decimal notation):

52"The integer fifty two"
371993326789901217467999448150835200000000"The factorial of 36"
-451"Negative integer literals must be (immediately) preceded by a minus sign"

Integer literals can also be specified in any base from 2 to 36. The format is to first specify the base (as a base 10 decimal literal,) then the letter "r" (lowercase,) followed by the digits of the number according to the specified base.

Examples (radix notation):

2r101"The number 5 in base 2 (1 * 2 squared + 1 = 5)"
3r200"The number 18 in base 3 (2 * 3 squared = 18)"
8r70"The number 56 in base 8 (7 * 8**1 = 56)"
16rFF"The number 255 in base 16 (15 * 16**1 + 15 = 255)"

For non-decimal digits greater than 9, use the alphabetic characters A-Z, where A has the decimal value 10, B has the decimal value 11, and Z has the decimal value 35. The lower case letters a-z may also be used, with the same decimal values as the corresponding upper case characters.

Scaled Decimal Literals: [Production rule: 26] A scaled decimal is a value (and hence, an object) that provides a precise representation of decimal fractions with some minimum number of fractional digits. A scaled decimal would typically be used to represent monetary/financial values, which need to have unlimited precision to the left of the decimal point, and need some minimum guaranteed precision to the right of the decimal point. Typically, implementations in Smalltalk actually provide unlimited precision on the right side of the decimal point, and not just on the left side—although the ANSI Standard does not require that. The minimum precision (whether implied, or explicitly specified) is used to limit the number of digits to the right of the decimal point that will appear when a scaled decimal value is converted to a textual representation.

Since class ScaledDecimal inherits from class Number, scaled decinal values are all polymorphically type-compatible with general Smalltalk Number protocol.

Syntactically, a scaled decimal is an optionally-negative decimal integer literal, optionally followed by a decimal point and another decimal integer literal, with all the preceding followed (non-optionally) by the letter "s" (lower case,) optionally followed by another decimal integer literal. The initial decimal integer literal represents the non-fractional part of the number. If present, the decimal point and following decimal integer literal represent the fractional part of the value. The lower-case "s" must immediately follow; its presence is essential in order to syntactically mark the literal as a scaled decimal value. The decimal integer literal that may optionally follow the "s" specifies the minimum decimal precision of the scaled decimal value (this is also known as the scale of the number—hence the "s".) If the minimum precision (scale) is not specified, it defaults to the number of decimal digits present in the literal (to the right of the decimal point, before the "s.")

Examples:

1s"A scaled decimal with the numeric value 1, with a scale (minimum precision) that defaults to 0 decimal places"
99.99s2"A scaled decimal for 99 and 99/100ths, with a scale (minimum presion) explicitly specied as 2 decimal places"
-654.052s"A scaled decimal for -654 and 52/1000ths, with a scale (minimum precision) that defaults to 3 decimal places"

Floating Point Literals: [Production rule: 27] A floating point literal is a value (and hence, an object) that approximates a real number. The syntax is either identical, or very similar, to that of most other programming languages. The ANSI Standard for Smalltalk provides syntax for specifying floating point literals with any one of three different precisions (single-precision, double-precision and quad-precision,) but does not require that a conforming Smalltalk implementation actually provide more than one precision (although the syntax for literals of all three precisions must be supported, with any literals for unsupported precisions auto-converted to one of the supported precisions.)

Since the various floating-point number classes all inherit from class Number, all floating point values are all polymorphically type-compatible with general Smalltalk Number protocol.

Syntactically, a floating point literal is an optionally-negative decimal integer literal (representing the non-fractional part of the number,) (non-optionally) followed either by a) a decimal point and another decimal integer literal (representing the fractional part of the value, a.k.a. the mantissa,) which is then optionally followed by an exponent specification, or b) an exponent specification. An exponent specification starts with one of three lower-case lettters: "e" (single-precision,) "d" (double-precision) or "q" (quad-precision,) optionally followed by an optionally negative decimal integer literal (specifying the value of the exponent—which specifies the power of ten by which the number to the left of the exponent specification is multiplied to obtain the value of the literal.) If no exponent specification is provided, then the precision defaults to single-precision, and the exponent defaults to zero. If no exponent is specified, then the exponent defaults to 0.

Examples:

3.14"A floating point literal which, by default, is single-precision"
3.14q"A floating point literal explicitly-specified to be quad-precision"
1e100"A single-precision floating point value that represents 1 * 10**100"
-3.141592654d"A floating point literal explicitly-specified to be double-precision"
31415.92654q-4"A quad-precision floating-point value that represents 31415.92654 * 10**-4 (i.e., 3.141592654)]"

Character Literals: [Production rule: 29] A character literal specifies a character value. Like all other values in Smalltalk, a character value is an object. Syntactically, a character literal begins with the dollar-sign character ("$"), followed immediately by the character itself.

Examples:

$x"The character x (lower case)"
$#"The `pound-sign` or `hash` character (`#`)"
$ "A space character (a blank)"
$$"The dollar-sign character (`$`)"

String Literals: [Production rule: 30] A string literal specifies a value that is an instance of the class String (or perhaps an instance of a dialect-specific subclass of String.) An instance of String (or of a subclass of String) represents a sequence of characters as a single value (and hence, as a single object.) There may be any number of String instances which all contain the same sequence of characters. Generally, the characters contained in a String instance can be changed dynamically—although some dialects make string literals immutable (the ability to make specific objects immutable—which is not the same thing as the ability to make all instances of a class immutable—is not supported by all dialects.)

Since class String inherits (indirectly) from class Collection, String instances are all polymorphically type-compatible with general Smalltalk collection protocol.

Syntactically, string literals are delimited between single-quotes ('). Many other languages use double quotes (") for the same purpose. Smalltalk doesn't—it uses the double-quote to delimit comments. To embed a single-quote within a string literal, use two single-quotes right next to each other.

Examples:

'x'"A literal for a string containing the single character $x"
'This is a string literal'"A self-describing string literal"
'Smalltalk doesn''t use the double-quote to delimit a string literal'"To embed a single-quote within a string literal, use two single-quotes right next to each other"
''"A string literal for an empty string value (object)"

Symbol Literals: [Production rule: 33] A symbol literal specifies a value that is an instance of the class Symbol (or perhaps an instance of a dialect-specific subclass of Symbol.) Conceptually, a Symbol is an immutable String (sequence of characters encapsulated as a single value.) There is only one Symbol instance for each distinct sequence of characters.

Symbols are frequently used to represent reified variable names, class names and message/method selectors—and that use is supported at the deepest levels of the system.

Since class Symbol inherits (indirectly) from class Collection, Symbol instances are all polymorphically type-compatible with general Smalltalk collection protocol.

Syntactically, a symbol literal starts with[1] a pound (hash) sign character (#), followed by one of four different syntactic patterns:

  1. An identifier ("identifier symbol syntax");
  2. A sequence of one or more keywords ("keyword symbol syntax");
  3. A sequence with any one, or with any two, characters from the set of binary selector characters ("binary selector syntax");
  4. A String literal. ("string literal symbol syntax")

Examples:

#x"A Symbol literal containing the single character $x (using identifier syntax)"
#at:put:"A Symbol literal that uses keyword syntax"
#value"A Symbol literal encapsulating the sequence of characters $v, $a, $l, $u and $e (using identifier syntax)"
#<="A Symbol literal that uses binary selector syntax"
#'This is a Symbol'"A Symbol literal that uses String literal syntax"

  1. When a symbol literal is specified as part of an array literal (see below,) and the symbol literal doesn't use String literal symbol syntax, then the leading pound sign (#) may optionally be omitted.

Array Literals: [Production rule: 34] An array literal specifies a sequence of values, encapsulated by an object that is an instance either of the class Array or of the class ByteArray—depending on the specific syntax used. The elements contained in an Array may each be of a different class (or type.) In contrast, the element values contained in a ByteArray must all be unsigned integers in the range 0-255.

Since class Array and class ByteArray both inherit (indirectly) from class Collection, instances of both classes are all polymorphically type-compatible with general Smalltalk collection protocol.

The syntax of ByteArray literals and Array literals is very similar, differing only in a) the tokens used to delimit (enclose) the elements of the array, and b) the literals that may legally be listed as elements of the array literal. In either case, the first token of an array literal must be the pound-sign character ($#). Immediately following the pound-sign character ($#) must occur the left-side array delimiter, followed by a list of literal values separated from each other by whitespace, followed by the right-side array delimiter. General object-valued array literals use the left paren, $(, as the left-side delimiter, and the right paren, $), as the right-side delimiter. Byte-valued array literals use the left square bracket, $[, as the left-side delimiter, and the right square bracket, $], as the right-side delimiter.

As mentioned above, ByteArray literals can only contain unsigned integer literal in the range from 0 to 255. General object-valued array literals may contain any literal other than a code block literal (see below.)

In the case of Symbol literals and array literals that are contained in an enclosing array literal, the leading pound-sign token (#), that is otherwise required, may optionally be omitted by the inner Symbol literal or inner array literal. Omitting the pound sign token usually doesn't change the value of the literal value in any way—except for any of three special Symbol literals: If the leading pound-sign is omitted from any of the Symbol literals in the set {#nil, #true, #false}, then those specific literals will be interpreted by the lexical analyzer as one of the pre-defined constants, and not as Symbol literals at all.

Examples:

#[2 4 8 16 32]"A ByteArray literal whose elements are the unsigned byte values 2, 4, 8, 16 and 32"
#(22 'hello' 3.14 nil #value $x)"An array literal containing 6 elements, each of a different type"
#()"An empty Array (it has no elements)"
#('hello' world)"An array literal whose first element is the String 'hello'
and whose second element is the Symbol #world"
#(('hello' 'world') #(hello world))"An array literal whose two elements are themselves array literals"
#(#('hello' 'world') (hello world))"Like the above—but swapping which inner array literal does or doesn't omit the #-token"
#(true false)"An Array whose two elements are the Boolean values true and false"
#(#true #false)"An Array whose two elements are the Symbols #true and #false"
#(#nil #value)"An Array whose two elements are the Symbols #nil and #value"
#(nil value)"An Array whose two elements are the metavalue nil and the Symbol #value"

Array/ByteArray instances are the canonical mechanism in Smalltalk for representing the very same array abstraction found in most other programming languages. But unlike the case in most other programming languages, and of course with the exception of the syntax for array literals, arrays in Smalltalk are implemented as part of the class library—and even were they not, they could be implemented by programmers themselves, without any special-case magic from the compiler or run-time system. Programmers can define their own implementations of array-like abstractions, should they so desire.

Code Block Literals: [Production rule: 41] A code block is a value (and hence, an object) that represents an anonymous function (or subroutine) which can be invoked by sending a message to the code block. A code block literal is usually referred to as simply a "block," instead of either of the more formally-correct terms block literal or code block literal. Since all values in Smalltalk are objects, and all objects are first-class values, a block can be assigned to a variable, passed in as an argument to a method, or returned as the result of an expression. Blocks can be sent the same messages, and respond to those message in the same way, regardless of whether they are referenced as literals, via named variables, or as an intermediate result value in an expression evaluation.

A block literal defines a function that has no name. Just like a function definition, a block literal does not, by itself, result in the execution of the algorithm it defines. Blocks only execute the function they embody when they are sent certain messages; not all messages sent to a block will cause the block to perform the computation it specifies.

Although Smalltalk's block literal syntax is at least as simple as the syntax for anonymous functions in other languages (and often simpler,) it is also quite different than that used by other languages. And block literals appear pervasively in Smalltalk code—which is one reason that programmers unfamiliar with Smalltalk syntax typically find Smalltalk code hard to decipher (although Smalltalk's keyword-based message syntax is more of a problem in that regard.) Another issue is that not all programming languages have any support for anonymous functions as first-class values (or at all)—although there are some widely-used languages that do: Lisp has full and complete support for anonymous function literals and the lambda calculus. Python and Ruby provide them. Java has anonymous classes, which are partially analogous—but the syntax is both complicated, and very different than that of Smalltalk. C# supports anonymous function literals with what it calls "delegates" (although the syntax is rather awkward.)

Syntactically, the specification of a block literal can be as simple as enclosing code between the two block literal delimiters, which are the single characters $[ and $] (expressed as Smalltalk character literals.) The character $[ (left square bracket) is the left-side block delimiter, and the character $] (right square bracket) is the right-side block delimiter. The following are examples of a few zero-argument block literals:

[3 + 4] A block literal defining an anonymous function whose algorithm computes the sum of 3 and 4.
[1 * 2 * 3 * 4 * 5] A block literal defining an anonymous function whose algorithm computes the factorial of 5.
[] A block literal defining an anonymous function whose algorithm computes nothing at all.

In the examples in this section, code that forms the body of block literals is presented without having first demonstrated or explained the grammatical production rules that govern Smalltalk expressions or statements. However, the examples in this section use Smalltalk syntax for the body of a block literal which is not only identical to that used in most programming languages, but is either identical, or very similar, to the notational conventions of everyday arithmetic. It should be noted, however, that the syntax for the code that forms the body of a block literal is the same as that which forms the body of a method—the expression syntax is the same, the statement syntax is the same, and the syntax for declaring temporary (local) variables is the same. What differs between the syntax of block literals and methods is the way formal arguments are declared, and the fact that, unlike block literals, methods don't use either a beginning or ending delimiter token (how that can be will also be explained later.)

Since a block is a function, it computes and returns a value as its result. Normally, the result returned from the evaluation of a block is the value of the last expression or statement in the body of the block. One exception to that is a block that has neither any code nor any arguments: An "empty," no-argument blocks evaluates to nil. An "empty" block that has arguments evaluates to the actual value passed in as the last argument.

The syntax for sending messages to zero-argument blocks so that they will execute the function they specify will be shown later.

Blocks can be defined to have any number of arguments. The declaration of a formal block argument begins with the character $: (a colon,) followed immediately by an identifier. The set of formal block argument declarations for a block are specified as a whitespace-separated list at the beginning of the block (after the left-side block delimiter.) The list of formal block argument declarations is terminated by a $| (bar) character—which is required, if there are any formal block argument declarations, but is not permitted otherwise.

The syntax for referencing the value of a formal block argument is simply to use the name of the same identifier used in its declaration—without the leading $: (colon) character. The $: (colon) at the beginning of each formal block argument declaration can be thought of as a token meaning "declare a formal block argument bound to the following identifier."

Examples:

[:x | x * x] A one-argument block, whose formal argument is x, and which computes the square of x
[:a :b | a < b] A two-argument block, whose formal arguments are a and b, and which answers whether a is less than b
[:x :m :b | m * x + b] A three-argument block, whose formal arguments are x, m and b, and which computes the y-coordinate of a point on a line whose x-coordinate is x, for a line whose slope is m and whose y-intercept is b.

Formal block arguments are not variablesassignment operations cannot be used to reassign (rebind) the value of formal block arguments.

For each of the three block literals from the preceding example, we show below equivalent notation, using the standard lambda calculus, and also using a notation (invented just for this example) that will hopfully resemble a syntax which is more familiar to a wider audience:

[:x | x * x] λx. (x * x)function f(x) is {return x * x;}
[:a :b | a < b] λ a b. (a < b) function g(a, b) is {return a < b;}
[:x :m :b | m * x + b] λ x m b. (m * x + b) function h(x, m, b) is {return m * x + b;}

The following three examples demonstrate the effect of sending invocation messages with message arguments to three different blocks—one example for the one-argument case, one example for the two-argument case, and one example for the three-argument case. Not shown is the actual syntax for sending invocation messages to blocks, which will be covered later:

Block Literal Invocation Message Argument(s) Result of Invocation
[:x | x * x] 6  =>   6 * 6   => 36;
When the block invocation message is sent to the block with the message argument 6, it responds by evaluating its function with the value of the formal argment (named x) set to the same value as the message argument (which is 6,) so that it evaluates the formula 6 * 6, and then answers the result of that computation (which is 36) as the result of the message send.
[:a :b | a < b] 3, 4  =>   3 < 4   =>   true;
When the block invocation message is sent to the block with the message arguments 3 and 4, it responds by evaluating its function with the value of the formal argments (named a and b) set to the same values as the message arguments (3 and 4, respectively,) so that it evaluates the expression 3 < 4, and then answers the result of that computation (which is true) as the result of the message send.
[:x :m :b | m * x + b] 33, 1.5, -12  =>   (1.5 * 33 + -12)   =>   37.5;
When the block invocation message is sent to the block with the message arguments 33, 1.5 and -12, it responds by evaluating its function with the value of the formal argments (named x, m and b) set to the same values as the message arguments (33, 1.5 and -12, respectively,) so that it evaluates the expression 1.5 * 33 + -12, and then answers the result of that computation (which is 37.5) as the result of the message send.

Executable Code

Examples of executable code have already been shown in the previous section, wherein the definition and usage of code block literal (usually referred to simply as "blocks") was demonstrated and explained. Block literals are used pervasively in Smalltalk code, so understanding the syntax and semantics of blocks is an important part of understanding code written in the language. However, those previous examples were limited to the minimum necessary usage of executable code in order to demonstrate and explain the syntax and semantics of block literals, and relied on the fact that Smalltalk's syntax for relatively simple arithmetic expressions is either identical, or very similar, to that of arithmetic notation, and/or that of most other programming languages.

What has not yet been fully demonstrated and discussed is the syntax for sending messages. Since almost all computation in Smalltalk is done by sending messages, the syntax used to do that is central to the syntax of executable Smalltalk code. The next four sections fully demonstrate and explain Smalltalk message sending syntax.

And now a word of "warning" to those readers who don't already know Smalltalk (I expect that there will be those who are already proficient at Smalltalk who will also read this document): If you read much further, you will be "taking the red pill" or "jumping down the rabbit hole" (whichever metaphor you prefer.) Just like learning how to see a stereogram, once you learn how to "read the code," there's no going back.

Messages: [Production rule: 53] A message is a request to perform a computation, and answer the result of that computation. To that extent, it is analogous to a function call. Like a function call, a message send a) may have arguments, and b) always has a return value. But a message send differs from a function call in that a) a message, when sent to one object, may result in the execution of a different method (function) than when that same message is sent to some other object, and b) the method (function) that actually executes in response to a message does so in the context of the object to which the message was sent—which means that the method (function) has access to the receiver and its internal structure (state.) Also, since the execution of a method may have side-effects, a message send may not striclty satisfy the semantics of a function call, as that term is used in mathematics.

A message send has three components:

  1. The object to which the message is sent—also known as the message receiver;
  2. The selector of the message—which is the unique identifier ("name") of the message;
  3. The arguments of the message—if any.

Although the semantics of sending a message is more fully discussed above, in the section that discusses the Smalltalk computational model, there is one point about the semantics of sending messages in Smalltalk that belongs here, because it concerns itself with the relation between the syntax and the semantics of sending messages: Conceptually, although a message receiver is not an argument of the message it is sent, it is an argument of the implied higher-order function responsible for the dynamic message dispatch operation that finds and executes a method as a function of both the message receiver and the message selector. In other words, sending a message to a receiver is conceputally equivalent to invoking a sendMessage function, with the message receiver, the message selector and the message arguments as its input parameters:

sendMessage(messageReceiver, messageSelector, messageArguments).

The sendMessage() function call—which performs dynamic message dispatch—is implied (and not explicitly specified) by the syntax of an object-oriented programming language. This is analogous to the syntax/semantics of function calls in most modern programming languages, which—unlike some early programming languages—do not use a "CALL" keyword or operator in order to invoke a function (although the "call subroutine" instruction does need to explicitly exist at the level of machine code.) In modern programming language syntax, the practice is to call a function by just referencing its name and listing its arguments. Similarly, the syntax of a Smalltalk message send specifies only the receiver, the message selector and the message arguments (if any.) The fact that the operation is a message send, and not a function call, is fully implied by the fact that Smalltalk methods can only be executed by sending a message—and also by the fact that almost everything in Smalltalk is done by sending messages.

Syntactically, there are three different types of Smalltalk messages: Unary messages, binary messages and keyword messages. Unary messages are used to send messages that have no arguments. Keyword messages are used to send messages having one or more arguments. Binary messages—which have one argment exactly—are used when the message selector is composed of punctuation characters, such as the characters usually used in other programming languages for the arithmetic operators +, -, * and /.

All message sends have the message receiver as an operand, in addition to the message arguments (if any.) The receiver of a message is always the leftmost operand in a message send. The message is specified to the right of the receiver, with only whitespace used to separate one from the other. The syntax for specifying a message selector (and any arguments) vary among the three message types.

Binary Messages: [Production rule: 48] The selector of a binary message is composed of either one or two binary selector characters. Although the complete list of binary selector characters was specified previously, here it is again for easy reference:

~ ! @ % & * - + = | \ < > , ? /"A whitespace-separated list of characters"

Syntactically, a binary message send starts with the receiver of the message, followed by the binary message selector, followed by the message argument (a binary message always has one argument.) Examples of binary message sends are shown in the following examples:

3 - 4 Receiver = 3
Selector = #-
Argument = 4
"Subtract 4 from 3"
=> -1
3 / 4 Receiver = 3
Selector = #/
Argument = 4
"Answers a Fraction whose numerator is 3 and whose denominator is 4"
3.0 / 4 Receiver = 3.0
Selector = #/
Argument = 4
"Floating-point division"
=> 0.75
10 // 4 Receiver = 10
Selector = #//
Argument = 4
"Integer division"
=> 2
10 \\ 3 Receiver = 10
Selector = #\\
Argument = 3
"Division modulo 3"
=> 1
'abc' = 'abc' Receiver = 'abc'
Selector = #=
Argument = 'abc'
"Answers whether the String literals 'abc' and 'abc' have the same value"
=> true
'abc' ~= 'abc' Receiver = 'abc'
Selector = #~=
Argument = 'abc'
"Answers whether the String literals 'abc" and 'abc' have different values"
=> false
42 > 42 Receiver = 42
Selector = #>
Argument = 42
"Answers whether 42 is greater than 42"
=> false
42 <= 42 Receiver = 42
Selector = #<=
Argument = 42
"Answers whether 42 is less than or equal to 42"
=> true
'con', 'cat' Receiver = 'con'
Selector = #,
Argument = 'cat'
=> 'concat'
#surname -> 'Flintstone' Receiver = #surname
Selector = #->
Argument = 'Flintstone'
"Answers an Association whose key is #surname and whose value is 'Flintstone'."
3 @ 4 Receiver = 3
Selector = #@
Argument = 4
"Answers a Point whose x-coordinate is 3 and whose y-coordinate is 4"

With two exceptions, binary message selectors are not operators! Their meaning is not defined by the syntax of Smalltalk. For all unary message selctors and all keyword message selectors, and for all but two binary message selectors, it is the object that receives a message that operationally defines the meaning of whatever messages it receives, as a result of the behavior that it exhibits in response to a message. And the behavior that an object exhibits is defined by its class. Which means that, with the exception of two special binary message selectors, the semantics of message selectors is defined by Smalltalk's class library, and not by its syntax. That's true of all the binary messages shown in the examples above.

The two binary message selectors which actually are operators, and whose semantics actually are defined by the syntax of Smalltalk, are the two pseudo-message selectors #== (the equal sign repreated twice) and #~~ (the tilde character repeated twice.) Those two "message selectors" syntactically look and act like binary message selectors, but are actually operators, and not really message selectors at all. Both the #== operator and the #~~ operator have two operands, one on the left and one on the right (just like a binary message selector.) The #== operator tests whether both its operands are the same, identical object. The #~~ operator tests whether both its operands are not the same, identical object. They are object identity tests—each with inverse semantics from the other. Two objects have the same identity (are identical) if they occupy the same storage location in memory—which is a far stronger notion of "equality" than simply having the same value. For example, two different String objects may contain the same sequence of characters—and so be "equal" to each other, in that they have the same value—and yet be different, distinct objects stored at different locations in memory. In the case of String objects (for example,) that distinction may be rather important, since String objects are mutable. Two different String objects that are equal one moment (have the same value) may not be equal the next—if one or both of them should happen to have any characters changed.

The default meaning of the binary message selector #= is that it tests the message receiver against the message argument for equality of value (which is a weaker equality test than the one performed by the #== operator.) The default meaning of the binary message selector #~= is that it tests the message receiver against the message argument for non-equality of value (which is a weaker equality test than the one performed by the #~~ operator.) It is usually best to use #= instead of #==, and #~= instead of #~~.

Since, generally speaking, binary message selectors are not operators, the Smalltalk compiler does not know the semantics of a binary message selector. The compiler cannot simply assume that the selector #+ has the semantics of addition, or that the selector #* has the semantics of multiplication. Therefore, the compiler cannot apply operator precedence rules to binary message selectors—they aren't operators. So the compiler parses expressions and emits code so that binary message sends are evaluated form left to right in all cases (unless parens are used to create a nested expression requiring a different order of expression evaluation,) as demonstrated by the following examples:

7 + 3 * 2 ...evaluates to 20, not 42
6 - 2 / 4 ...evaluates to 1, not the Fraction 11/2
12 - (2 * 5) ...evaluates to 2 (parentheses can be used to change the order in which messages are sent)

Finally, it should be noted that a method with any binary message selector can be added to any class—including any class in the standard library, such as String, Collection or Object. For example, any class can have a method whose selector is #+ (a plus sign,) or #* (an asterisk,) or #< (a left angle bracket.) Although the standard Smalltalk concatenation message selector is a comma (#,), a programmer can easily add a method to SequenceableCollection with the selector #+ (plus sign) that also does concatenation, so that Strings (or any other SequenceableCollections, such as Arrays) could then be concatenated using #+, in addition to using #, (comma):

'hello' + ' world' => 'hello world' (assumes addition of #+ method to SequenceableCollection)
#(1 2) + #(3) => #(1 2 3) (assumes addition of #+ method to SequenceableCollection)

Were selectors such as #+, #<= or $| operators, the general ability to define methods with such selectors in any class would constitute what in other languages is called "operator overloading." Although the effect is the same, it would not be correct to apply that term to the syntax of Smalltalk, because selectors such as #+, #* and #= are not operators! To put it another way: Programmer-definable binary message selectors give Smalltalk all the benefits of operator overloading, without actually having operators (execpt as noted above.)

Unary Messages: [Production rule: 45] A unary message has no message arguments. The selector of a unary message is syntactically an identifier. As is the case for all message sends, a unary message is sent (syntactically) by writing the message after the message receiver—with only whitespace as separation, without any other syntactic decoration (i.e., no periods or any other punctuation is used between the receiver and the message.)

Although it has no message arguments, a unary message send has one operand: the message receiver. The following are some examples of sending unary messages to receivers:

1 negated=> -1
0.5 reciprocal=> 2.0
1.9 rounded=> 2
9 sqrt=> 3
'hello' size=> 5
'72' asNumber=> 72
72 printString=> '72'
(3 > 4) not=> true
Object superclass=> nil "The class Object is a root class that has no superclass"
#(2 $y 3.14d) class=> Array
#(2 $y 3.14d) copy=> #(2 $y 3.14d) "Answers a copy"

Keyword Messages: [Production rule: 52] A keyword message has one or more message arguments. The selector of a keyword message is syntactically a sequence of one or more keywords; a keyword is an identifier followed by a colon character, without any whitespace in between.

There is one message argument per keyword, and one keyword per message argument. A keyword message begins with the first keyword of its selector, which must be followed by the first message argument. That pattern is repeated for each keyword and message argument in the selector: The ith keyword is followed by the ith message argument, such that the message arguments are interleaved in between the keywords, in order, until every pair of keyword and message argument has been written. Note that the colon character at the end of each keyword makes it easy to differentiate the keywords from the message argument that follows each keyword..

As is the case for all message sends, a keyword message is sent (syntactically) by writing the message after the message receiver—with only whitespace as separation, without any other syntactic decoration (i.e., no periods or any other punctuation is used between the receiver and the message.)

In addition to the message arguments, a keyword message send has one additional operand: the message receiver. The following are some examples of sending keyword messages to receivers:

12 quo: 5 Receiver = 12
Selector = #quo:
Argument = 5
"12 div 5 (integer division)"
=> 2
12 rem: 7 Receiver = 12
Selector = #rem:
Argument = 7
"12 mod 7"
=> 5
5 bitAnd: 2 Receiver = 5
Selector = #bitAnd:
Argument = 2
"2r101 bitAnd: 2r10"
=> 0
5 bitOr: 2 Receiver = 5
Selector = #bitOr:
Argument = 2
"2r101 bitOr: 2r10"
=> 7
'hello' at: 1 Receiver = 'hello'
Selector = #at:
Argument = 1
=> $h
#(#xyz 'fred' 7.99s) at: 3 Receiver = #(#xyz 'fred' 7.99s)
Selector = #at:
Argument = 3
=> 7.99s
'abc' collect: [:char | char asUppercase] Receiver = 'abc'
Selector = #collect:
Argument = [:char | char asUppercase]
=> 'ABC'
#(1 2 3 4 5) select: [:n | n odd] Receiver = #(1 2 3 4 5)
Selector = #select:
Argument = [:n | n odd]
=> #(1 3 5)
aView displayOn: aWindow at: aPoint Receiver = aView
Selector = #displayOn:at:
Arguments = (aWindow, aPoint)
"A view is asked to display itself on a window at a point"
3 < 4 ifTrue: ['con', 'cat'] ifFalse: [3 + 4] Receiver = true
Selector = #ifTrue:ifFalse:
Arguments = (['con', 'cat'], [3 + 4])
=> 'concat'
3 > 4 ifTrue: ['con', 'cat'] ifFalse: [3 + 4] Receiver = false
Selector = #ifTrue:ifFalse:
Arguments = (['con', 'cat'], [3 + 4])
=> 7
DateAndTime
    year: 2008
    month: 9
    day: 17
    hour: 17
    minute: 16
    second: 43
Receiver = DateAndTime
Selector = #year:month:day:hour:minute:second:
Arguments = (2008, 9, 17, 17, 16, 43)
=> 2008-09-17T17:16:43 (17 Sep 2008 @ 5:16:43 pm)

Keyword message syntax makes Smalltalk far more self-documenting:

  1. The keyword that precedes each message argument documents the role or intent of that argument;
  2. Due to the interleaving of keyword and arguments, multi-argument message sends often resemble English sentences that reveal/document the meaning/intent of the message—far more effectively than can be achieved using any conventional syntax for multi-argument function/method calls.

For example, contrast and compare the following "multi-argument" function calls using Java syntax against the equivalent Smalltalk message sends[1]:

aString.substring(2, 5)<==> aString copyFrom: 2 to: 4
aPoint.nearestPointOnLine(p1, p2)<==> aPoint nearestPointOnLineFrom: p1 to: p2
new ColorValue(a, b, c)<==> ColorValue hue: a saturation: b value: c
aFont.display(s, n, m, p, gc)<==> aFont displayString: s from: n to: m at: p on: gc
new DateAndTime(a, b, c)<==> DateAndTime year: a day: b timeZone: c

Given dynamic strong typing, it's far more important in practice to document (and clearly indicate) the semantic role played by message arguments (and variables and classes,) than it is to specify static type constraints. Whatever type information might be useful can usually be inferred from the semantic role of an argument, variable or class.

  1. Most of the Smalltalk message selectors shown are those of methods in the standard class library of one or more dialects of Smalltalk. For the first Java example, the function is defined in Java's standard library. The other Java examples show Java function names and arguments that attempt to be reasonable translations of the corresponding Smalltalk messages.

Message Evaluation Precedence: [Production rule: 53] Since a Smalltalk expression can be composed of multiple message sends, it is necessary that the order of evaluation for message sends be well defined. There are two rules that determine the order in which message sends will be evaluated:

  1. Messages with higher intrinsic evaluation precedence are sent before messages with lower intrinsic evaluation precedence.
  2. When messages have the same intrinsic evaluation precedence, the message on the left is sent before the message on the right (in other words, messages with the same intrinsic evaluation precedence are evaluted in left-to-right order.)

The intrinsic evaluation precedence of a method is based on its type, as specified in the following table:

Message Type Intrinsic Evaluation Precedence
Unary Message High
Binary Message Medium
Keyword Message Low

So all unary messages in an expression are sent first—in left to right order relative to each other. Then, all binary messages are sent—again, in left-to-right order relative to each other. Finally, any keyword messages are sent—in left-to-right order relative to each other.

Examples of the order of evaluation of multiple messages in the same expression are shown below:

10 squared negated"#squared is sent before #negated"
3 negated @ 4 negated"both #negated messages are sent before #@ is sent"
anArray at: 2 put: 3 @ 4"3 @ 4 is evaluated frist, then #at:put: is sent to anArray"
'con', 'cat' at: 5"#at: 5 is sent to 'concat', which was previously constructed by sending #, 'cat' to 'con'"

Cascaded Messages: [Production rule: 54] A cascaded message is a syntactical abbreviation for sending multiple messages, one after the other, to the same receiver, so that the receiver is only specified once. Any number, and any combination, of unary messages, binary messages and keyword messages may be linked together using the message cascade syntactic operator to form a cascaded message, with the effect that all the messages in the message cascade have the same receiver. The first message in the cascade must explicitly specify a receiver; subsequent messages in the same cascade must not specify any receiver, since their reciever is the same as that of the first message in the cascade.

Cascaded messages are used quite frequently.

The value of a message cascade is the value returned by the final message in the cascade. That means that all messages in a message cascade other than the final one have no import other than for whatever side effects they may have. In other words, except for the final message in a cascade, the only reason to send messages in a cascade is because of the side effects of those messages.

The message cascade syntactic operator is the semicolon ($;), which acts as a message separator. Many other languages use the semicolon as a statement separator. Smalltalk uses it as a message separator, instead. The following example demonstrates the syntax (the line breaks in the example are simply whitespace, and have no other syntactical significance):

Date new
    setYear: 2007;
    setMonth: 9;
    setDayOfMonth: 21;
    yourself
The unary message #new is sent to Date, which results in a new instance of Date. The new instance of Date is then sent the following messages, in the specified order:
  1. setYear: 2007 (because this message is the one on the left-hand side of the first semicolon, its receiver is the one that will be sent all the subsequent messages in the cascade)
  2. setMonth: 9 (second message in cascade)
  3. setDayOfMonth: 21 (third message in cascade)
  4. yourself (final message in cascade—it's return value is also the return value of the entire message cascade)

The receiver of the first message in a message cascade is explicitly specified as the expression to the left of the message—which is just normal message sending syntax, as explained previously. Since the first message in a message cascade ends with a semicolon, the rule for determining the one receiver used by all the messages in a cascade is to find the message on the left-hand side of the first (leftmost) semicolon, and then to identify the receiver of that message—the receiver of that message is the receiver for all the subsequent messages in the same cascade. For example, let's analyze how and why the expression 10 squared // 20; + 1 evaluates to 101:

  1. The message #squared is sent to 10 with result = 100;
  2. The message #// 20 is sent to 100 (which was the result of the previous unary message send) with result = 5;
  3. The message #+ 1 is sent to the receiver of the previous message—which was 100—with result = 101;
  4. The value of the message cascade as a whole is the value returned by the final message in the cascade—which was 101.

The primary motivation for the existence of the message #yourself—which does nothing except return the receiver of the message, is to be used as the final message in a message cascade. That's because a message cascade is often used to create and initialize an object, using a cascade of messages to set the object's attributes (so only the side effects matter,) and then sending the message #yourself as the final message in the cascade in order to return the initialized object—exactly as shown in the example above.

Expressions: [Production rules: 43, 55] An expression is a segment of code in a body of executable code that can be evaluated to yield a value as a result of its execution. Expressions are nestable (recursive) structures: An expression may be an indivisible lexical token (such as a number literal,) or it may be composed of sub-expressions.

An expression may be any of the following syntactical elements:

Expression Type Example(s)
Literal: 3.14    "Any literal value—including a block literal"
Variable or Argument Reference:
Pseudo-variable Reference:
Constant Reference:
anArray    "A reference to a variable or formal argument (of any type)"
self    "A reference to a pseudo-variable"
nil    "A reference to a buit-in constant"
Message Send (or Cascade): 456 factorial    "Whether or not the message is cascaded is irrelevant"
Nested Expression: ('hello' at: 1)    "The enclosing parens form an explicit nested expression"

When the name (identifier) of a variable appears in an expression, it may refer to the value of a variable of any type: local variable, formal argument, instance variable, class variable, shared pool variable or global variable.

One expression may be nested inside another as a sub-expression, in either of two different ways:

  1. Any expression that is either the receiver of a message, or is the argument of a message, is implicitly a nested sub-expression of the expression formed by the message send of which it is a part;
  2. An expression (or, in fact, a statement) may be explicitly made to be a nested expression by enclosing it between a left-paren and a right-paren, which operate as the left and right delimiters, respectively, of an explicit nested expression.

Explicit nested expressions (enclosed between two parens as delimiters) are used to change the order in which messages are sent. An expression (or statement) that is delimited as an explicit nested expression is evaluated before the expression that contains it.

Due to the fact that unary messages are sent before binary messages, binary messages are sent before keyword message, and messages of the same type are evaluated from left to right, explicit nested expressions must be used for any of the following cases:

Unary message sent to result of binary message send: (3 @ 4) negated
Unary message sent to result of keyword message send: ('hello' at: 1) asUppercase
Binary message with a binary message send as its argument: 1 / (3 @ 4)
Binary message with a keyword message send as its argument: x * (anArray at: n)
Keyword message sent to result of keyword message send: (matrix at: rowIndex) at: columnIndex
Keyword message with a keyword message send as its argument: Transcript show: (errorMessages at: errorIndex)

There are also cases where it is not necessary to use an explicit nested expression in order to form complex expressions involving multiple message sends. For example, unary message sends can be infinitely nested without any need to use an explicit, paren-delimited nested expression, as in the following example: 42 factorial negated sqrt reciprocal (which in math-like notation would be "reciprocal(sqrt(negated(factorial(42))))." Similarly, binary message sends can also be infinitely nested without using explicit, paren-delimited nested expressions—provided the default left-to-right order in which the messages will be evaluated will compute the right result. When in doubt—or just for clarity—use parens to form an explicit nested expression.

Statements: [Production rules: 56, 57] Syntactically, a statement differs from an expression by the fact that a statement may optionally begin with one or more assignment operations. Each assignment operation assigns the value of the expression that follows it to the value of a variable. The variable to which a value is assigned by an assignment operation may be any type of variable. Note, however, that neither formal block arguments nor formal method arguments are variables; their value cannot be reassigned (rebound,) and they cannot appear on the left-hand operand of an assignment operation. But any local variable, instance variable, class variable, shared pool variable or global variable can be the left-hand operand of an assignment operation.

When a statement does not start with one or more assignment operations, it is indistinguishable from an expression. Syntactically, all expressions also qualify as valid statements, but statements that are prefixed with one or more assignment operations do not qualify as valid expressions. Statements that are not prefixed by any assignment operations are useful only for whatever side effects they may have—which is similar to the usage of all but the last message in a cascaded message send.

The lexical token for the Smalltalk assignment operator—which really is an operator, and not a message—is a colon followed immediately (no whitespace) by an equal sign (":="), which is the same assignment operator token used by many other programming languages—but is not the one used by the C language family, which uses the equal sign by itself as its assignment operator. In Smalltalk, a lone equal sign is a binary message selector that is almost universally used to compare two operands for equality of value.

The following examples demonstrate statements having one or more assignment operations as their prefix:

n := 0 "Assign 0 as the value of the variable n"
n := n + 1 "Increment the variable n by 1"
name := 'fred flintstone' asUppercase "Assign the value 'FRED FLINTSTONE' as the value of the variable name"
prev := next := nil "Assign nil as the value of both the variable prev and the variable next"
next := (prev := next) parent "A nested expression encloses a statement—which can itself have an assignment operation as a prefix—so the example first assigns the value of next to prev, and then assigns the value of next parent to next"

Note that last example in the table above. As its comment says: A nested expression encloses a statement—which can itself have an assignment operation as a prefix. The presence of one or more assignment operations as prefixes to a statement does not change the value of the statement as an expression—so the value of the expression "(prev := next)" is the same as the value of the expression "next". In order for a statement that has any assignment operations as prefixes to be embedded as a sub-expression, it is necessary to use an explicit nested expression (i.e., it must be delimited by a left-paren and a right-paren.)

Statement Sequences: [Production rules: 58, 59, 61] The statements in a sequence of statements are separated from each other using the statement separator token. The statements in a series of statements are evaluated in left-to-right order (although, since each statement is often written on a new line below the previous statement, the sequence of execution will usually appear to be from the top of the page down.) Note that the statement separator token serves to separate one statement from another syntactically—it is not a statement terminator. Although it is allowed, it is not required to terminate the last statement in a sequence using a statement separator token.

Each statement in a sequence of statements forms a distinct, independent expression. Without the statement separator, there would be no way to syntactically isolate one expression from another. The statement separator stops the left-to-right evaluation of the messages in an expression, where each new message on the right is sent to whatever object is the value of the expression on the left.

The statement separator token is the period (.) character. Using the period as the statement separator token makes Smalltalk read much more like a natural (human) language—since the period is widely used by natural languages as a sentence terminator/separator. Making Smalltalk code resemble natural language notational conventions and sentence structure was an important design goal of Smalltak's creators.

A final statement is the final (ending) statement either of a method or of a block literal. A final statement may optionally have a method return operator as a prefix. Like the assignment operator, the method return operator is actually an operator, not a message. When executed, a method return operator causes the method in which it appears to return control to the sender of the message whose sending resulted in the execution of the method. The value returned by the method return operator to the sender of the message is the value of the statement to which the method return operator is prefixed.

Syntactically, the method return operator token is a caret (^) character. So, when executed, the statement "^nil" would return the value nil as the result of the method in which it appears.

The following example demonstrates a sequence of statements separated by the statement separator token, and also the use of the method return operator token:

"For clarity, each statement is on a different line. That's not required, but it's standard practice."

filename := 'document.txt' asFilename.
stream := filename readStream.
string := stream upToEnd.
stream close.
^string asCollectionOfLines

Block Invocation: There are no grammatical production rules for invoking/evaluating blocks in Smalltalk, because block invocation/evaluation is done by sending messages, and not by means of dedicated syntax. To invoke/evaluate a block, it must be sent a message.

To invoke/evaluate a block that has no arguments, send the unary message #value to the block, as in the following examples:

[3 + 4] value The value 7 is returned as the result of sending the message #value to the block [3 + 4].
[1 * 2 * 3 * 4 * 5] value The value 120 is returned as the result of sending the message #value to the block [1 * 2 * 3 * 4 * 5].
[] value The metavalue nil is returned as the result of sending the message #value to the block [].

To invoke/evaluate a block that has one argument, send it the keyword message #value:, with the block argument as the argument of the message. To invoke/evaluate a block that has two arguments, send it the keyword message #value:value:, with the two block arguments as the arguments of the message. To invoke/evaluate a block that has three arguments, send it the keyword message #value:value:value:, with the three block arguments as the arguments of the message. To invoke/evaluate a block that has 4 or more arguments, send it the keyword message #valueWithArguments:, with an Array as the message argument—the elements of the Array will be used, each in order, as the logical arguments to the block. Examples are shown below:

Block Literal Invocation Message Result of Invocation
[:x | x * x] value: 6  =>   6 * 6   => 36;
When the message value: 6 is sent to the block (with the message argument 6,) it responds by evaluating its function with the value of the formal argment (named x) set to the same value as the message argument (which is 6,) so that it evaluates the formula 6 * 6, and then answers the result of that computation (which is 36) as the result of the message send.
[:a :b | a < b] value: 3 value: 4  =>   3 < 4   =>   true;
When the message value: 3 value: 4 is sent to the block (with the message arguments 3 and 4,) it responds by evaluating its function with the value of the formal argments (named a and b) set to the same values as the message arguments (3 and 4, respectively,) so that it evaluates the expression 3 < 4, and then answers the result of that computation (which is true) as the result of the message send.
[:x :m :b | m * x + b] value: 33 value: 1.5 value: -12  =>   (1.5 * 33 + -12)   =>   37.5;
When the message value: 33 value: 1.5 value: -12 is sent to the block (with the message arguments 33, 1.5 and -12,) it responds by evaluating its function with the value of the formal argments (named x, m and b) set to the same values as the message arguments (33, 1.5 and -12, respectively,) so that it evaluates the expression 1.5 * 33 + -12, and then answers the result of that computation (which is 37.5) as the result of the message send.
    [:y :mo :d :h :mi :s |
        DateAndTime
            year: y
            month: mo
            day: d
            hour: h
            minute: mi
            second: s]
valueWithArguments:
        #(2007 9 22 8 0 0)
 =>   2007-09-22T08:00:00;
When the message valueWithArguments: #(2007 9 22 8 0 0) is sent to the block (with the message argument #(2007 9 22 8 0 0), it responds by evaluating its function with the value of the formal argments (named y, mo, d, h mi and s) set to the same values as the elements of the array which is the message argument, so that it evaluates the expression DateAndTime year: 2007 month: 9 day: 22 hour: 8 minute: 0 second: 0, and then answers the result of that computation (which is an instance of DateAndTime) as the result of the message send.

Control Structures: There are no grammatical production rules for control structures in Smalltalk, because control structures are implemented as methods that are invoked by sending messages, and not by means of dedicated syntax. The obvious implication of that would be that you now already know the syntax of control structures in Smalltalk—you just don't know the messages that are used. A not so obvious implication is that programmers can and do define their own control structures by defining/implementing classes and methods when and as needed.

The messages that are most commonly used for the same purpose as traditional control structures are shown in the following table:

Control Structure Message Pattern Example
"If..then.." boolean expression
    ifTrue: trueBlock
a < b ifTrue: [b - a]
"If..then..else.." boolean expression
    ifTrue: trueBlock
    ifFalse: falseBlock
a < b
    ifTrue: [b - a]
    ifFalse: [a - b]
"Infinite loop" block repeat [connection := socket listenForConnection.
self handleConnection: connection] repeat
"Loop N times" count timesRepeat: block 3 timesRepeat: [Transcript show: 'I love you'; cr]
"For loop" initialIndex to: finalIndex
    do: oneArgumentBlock
1 to: aString size
    do: [:index | outStream nextPut: (aString at: index)]
"For..by.. loop" initialIndex to: finalIndex
    by: stepValue
    do: oneArgumentBlock
1 to: aString size
    by: 2
    do: [:index | outStream nextPut: (aString at: index)]
"Repeat while loop" conditionBlock whileTrue [char := stream next.
char isSeparator] whileTrue
"Repeat until loop" conditionBlock whileFalse [char := stream next.
char isPunctuation] whileFalse
"While..do.. loop" conditionBlock
    whileTrue: inductionBlock
[parser nextToken: token; isExpectingToken]
    whileTrue: [token := lexicalAnalyzer nextToken]
"Until..do.. loop" conditionBlock
    whileFalse: inductionBlock
[readStream atEnd]
    whileFalse: [writeStream nextPut: readStream next]

Note that the operands of Smalltalk's "control structure" messages are usually blocks, boolean values or numbers.

The value of the "if test" messages is the value of whichever block happens to execute—or nil if no block executes. So the value of the expression 3 < 4 ifTrue: [4 - 3] is 1; the value of the expression 3 > 4 ifTrue: [4 - 3] is nil; and the value of the expression a < b ifTrue: [b - a] ifFalse: [a - b] is the absolute value of a - b. In the case of the "if test" messages, the receiver of the message (#ifTrue:, #ifTrue:ifFalse:, #ifFalse: or #ifFalse:ifTrue:) would normally be either the object true or the object false. Given that true is an instance of the class True, and that false is an instance of the class False, how do you think the corresponding methods might be implemented? The answer will be shown later, in the section on method declarations.

The "for.. loop" messages, which take a one-argument block as their final message argument, evaluate that block some number of times, each time with a different number as the actual argument. In the case of the expression 1 to: 3 do: [:n | product := product * n], the one-argument block is evaluated three times, once with the value of the formal block argument (named n) set to 1, once with it set to 2, and once with it set to 3.

Boolean Operators: Smalltalk implements the standard boolean operators using a set of standard messages. Two of those messages (#and:, #or:) use a no-argument block as the right-hand operand. The reason for using a block as an operand is in order to provide lazy evaluation of boolean expressions: Whether the block that is the right-hand operand is evaluated depends on the value of the left-hand expression. If evaluated, the block that is the right-hand operand must evaluate to either true or false:

Boolean Operator Message Pattern Example
and expression and: block
"The block on the right is only evaluated
if the expression on the left is true"
account isOpen and: [amount <= max]
inclusive or expression or: block
"The block on the right is only evaluated
if the expression on the left is false"
filename exists or: [filename createWithSize: 0]
and expression & expression
"Both operands are always evaluated"
account isOpen & ((amount := amount + fee) <= max)
inclusive or expression | expression
"Both operands are always evaluated"
account balance >= amount | account holder creditEvaluation isExcellent
"The creditEvaluation will always be performed"
exclusive or expression xor: expression
"Both operands are always evaluated"
operand1 < 0 xor: operand2 < 0
not expression not filename exists not

Collections: Conceptually, a collection is an encapsulation of a set or sequence of values by a single value (object) that functions as the container of the values in the set or sequence. Arrays, Strings, Sets and Dictionaries (i.e, Maps) are all implemented as Smalltalk Collections. Operationally, a collection is any object that responds to the basic set of messages that provide "collection behavior." Usually, but not necessarily, that means that a "Collection object" is an instance of a class that inherits from the standard class Collection.

The fundamental "collection message" is #do:, whose receiver is a collection and whose argument is a one-argument block. In response to the message #do:, a collection iterates over each of the values it contains as its elements, and evaluates the one-argument block (which is the message argument of #do:) with each of its elements as an argument to the block (so the block is executed once for each element of the collection). The following table provides some examples:

sum := 0.
#(-35 51 6 -192 278)
    do: [:element | sum := sum + element].
^sum
"The example computes the sum of all the values that are elements of the array literal, and sets the variable sum to that value—which in this example is the value 108."
'Strings are collections, too!'
    do: [:char | writeStream nextPut: char].
"The example writes each character of a String onto a Stream."
minValue := Infinity positive.
#(-35 51 6 -192 278)
    do: [:element | minValue := minValue min: element].
^minValue
"The example results in the variable minValue being set to the value of the element of the array whose value is less than or equal to that of any other in the array—which in this example, is the element whose value is -192."

In addition to the message #do:, there are other widely-used collection messages that involve enumerating over the elements contained in a collection. These include the messages #select:, #reject:, #collect:, #contains:, #detect:ifNone: and #inject:into:—all of which can be impelemented in terms of the message #do:, which is the way they are all implemented in class Collection (so all Collections inherit them.) An example usage of each, with an explanation of what each one does, is shown below:

Collection Enumeration Message Pattern
Example
Explanation; Result
Message pattern:
    aCollection select: aOneArgBlock

#(-35 51 6 -192 278) select: [:n | n < 0]

"#select: answers a new collection (usually of the same type as the receiver) all of whose elements satisfy a predicate. The predicate (which is a function that accepts one or more arguments and evaluates to a Boolean value) is represented by a one-argument block that, when evaluated with each of the receiver's elements as its argument, answers either true or false. The collection returned will only contain those elements of the receiver for which the predicate block (the argument of #select;) evalutes to true."

=> #(-35 -192)

Message pattern:
    aCollection reject: aOneArgBlock

#(-35 51 6 -192 278) reject: [:n | n < 0]

"#reject: is the inverse of #select:, in that it answers a new collection that contains only those elements that do not satisfy the predicate function specified by the one-argument block that is the argument of #reject:."

=> #(51 6 278)

Message pattern:
    aCollection collect: aOneArgBlock

'Strings are Collections, too!'
    collect: [:char | char asUppercase]

"#collect: answers a new collection (usually of the same type as the receiver) constructed by evaluating the one-argument block that is the argument of the #collect: message with each element of the receiver as its argument, and storing the value returned as the result of that evaluation as the corresponding element of the new collection returned as the result of sending #collect:."

=> 'STRINGS ARE COLLECTIONS, TOO!'

Message pattern:
    aCollection contains: aOneArgBlock

'Strings are Collections, too!'
    contains: [:char | char = $!]

"#contains: answers whether or not the receiver contains any elements that, when evaluated as the argument of the one-argument block that is the argument of #contains:, results in the value true."

=> true

Message pattern:
    aCollection detect: aOneArgBlock ifNone: noArgBlock

#('http://www.apple.com/' 'http://planet.smalltalk.org/'
'ftp://elsie.nci.nih.gov/pub/' 'https://www.jpmorgan.com/')
    detect: [:string | string beginsWith: 'ftp://']
    ifNone: ['ftp://localhost/']

"#detect:ifNone: answers the first element of the receiver that, when evaluated as the argument of the one-argument block that is the first argument of #detect:ifNone: (the one following the keyword #detect:), results in the value true. If no elements of the receiver 'pass the test', then the result of evaluating the no-argument block (which follows the keyword #ifNone:) is returned, instead."

=> 'ftp://elsie.nci.nih.gov/pub/'

Message pattern:
    aCollection inject: anObject into: aTwoArgBlock

#(31 -117 208)
    inject: 0
    into: [:sum :element | sum + element]

"#inject:into: answers the value that results from the following procedure: The two-argument block that follows the keyword #into: is initially evaluated with two arguments, the first being the value of the argument that follows the keyword #inject: and the second being the first elment of the receiver. Then the two-argument block that follows the keyword #into: is evaluated with the result of the previous evaluation as its first argument, and the next element of the receiver as its second argument. The result of the final evaluation of the block is then returned as the result of sending #inject:into:. This message is commonly used to do precisely what the example on the left actually does: compute the sum of a collection of numbers—although it can be used for much more than just that."

=> 122

There are a few other messages commonly sent to collections—although some types of collections cannot meaningfully respond to all of them:

Common Collection Message Pattern
Example
Explanation; Result
Message pattern:
    aCollection size

'How many characters are in this string?' size

"#size answers the number of elements contained by the receiver."

=> 39

Message pattern:
    aCollection at: aKeyOrIndex

#('one' 'two' 'three') at: 2

"#at: answers the element of the receiver at the index or key that is the value of the argument (an index is not the same as a key—but the difference doesn't matter as far the semantics of the message are concerned)."

The receiver must be either a SequenceableCollection (e.g., a String) or a KeyedCollection (e.g, a Dictionary)

=> 'two'

Message pattern:
    aCollection at: aKeyOrIndex put: anObject

aDictionary at: #surName put: 'Flintstone'

"#at:put: stores the argument that follows the keyword #put: as an element of the receiver, at the specified key or index (an index is not the same as a key—but the difference doesn't matter as far the semantics of the message are concerned). The value returned by the message is the value of the argument that follows the keyword #put:."

The receiver must be either a SequenceableCollection (e.g., a String) or a KeyedCollection (e.g, a Dictionary)

=> 'Flintstone'

Message pattern:
    aCollection add: anObject

aSet add: 42

"#add: stores the argument as an additional element of the receiver—as the last element of the receiver, if its elements are ordered. If the receiver implements Set semantics, then a new element will be added only if one with the same value (as determined by the receiver) is not already present. The value returned by the message is the value of the argument."

The receiver must not be a KeyedCollection (e.g, a Dictionary), but must be a growable Collection (e.g, not an Array or String)

=> 42

Instance Creation: New instances are created by sending messages to classes, and not by means of dedicated syntax. So there are no grammatical production rules for "constructors" in Smalltalk. There are two different canonical messages that are used for creating new instances of a class: #new and #new:.

The message #new is used to create either an uninitialized instance of a class, or else to create a new instance whose instance variables have been initialized to some set of default values. Example: The expression Object new creates and answers a new instance of the class Object—which can actually be a useful thing to do in certain (uncommon) situations.

To create a new instance of a class which will be a container for some number of other objects, the message new: aNumber is used. The message argument either specifies the precise number of objects that the new instance will contain, or else it provides a hint to the receiver regarding the number of objects that the new instance should be optimized to hold. The distinction matters because some collections (or collection-like objects) have a fixed size that cannot be changed, but other collections (or collection-like objects) are "growable" (their size can be changed dynamically.)

Normally (but not necessarily,) a new instance created using the message #new: will use indexed instance variables to hold the objects it will contain. An object with indexed instance variables encapsulates/represents an ordered sequence (ordered collection) of objects as a single value. For example, an array object encapsulates/represents (as a single value) an ordered sequence of the values (objects) it contains as its elements—and does so by means of indexed instance variables, one for each element in the array. Similarly, a String object encapsulates/represents (as a single value) an ordered sequence of character values—and does so by means of indexed instance variables, one for each character in the String.

The expression Array new: 3 will create a new Array object with 3 elements—all of which will initially be the value nil. The expression String new: 80 will create a new String object that contains 80 characters—all of which will initially be the null character.

A class (or any other object) may also implement additional instance creation methods, whose selectors and arguments will of course be specific to each case.

Local Variables: [Production rule: 60] A local variable is a variable that is local to either a method or a block. It's called a "local variable" because it is only referenceable by (accessible to) the code of the method or block where it is declared. A local variable is alternatively called a temporary variable—in reference to the fact that it comes into existence each time a method or block is executed, and usually ceases to exist when the execution of the method or block where it is declared terminates (there's an exception that will be discussed later.) There is a distinct, separate existence of "the same" local variable for each recursive invocation of a method or block, as there is for each separate thread that executes a method or block—even when those executions of the method or block overlap in time.

One or more local variables can be declared in a local variable declaration list, which can appear at the beginning of any method or block literal. The local variable declaration list (if present—it's not required) must immediately follow any formal argument declarations or method headers, and must precede any executable statements.

Syntactically, a local variable declaration list is delimited by the bar (|) character, which serves to mark the beginning and the end of the list of variables declared by a local variable declaration list. Between the beginning and ending bar (|) character, there may occur a list of zero or more bindable identifiers, separated from each other by whitespace (which is required.) A bindable identifier is an identifier that does not match one of the three built-in constants (nil, false, true) nor one of the three pseudo-variables (self, super, thisContext).

Example 1:

"Answer the highest-valued element of an Array of numbers"

| maxValue |    " <== Declares maxValue as a local variable"
maxValue := Infinity negative.
#(18 2 64 -99) do: [:element | maxValue := element max: maxValue].
    "For each element of the array, assign to maxValue whichever value is larger: the value of the element, or that of maxValue"
^maxValue

Example 2:

"Answer the y-coordinate of a point on a line whose x-coordinate is xCoordinate, for a line whose slope is slope and whose y-intercept is yIntercept."

| xCoordinate slope yIntercept yCoordinate |    " <== Declares xCoordinate, slope, yIntercept and yCoordinate as local variables"
xCoordinate := 33.
slope := 1.5.
yIntercept := -12.
yCoordinate := slope * xCoordinate + yIntercept.
^yCoordinate

Method Declarations

A method declaration starts with a method header, which is then followed by executable code. Executable code optionally starts with a local variable declaration list, followed by a sequence of statements. Method declarations do not make any reference to the class that defines the method.

A method's selector is specified by its header. Methods are executed in response to a message whose selector matches that of the method.

For a method that has arguments, the method header declares a formal argument corresponding to each actual argument of any message whose sending causes the method to be executed. A formal method argument declaration is effected syntactically by writing a bindable identifier in the appropriate location in a method header (see below.) A bindable identifier is an identifier that is not one of the six reserved identifiers: nil, true, false, self, super or thisContext.

For each distinct execution of a method in response to a message, the value of each actual message argument is assigned to be the value of the corresponding formal method argument—independently for each separate execution, even when a method re-executes itself recursively, or when a method is invoked by multiple threads at the same time. Also, each distinct execution of a method uses a distinct instance of each local variable declared in the method. In other words, Smalltalk methods (and blocks) are reentrant.

Formal method arguments are not variables, so assignment operations cannot be used to reassign (rebind) the value of formal method arguments.

Each method execution is bound to the context of the receiver of the message that resulted in the method being executed. That means that the code of the method will have access to the instance variabes of the object that received the message, and to the class variables defined in the inheritance hierarchy of the receiver's class, and to the shared pool variables imported by its class.

A method may have any number of statements prefixed by a method return operator—but only one of them can be in the method directly; the others must be in block literals within the code of the method. A statement that is prefixed by a method return operator must be the final statement of that body of executable code—in other words, it must be the final statement of a method or block literal. So, although the main body of a method's executable code may contain at most 1 method return operator, the executable code of each block literal that appears in a method's executable code may also have a method return operator prefixed to its final statement.

A method return operator does just what its name implies: It returns a result from a method, and so returns the flow of control from the method to the sender of the message. It is not a block return operator. The execution of a statement in a block literal that is prefixed by a method return operator effects a return from the execution of the enclosing method, and not just a return from the execution of the block.

In the case of a method which contains no method return operators, or where the path of execution does not end with a statement prefixed by a method return operator, the value returned by the method is the receiver of the message whose sending caused the method to be executed.

Just as there are syntactically three different types of messages, there are also syntactically three different types of method headers—for each syntactic message type, there is a corresponding type of method header. Since unary messages have no arguments, unary method headers don't either. Since binary messages have exactly one argument, binary method headers also have exactly one argument. Since keyword messages may have one or more arguments, keyword method headers also may have one or more arguments.

For the example method declarations that follow, there are two matters that should be understood:

  1. Each method declaration is an independent compilation unit; there is no standard syntax for declaring a sequence of methods, so that the text of one method may be syntactically distinguished from that of one that precedes or follows it. Above the level of individual methods, Smalltalk programs are defined by dynamic, living objects—and not by static statements residing in flat text files.
  2. Classes are not declared syntactically, so there is no syntactic mechanism by which to associate a method with a class. For the purposes of this pedagogical text only, the comments in the example method declarations that follow will include either a) indications as to the class that is assumed to define the method, or else b) other contextual information necessary to understand the method's semantics.

Unary Method Header: [Production rules: 62, 66, 67] A unary method header is a unary message selector—which is syntactically an identifier. Examples of the complete text of the declaration of a three unary methods follow (the header of each method is highlighted in bold):

value
    "Answer the value of the receiver's instance variable named value."

    ^value

area
    "Answer the area of the receiver, by multiplying the value of its two instance variables width and height."

    ^width * height

fullName
    "Answer the full name of the receiver, formed by concatenating its givenName with its surName (with a space in between.)"

    ^self givenName, ' ', self surName

Binary Method Header: [Production rules: 63, 66, 67] A binary method header is a binary message selector followed by a formal method argument declaration. Examples of the complete text of the declaration of three binary methods follow (the header of each method is highlighted in bold):

+ aSuffix
    "Answer a new SequenceableCollection (of the same type as the receiver)
    which has all the elements of the receiver as its prefix,
    followed by all the elments of aSuffix as its suffix."

    | concatenation |
    concatenation := self class new: self size + aSuffix size.
    1 to: self size do: [:i | concatenation at: i put: (self at: i)].
    1 to: aSuffix size do: [:i | concatenation at: i + self size put: (aSuffix at: i)].
    ^concatenation

- aSet
    "Answer the set difference between the receiver and aSet."

    | setDifference |
    setDifference := self shallowCopy.
    aSet do: [:each | setDifference remove: each ifAbsent: []].
    ^setDifference

/ aDuration
    "Answer the Timeperiod whose starting moment is the receiver, and whose duration is aDuration."

    ^Timeperiod from: self duration: aDuration

Keyword Method Header: [Production rules: 65, 66, 67] A keyword method header declares one or more method arguments. The selector of a keyword method is syntactically a sequence of one or more keywords.

There is one formal method argument per keyword, and one keyword per formal method argument. A keyword method header begins with the first keyword of its selector, which must be followed by the first formal method argument. That pattern is repeated for each keyword and formal method argument: The ith keyword is followed by the ith formal method argument, such that the formal method arguments are interleaved in between the keywords, in order, until every pair of keyword and formal method argument has been written. Note that the colon character at the end of each keyword makes it easy to differentiate the keywords from the formal method argument that follows each keyword..

Examples of the complete text of the declaration of several keyword methods follow (the header of each method is highlighted in bold):

setValue: newValue
    "Set the value of the receiver's instance variable value
    to be the value of the argument (newValue.)"

    value := newValue

max: comparand
    "Answer whichever Magnitude is greater: the receiver, or the argument (comparand).
    If equal, answer the receiver."

    ^self >= comparand ifTrue: [self] ifFalse: [comparand]

ifTrue: trueBlock ifFalse: falseBlock
    "If the receiver represents the value true, answer the result of evaluating trueBlock.
    If the receiver represents the value false, answer the result of evaluating falseBlock."

    "Since the receiver is the sole instance of False, the result of evaluating falseBlock is answered."

    ^falseBlock value

inject: initialValue into: twoArgBlock
    "Evaluate twoArgBlock once for each element of the receiver.
    For each evaluation of twoArgBlock, the first argument is
    the currentValue, and the second argument is each element
    of the receiver in succession. Initially, the value of currentValue
    is initialValue. Subsequently, the value of currentValue
    is the value that resulted from the previous evaluation of twoArgBlock.
    Answer the final value of currentValue."

    "For example, to sum a collection, use:
        collection inject: 0 into: [:sum :each | sum + each]."

    | currentValue |
    currentValue := initialValue.
    self do: [:element | currentValue := twoArgBlock value: currentValue value: element].
    ^nextValue

year: year day: dayOfYear hour: hour minute: minute second: second
    "Anwser a new instance of the receiver (a new instance of DateAndTime)
     representing the specified date and time-of-day."

    ^self new
        setYear: year dayOfYearOrdinal: dayOfYear;
        setHour: hour minute: minute second: second;
        canonicalizeFromLocalTime;
        beImmutable

Advanced Topics

Closures: Smalltalk blocks are full closures. They are able to reference and change the values of variables from any outer scopes that lexically enclose them. So a block literal can not only access and modify the local variables declared in the method whose code defines it, it can also access and modify all the same variables from outer scopes that the method can: the instance variables of the receiver, the class variables of the receiver's class, and the shared pool variables imported by the receiver's class.

Closures: Example 1

    | arrayOfBlocks |
    arrayOfBlocks := #(1 2 3) collect: [:n | [n * n]].
    (arrayOfBlocks at: 3) value

When executed, the final statement of the example above ((arrayOfBlocks at: 3) value) evaluates to 9. Here's the step-by-step explanation:

  1. The message collect: [:n | [n * n]] is sent to an array containing three elements: the numbers 1, 2 and 3.
  2. In response to the message in step 1, the receiving array constructs a new array of size 3. As the ith element of the new array, it stores the result of evaluating the message argument (the one-argument block [:n | [n * n]]) with its ith element as the argument to the block:
    1 to: self size do: [:i | newArray at: i put: ([:n | [n * n]] value: (self at: i))]
    .
  3. When the one-argument block [:n | [n * n]] is evaluated (in step 2) with an argument value of 3 (as happens in the case of the third element of the orginal array,) the value returned is yet another block: the no-argument block [n * n]. But isn't that the same block as in any of the other cases? No, it isn't. Each invocation of the outer block ([:n | [n * n]]) that encloses the inner block [n * n] creates a completely new execution context. And in each execution context, the binding of the formal block argument n is different. When the outer block ([:n | [n * n]]) is evaluated with the actual argument 3, the formal block argument n is bound to the value 3. So the block closure that is stored at index 3 of the new array has the lexical form [n * n], but the external reference it makes to n is bound to an execution context where n is a block argument whose value is 3.
  4. When returned as the result of the message sent in step 1, the array constructed in step 2 contains the block [n * n] as its first, second and third elements. But the identifier n in the block stored as the first element of the returned array is bound to an execution context where n is a reference to a formal block argument bound to the value 1. The identifier n in the block stored as the second element of the returned array is bound to an execution context where n is a reference to a formal block argument bound to the value 2. The identifier n in the block stored as the third element of the returned array is bound to an execution context where n is a reference to a formal block argument bound to the value 3.
  5. When the 3rd element of the array of blocks returned as the result of the message sent in step 1 is sent the message value, it answers the result of multiplying n by n. Since the identifier n in that particular closure is bound to an execution context where n is a reference to a formal block argument bound to the value 3, the answer it returns is 9.

Closures: Example 2

So what happens when a block that modifies a variable in a lexically-enclosing scope is returned as the value of an expression, and then executed? Consider the following method, which we'll assume is defined as a class method of the class Examples:

blockClosureExample
    | Variable aVariable accessor mutator values |
    values := Array new: 2.
    Variable :=
        [| value |
        [:selector |
        selector == #setValue:
            ifTrue: [[:newValue | value := newValue]]
            ifFalse:
                [selector == #getValue
                    ifTrue: [[value]]
                    ifFalse: []]]].
    aVariable := Variable value.
    accessor := aVariable value: #getValue.
    mutator := aVariable value: #setValue:.
    mutator value: 5.
    values at: 1 put: accessor value.
    mutator value: -20.
    values at: 2 put: accessor value.
    ^values

What value is returned when the expression Examples blockClosureExample is evaluated? The answer is the array #(5 -20). Here's the step-by-step explanation:

  1. An outer block containing an inner block is assigned to the variable named Variable (the reason for that name, and why it's capitalized, will be explained later.) The inner block itself contains yet more inner blocks.
  2. The variable named aVariable is assigned the result of sending the message #value to Variable. As a result, the value assigned to aVariable is the outermost inner block mentioned in step 1 (the block which lexically starts with [:selector | ....)
  3. Each separate execution of a block (or method) creates a new execution context. Each separate execution context creates a separate instance of each formal argument and local variable declared by the block (or method) that is being executed. So, since the outer block (referenced by Variable) declares a local variable named value, the execution of the outer block in step 2 creates an execution context which contains an instance of the local variable value.
  4. Since blocks are closures, the outermost inner block that is assigned to the variable aVariable in step 2 is bound to the execution context that was created when the message #value was sent to the outer block (referenced by the variable Variable.) So it is also bound to the instance of the local variable value that is specific to that execution context. Were the message #value sent to (the object referenced by) Variable again, it would answer a different closure bound to a different execution context containing a different instance of the local variable value.
  5. When the inner block that was assigned to the variable aVariable (in step 2) is sent the message #value: #getValue, the actual argument #getValue is assigned to the receiving block's formal block argument :selector for the execution of the block. As a result, the value returned, and assigned to the variable accessor, is the inner inner block [value]—a closure also bound the same execution context as is the one assigned to the variable aVariable (in step 2.)
  6. When the inner block that was assigned to the variable aVariable (in step 2) is sent the message #value: #setValue:, the actual argument #setValue: is assigned to the receiving block's formal block argument :selector for the execution of the block. As a result, the value returned, and assigned to the variable mutator, is the inner inner block [:newValue | value := newValue]—a closure also bound the same execution context as is the one assigned to the variable variable (in step 2.)
  7. When the message #value: 5 is sent to the variable mutator, the result is the execution of the block [:newValue | value := newValue] with its formal block argument (:newValue) assigned the value 5. As a result, the local variable value is assigned the value 5—even though the local variable value belongs to an execution context that was created for a block execution (in step 2) that has already terminated. This is the exception, mentioned in the section dealing with local variables, to the general rule that local variables cease to exist when the execution of the method or block where they are declared terminates.
  8. The value of the expression accessor value is assigned as the first element of the array referenced by the variable values. When the message #value is sent to the variable accessor, the result is the execution of the block [value]. As a result, the value of the local variable value is answered—which at that moment happens to be 5, due to the action in step 7.
  9. When the message #value: -20 is sent to the variable mutator, the result is the execution of the block [:newValue | value := newValue] with its formal block argument (:newValue) assigned the value -20. As a result, the local variable value is assigned the value -20.
  10. The value of the expression accessor value is assigned as the second element of the array referenced by the variable values. When the message #value is sent to the variable accessor, the result is the execution of the block [value]. As a result, the value of the local variable value is answered—which at that moment happens to be -20, due to the action in step 9.

Every evaluation of the expression Variable value (as defined in the example above) answers what is lexically the same inner block, but which is dynamically a different closure. Each closure answered by the evaluation of the expression Variable value is bound to the execution context specific to each distinct sending of the message #value to the outer block referenced by Variable. That means that each closure references a different set of local variables (and formal arguments, if any) defined by the outer block (or method.) In this case, there is only one such local variable: value.

Each distinct closure generated by the evaluation of the code defining a lexical block literal is analogous to a distinct intance of the same class: A class specifies a set of instance variables that each instance will have—but each instance encapuslates its own, distinct and private instance of each variable. A block (or method) specifies a set of formal arguments and variables that each closure dynamically generated from it will access—but each such closure is bound to its own, distinct and private instance of each of those variables (or formal arguments).

So the expression Variable value (as defined in the example above) is analogous to an expression that creates an instance of a class. That's why the outer block in the example above was assigned to a local variable named Variable (with a capital V): Each closure generated when (the block referenced by) Variable is sent the message #value acts like a distinct instance of a class that defines a single instance variable named "value." That's also why the result of the expression Variable value was assigned to the local variable aVariable: Conceptually, the closure (referenced by the variable) aVariable is an instance of the "class" Variable.

To complete the analogy between closures and instances of classes (based on the example above):

  • The statement accessor := aVariable value: #getValue is analogous to sending the message #getValue, resulting in a dynamic message dispatch operation where the "method" associated with that selector is found dynamically.
  • The statement mutator := aVariable value: #setValue: is analogous to sending the message #setValue:, resulting in a dynamic message dispatch operation where the "method" associated with that selector is found dynamically.
  • The expression accessor value is analogous to execution of the "method" whose selector is #getValue.
  • The expression mutator value: 5 is analogous to execution of the "method" whose selector is #setValue:—using an actual argument of 5.
  • The expression mutator value: -20 is analogous to execution of the "method" whose selector is #setValue:—using an actual argument of -20.

Defining Classes:

A Smalltalk class is a living object—it is not a static declaration in a text file. A Smalltalk class is not an object created only at "runtime" as an afterthought based on a declarative definition—it is an object that functions as its own specification, even while the program is being written! The object that is the class comes into existence as a result of sending messages as the programmer writes the code (usually, the development tools send the necessary messages, so the programmer does not have to do it directly.) In fact, the class object must exist first, before any instance variables can be defined or methods can be "declared." And that's why there is no distinction between "compile time" and "run time."

Because Smalltalk classes function as their own specifications (i.e., models,) Smalltalk is intrinsically based on model-driven engineering from design to execution, and from top to bottom. Smalltalk programs model themselves intrinsically, pervasively, comprehensively, completely and down to the core. Introspection/reflection arises as a natural property of the Smalltalk computational model—it does not need to be added as an "extra cost" afterthought. Deep introspection is a natural result of unifying "compile time" with "run time," specifications (models) with their instances, values with objects, and (last but not least) the base level and the meta-level.

Smalltalk programs are built dynamically and incrementally, by sending messages to live objects, which result in the creation of live objects which can themselves be sent messages. When you add, rename or remove the instance variables specified by a class, the effect is immediate: All the existing instances of the class are atomically modified right then and there. There is no waiting until the "program" is recompiled. Such is the value of using live objects as the governing specifications of your system. Smalltalk "programs" are not declaratively specified in flat, static text files!

The following example shows a message that defines (or more likely, redefines) a class named Point as a subclass of the class Object, and also specifies the class's instance variables and category:

Object subclass: #Point
	instanceVariableNames: 'x y'
	classVariableNames: ''
	poolDictionaries: ''
	category: 'Graphics-Primitives'
  

The category of a class has no affect on the behavior of the class or its instances, but does affect how the development environment organizes the class among all the other classes in the class library (in other words, it's an annotation for the benefit of prgrammers.) In the example above, the class is assigned to the category 'Graphics-Primitives'.

Based on the message sent to Object in the example above, instances of Point will have two instance variables, named x and y. Also, the class Point will not have any class variables. It also won't have any shared pool dictionaries (i.e., it doesn't import any namespaces.)

Here's a slightly more elaborate example showing a message that defines a subclass of Point named ThreeDPoint , which specifies that ThreeDPoint will have the instance variable z (in addition to x and y, which are inherited from its superclass, Point,) and has a class variable named Zero and imports a shared pool dictionary (namespace) named TrigConstants:

Point subclass: #ThreeDPoint
	instanceVariableNames: 'z'
	classVariableNames: 'Zero'
	poolDictionaries: 'TrigConstants'
	category: 'Graphics-Primitives'
  

To add an accessor method and a mutator method for ThreeDPont's instance variable z, we can send the following messages:

ThreeDPoint
    compile:
        'z
            "Answer the value of my instance variable z."

            ^z'
    classified: #accessing.

ThreeDPoint
    compile:
        'z: newValue
            "Set the value of my instance variable z to be that of the argument newValue."

            z := newValue'
    classified: #accessing.

The argument following the keyword #classified: specifies the category of the method being defined. As was the case with the category of a class, the "category" of a method (which is more usually referred in Smalltalk jargon as a method protocol) is simply an annotation to be used by the developement environment for organizing methods into groups, for the convenience of programmers.

Messages such as those in the examples above may be sent at any time. Programmers don't usually send such messages directly, however. The development tools do it for them. But when needed, programmers can and do "do it themselves."

Final Thoughts

Notation vs. Turing Equivalence: All programming languages are Turing Equivalent. So why does it matter which programming language one uses? It matters because Turing Equivalence only defines what can be computed, not how quickly or easily the program that performs the computation can be designed, coded, implemented, tested and deployed to production. If you don't think notation matters in that regard, then consider the difference between doing arithmetic using Roman Numerals versus using Arabic Numerals. Ever try doing long division using Roman Numerals?

There's a reason most programs aren't written in assembly language anymore. Differences in the expressive power of the programming notation used do matter.

Smalltalk as a Domain-Specific Meta- Language: Because almost all computation in Smalltalk is invoked and implemented by sending messages, and because the syntax of sending messages is the same regardless of when, where, why or by whom a message is first defined, Smalltalk syntax unifies the language, the class library and the applications written by programmers.

You can add your own control structures—and they will not only work just like any other control structures, they will look like them, too. You can add your own number types—and they will look and act just like those in the base language. You can add your own collection types—and those, too, will look and act just like those in the standard class library.

Smalltalk essentially eliminates any distinction between "built-in" and "programmer defined," and does so more thouroughly than just about any other language—Lisp and Forth would be the only well-known exceptions. Lisp syntax is uniformly based on prefix symmetry. Forth syntax is uniformly based on postfix symmetry. Smalltalk syntax is uniformly based on infix symmetry.

Writing a program in Smalltalk essentially defines a domain-specific language —which can then be recursively used by programmers to extend Smalltalk further, with ever richer domain-specific languages. Ultimately, the entire application becomes a single message send which executes a method whose code is written in a high-level domain specific language, and so on down the chain of abstractions until the primitive methods are reached.

It's all just messages, "all the way down."

Messages are Primary: In Smalltalk, messages are "the message" (with apologies to Marshall McLuhan.) Messages and their semantics are primary: Classes and methods only exist to satisfy the expected semantics of the messages that are sent, and not the other way around. That's why extreme programming and test-driven development both originated in Smalltalk: Both assume that implementation artifacts exist to satisfy the users of those artifacts, that classes and methods exist to satisfy the requirement specification(s) embodied by the application/business logic that uses them. That's why those methodologies have the programmer first write test code, and then implement classes and methods so that the test code satisfies the requirements implied by the messages sent by that code.

In extreme programming, in test-driven development, and in Smalltalk, it's the semantics of messages that matter, and that are primary. Classes and methods are written to conform to the expected semantics of the messages they will be sent—to appropriately respond to the messages sent by applications or business logic. If the code fails, it could just as easily be the fault of the implementation of the objects that are responding (or failing to respond) to the messages, as the fault of the programmer who sent the messages. The compiler is not smart enough to know which is the case—so why expect it to assume that responsibility?

From the Smalltalk perspective, it makes just as much sense for the development environment to prevent the programmer from sending whatever messages he desires as it would for the existing class library (of any programming language) to prevent business users from writing use cases as they wish. Imagine a "use case compiler" rejecting a statement in a use case because there's no such class or method in the existing class library. That would make no sense at all. To someone used to programming in Smalltalk, neither does static typing, and for the same reason: The programmer is the master, and the class library is his servant. The responsibility of the class library is to serve the needs of the programmer; satisfying the constraints of the class library is not the proper function of a programmer.

Of course, both use cases and methods can incorrectly specify system behavior. But static type checking cannot prevent that from happening, because it's not at all equivalent to declaratively specifying the desired semantics, so that the correct behavior can be computed by the system. The behavior of a program must be tested for correctness and acceptability in any case—and the faster the programmer can get the code ready for such testing, the better. Dynamic typing gets that done more effectively.

Without static typing on which to rely, Smalltalk programmers put much more focus on the semantics of messages. The Smalltalk programming culture puts great weight and importance on having well-defined, reliable message semantics. Informally using the semantics of messages as (near) invariants not only provides many of the same benefits as would static typing, it also provides some of the benefits that would ensue from declarative programming. Why? Because a body of code that sends messages (whose semantics are reliably defined) functions, in a sense, as a declarative specification of the desired semantics of a computation—a property of code on which test-driven development relies. Test cases validate that the messages have the expected semantics—which static typing cannot do.

With reliable message semantics (validated and enforced by test cases,) methods and classes become nothing more than implementation details—as they should be.

Smalltalk Syntax: Formal Specification

Below is presented the full and complete formal specification of the syntax (grammar) of ANSI-Standard Smalltalk, using a metalanguage known as Extended Backus-Naur Formalism (EBNF). The specific flavor of EBNF syntax used is as specified by the ISO International Standard for EBNF.

The EBNF grammar of Smalltalk is presented as a list of numbered production rules. In the preceding section, where actual code examples for each production rule in the grammar are presented and explained, the number (or numbers) of each production rule demonstrated in the example (or examples) is referenced. The production rule numbers have no other purpose or significance.

Note that there are only 67 production rules, that five of them simply define aliases (alternative names) solely for conceptual clarity, and that over half the production rules concern themselves with literal values, comments, identifiers and other low-level lexical constructs.

Formal EBNF Specification of Smalltalk Syntax

  1. Character = ? Any Unicode character ?;
  2. WhitespaceCharacter = ? Any space, newline or horizontal tab character ?;
  3. DecimalDigit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9";
  4. Letter = "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M"
                    | "N" | "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z"
                    | "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m"
                    | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z";
  5. CommentCharacter = Character - '"';
            (* Any character other than a double quote *)
  6. Comment = '"', {CommentCharacter}, '"';
  7. OptionalWhitespace = {WhitespaceCharacter | Comment};
  8. Whitespace = (WhitespaceCharacter | Comment), OptionalWhitespace;
  9. LetterOrDigit =
                    DecimalDigit
                    | Letter;
  10. Identifier = (Letter | "_"), {(LetterOrDigit | "_")};
  11. Reference = Identifier;
  12. ConstantReference =
                    "nil"
                    | "false"
                    | "true";
  13. PseudoVariableReference =
                    "self"
                    | "super"
                    | "thisContext";
            (* "thisContext" is not defined by the ANSI Standard, but is widely used anyway *)
  14. ReservedIdentifier =
                    PseudoVariableReference
                    | ConstantReference;
  15. BindableIdentifier = Identifier - ReservedIdentifier;
  16. UnaryMessageSelector = Identifier;
  17. Keyword = Identifier, ":";
  18. KeywordMessageSelector = Keyword, {Keyword};
  19. BinarySelectorChar = "~" | "!" | "@" | "%" | "&" | "*" | "-" | "+" | "=" | "|" | "\" | "<" | ">" | "," | "?" | "/";
  20. BinaryMessageSelector = BinarySelectorChar, [BinarySelectorChar];
  21. IntegerLiteral = ["-"], UnsignedIntegerLiteral;
  22. UnsignedIntegerLiteral =
                    DecimalIntegerLiteral
                    | Radix, "r", BaseNIntegerLiteral;
  23. DecimalIntegerLiteral = DecimalDigit, {DecimalDigit};
  24. Radix = DecimalIntegerLiteral;
  25. BaseNIntegerLiteral = LetterOrDigit, {LetterOrDigit};
  26. ScaledDecimalLiteral = ["-"], DecimalIntegerLiteral, [".", DecimalIntegerLiteral], "s", [DecimalIntegerLiteral];
  27. FloatingPointLiteral = ["-"], DecimalIntegerLiteral, (".", DecimalIntegerLiteral, [Exponent] | Exponent);
  28. Exponent = ("e" | "d" | "q"), [["-"], DecimalIntegerLiteral];
  29. CharacterLiteral = "$", Character;
  30. StringLiteral = "'", {StringLiteralCharacter | "''"}, "'";
            (* To embed a "'" character in a String literal, use two consecutive single quotes *)
  31. StringLiteralCharacter = Character - "'";
            (* Any character other than a single quote *)
  32. SymbolInArrayLiteral =
                    UnaryMessageSelector - ConstantReference
                    | KeywordMessageSelector
                    | BinaryMessageSelector;
  33. SymbolLiteral = "#", (SymbolInArrayLiteral | ConstantReference | StringLiteral);
  34. ArrayLiteral =
                    ObjectArrayLiteral
                    | ByteArrayLiteral;
  35. ObjectArrayLiteral = "#", NestedObjectArrayLiteral;
  36. NestedObjectArrayLiteral = "(", OptionalWhitespace, [LiteralArrayElement, {Whitespace, LiteralArrayElement}], OptionalWhitespace, ")";
  37. LiteralArrayElement =
                    Literal - BlockLiteral
                    | NestedObjectArrayLiteral
                    | SymbolInArrayLiteral
                    | ConstantReference;
  38. ByteArrayLiteral = "#[", OptionalWhitespace, [UnsignedIntegerLiteral, {Whitespace, UnsignedIntegerLiteral}], OptionalWhitespace,"]";
  39. (* The preceding production rules would usually be handled by the lexical analyzer;
         the following production rules would usually be handled by the parser
    *)

  40. FormalBlockArgumentDeclaration = ":", BindableIdentifier;
  41. FormalBlockArgumentDeclarationList = FormalBlockArgumentDeclaration, {Whitespace, FormalBlockArgumentDeclaration};
  42. BlockLiteral = "[", [OptionalWhitespace, FormalBlockArgumentDeclarationList, OptionalWhitespace, "|"], ExecutableCode, OptionalWhitespace, "]";
  43. Literal = ConstantReference
                    | IntegerLiteral
                    | ScaledDecimalLiteral
                    | FloatingPointLiteral
                    | CharacterLiteral
                    | StringLiteral
                    | SymbolLiteral
                    | ArrayLiteral
                    | BlockLiteral;
  44. NestedExpression = "(", Statement, OptionalWhitespace, ")";
  45. Operand =
                    Literal
                    | Reference
                    | NestedExpression;
  46. UnaryMessage = UnaryMessageSelector;
  47. UnaryMessageChain = {OptionalWhitespace, UnaryMessage};
  48. BinaryMessageOperand = Operand, UnaryMessageChain;
  49. BinaryMessage = BinaryMessageSelector, OptionalWhitespace, BinaryMessageOperand;
  50. BinaryMessageChain = {OptionalWhitespace, BinaryMessage};
  51. KeywordMessageArgument = BinaryMessageOperand, BinaryMessageChain;
  52. KeywordMessageSegment = Keyword, OptionalWhitespace, KeywordMessageArgument;
  53. KeywordMessage = KeywordMessageSegment, {OptionalWhitespace, KeywordMessageSegment};
  54. MessageChain =
                    UnaryMessage, UnaryMessageChain, BinaryMessageChain, [KeywordMessage]
                    | BinaryMessage, BinaryMessageChain, [KeywordMessage]
                    | KeywordMessage;
  55. CascadedMessage = ";", OptionalWhitespace, MessageChain;
  56. Expression = Operand, [OptionalWhitespace, MessageChain, {OptionalWhitespace, CascadedMessage}];
  57. AssignmentOperation = OptionalWhitespace, BindableIdentifier, OptionalWhitespace, ":=";
  58. Statement = {AssignmentOperation}, OptionalWhitespace, Expression;
  59. MethodReturnOperator = OptionalWhitespace, "^";
  60. FinalStatement = [MethodReturnOperator], Statement;
  61. LocalVariableDeclarationList = OptionalWhitespace, "|", OptionalWhitespace, [BindableIdentifier, {Whitespace, BindableIdentifier}], OptionalWhitespace, "|";
  62. ExecutableCode = [LocalVariableDeclarationList], [{Statement, OptionalWhitespace, "."}, FinalStatement, ["."]];
  63. UnaryMethodHeader = UnaryMessageSelector;
  64. BinaryMethodHeader = BinaryMessageSelector, OptionalWhitespace, BindableIdentifier;
  65. KeywordMethodHeaderSegment = Keyword, OptionalWhitespace, BindableIdentifier;
  66. KeywordMethodHeader = KeywordMethodHeaderSegment, {Whitespace, KeywordMethodHeaderSegment};
  67. MethodHeader =
                    UnaryMethodHeader
                    | BinaryMethodHeader
                    | KeywordMethodHeader;
  68. MethodDeclaration = OptionalWhiteSpace, MethodHeader, ExecutableCode;

To resolve any ambiguities that may arise due to the absence of optional whitespace, lower-numbered production rules take precedence over higher-numbered production rules. The ambiguity issue is normally taken care of by having production rules 1 through 38 handled by the lexical analyzer, but having the remainder (production rules 39 through 67) handled by the parser.

When compiling a method declaraion, MethodDeclaration is the grammatical start symbol. When compiling executable code that's not a method definition, ExecutableCode is the start symbol (it's possible, for example, to select a section of a method's code in a code browser and execute it, without invoking the method itself; the same is true of the text of code comments, if they happen to contain valid Smalltalk code.)

Resources & References

Implementations of Smalltalk

Books/Articles/Web Pages/Tutorials on Smalltalk

Promotion/Advocacy of Smalltalk

Applications/Libraries Using Smalltalk

© Copyright 2007 by Alan L. Lovejoy.
"Smalltalk: Getting The Message" is licensed under a Creative Commons Attribution 3.0 United States License.
Creative Commons License

You can vote on this article at dzone.com, reddit.com and digg.com:


Copyright 1999-2010 by Smalltalk.org™, All Rights Reserved.
Sunday
April 20 2014
Travel Adventure

Costa Rica

El Salvador

Guatemala

Honduras

Panama

Enjoy a tropical vacation in the sun, surf, in the jungle for adventure, yoga and massage for relaxation or learn the culture, history and langauge. Visit, live, work, play, retire. Enjoy.

Central America
Interoperate.org, the place to find out how to interoperate.
Google
Web
Smalltalk.org

64bits.net AMD64, Intel Pentium EM64T, Intel Itanium
Meet other Smalltalk people, contribute, learn, earn.
lingoize.me
On speaking languages
naturally with ease
aiming for fluid fluency.