To save content items to your account,
please confirm that you agree to abide by our usage policies.
If this is the first time you use this feature, you will be asked to authorise Cambridge Core to connect with your account.
Find out more about saving content to .
To save content items to your Kindle, first ensure no-reply@cambridge.org
is added to your Approved Personal Document E-mail List under your Personal Document Settings
on the Manage Your Content and Devices page of your Amazon account. Then enter the ‘name’ part
of your Kindle email address below.
Find out more about saving to your Kindle.
Note you can select to save to either the @free.kindle.com or @kindle.com variations.
‘@free.kindle.com’ emails are free but can only be saved to your device when it is connected to wi-fi.
‘@kindle.com’ emails can be delivered even when you are not connected to wi-fi, but note that service fees apply.
We define rational expressions, their star height and rational identities. Section 4.1 studies the rational identity E* ≡ 1 + EE* ≡ 1 + E*E and its consequences and the operators a−1E. In Section 4.2, we show that, over a commutative ring, rational identities are all consequences of the previous identities. In Section 4.3, we show that, over a field, star height may be characterized through some minimal representation, and deduce that the star height of the star of a generic matrix of order n is n. In the last section, we see that the star height may decrease under field extension and show how to compute the absolute star height, which is the star height over the algebraic closure of the ground field.
Rational expressions
Let K be a commutative semiring and let A be an alphabet. We define below the semiring of rational expressions on A over K. This semiring, denoted ε, is defined as the union of an increasing sequence of subsemirings εn for n ≥ 0. Each such subsemiring is of the form εn = K ⟨An⟩ for some (in general infinite) alphabet An; moreover, there will be a semiring morphism E ↦ (E, 1), εn → K. We call (E, 1) the constant term of the rational expression E.
Now A0 = A, ε0 = K ⟨A⟩ and the constant term is the usual constant term.
In this appendix, we show how to use both the Scala compiler (scalac) and the Scala interpreter (scala) by experimenting with their command line arguments. Part of our presentation is based on the man pages coming with every Scala distribution. For our exposition, we assume a Unix terminal.
Scala is a scalable language. Marketing-wise this is mentioned quite frequently. The good news is that Scala is indeed scalable in many ways and, after all, there is no harm in advertising features that already exist. One such dimension of scalability has to do with the provided tools and how they can be used to increase the overall experience of programming in Scala. We will see that the features provided give a pleasant feeling that the language “grows” to our needs.
For the following, we assume that Scala is installed under a folder denoted by the value of the environment variable SCALA_HOME. Under Unix, this value is obtained by $SCALA_HOME, while under Windows this is obtained by %SCALA_HOME%. It is good practice to set this variable, since other applications that use Scala may depend on it.
The Scala compiler
The compiler is the workhorse of the whole platform. Even the interpreter uses it internally in order to give the impression of a scripting environment. As expected, it is packed with a wealth of command line options. Using scalac with no options and parameters informs us of all the options. The outcome is given in Table C.1.
Scala is a scalable object-oriented programming language with features found in functional programming languages. Nowadays, the object-oriented approach to software construction is considered the most succesful methodology for software design, mainly because it makes software reuse extremely easy. On the other hand, functional programming offers some very elegant tools which when combined with an object-oriented program development philosophy define a really powerful programming methodology. Generally, any programming language that can be extended seamlessly is called scalable. When all these ideas are combined in a single tool, then the result is a particularly powerful programming language.
Object orientation
The first object-oriented programming language was SIMULA, which was designed and implemented by Ole-Johan Dahl and Kristen Nygaard. The SIMUlation LAnguage was designed “to facilitate formal description of the layout and rules of operation of systems with discrete events (changes of state).” In other words, SIMULA was designed as a simulation tool of discrete systems. Roughly, a simulation involves the representation of the functioning of one system or process by means of the functioning of another. In order to achieve its design goal, the designers equipped the language with structures that would make easy the correspondence between a software simulation and the physical system itself. The most important of these structures is the process. A process “is intended as an aid for decomposing a discrete event system into components, which are separately describable.”
Symbolic computation refers to the use of machines, such as computers, to manipulate mathematical content (i.e., equations, expressions, etc.) in symbolic formand deliver a result in this form. A classical example of such a systemis one that can compute the derivative of a function expressed in some symbolic form(i.e., sin(x)+1). In this chapter we will explain why symbolic computation is interesting and what it can achieve, and then we are going to present a simple system that can differentiate functions.
Mechanical symbol manipulation
In the beginning of the twentieth century, the great German mathematician David Hilbert asked whether it would be possible to devise a method to solve mechanically any diophantine equation, that is, any polynomial equation with integer coefficients. In other words, he asked whether it is possible to solve any diophantine equation by dully following a clerical procedure. In fact, Hilbert was dreaming of a fully mechanized mathematical science. Unfortunately for Hilbert it has been shown that one cannot construct a general algorithm to solve any diophantine equation. A consequence of this proof was that the dream of a fully mechanized mathematical science was not feasible. Nevertheless, certain problems of mathematics can be solved by purely mechanical methods. For example, it has been demonstrated that certain operations like differentiation and integration can be performed mechanically. In general, such systems are known as symbolic computation or computer algebra systems.
A parser is a piece of software capable of resolving a string into tokens and then checking whether the string belongs or not in a particular (formal) language. Constructing a parser from scratch is an interesting problem. However, a more interesting problem is that of constructing a particular parser from other (predefined?) parsers rather than from scratch. This problem can be solved by using parser builders. In the end, some of these parser builders have to be constructed from scratch, but all the complex parsers can be built from the other parser builders that parse components. Scala includes a rich library for building parsers using parser builders. In this chapter we first give an overview of some relevant notions, then we describe the library and finally we use this library to construct an interpreter for a simple programming language.
Language parsers
Given an alphabet (i.e., a set of symbols or characters), the closure of this alphabet is a set that has as elements all strings that consist of symbols drawn from this particular alphabet. For example, the digits 0 and 1 form an alphabet and the closure of this alphabet consists of “numbers” like 000, 101, 11, etc. A language can be considered a subset of the closure (including the empty string) of an alphabet. The language that is the closure of an alphabet is not particularly interesting since it is too large.
Most “real” programs have a graphical user interface (GUI for short). Therefore, any serious modern programming language should provide tools for GUI programming. Scala provides such tools through the scala.swing package, which is an interface to Java's JFC/Swing packages. The ambitious goal of this chapter is to teach you how to build GUI applications in Scala.
“Hello World!” again!
Typically, most introductory texts on programming are written without any coverage of GUI programming. In addition, advanced texts on programming cover GUI programming only as a marginal or optional topic. The truth is that the most “useful” applications have a graphical user interface that allows users to interact with the application. This implies that GUI programming is more common than programming textbooks “assume.” GUI programming is excluded by most texts because it is assumed that it is significantly harder than ordinary applications programming. Nevertheless, this is not true – it is true that GUI programming differs from conventional application programming, but being different does not make a methodology more difficult.
Creating simple GUI applications with Scala is relatively simple, however, one has to compile the source code of the application as it is not straightforward to create runnable GUI scripts. When compiling even a very simple GUI application, the Scala compiler will generate a number of .class files. This implies that if one wants to run this application, one needs to have all these files in a particular directory.
The expression problem, also known as the extensibility problem, refers to the situation where we need to extend the data types of a program as well as the operations on them, with two constraints: (a) we do not want to modify existing code and (b) we want to be able to resolve types statically. Thus, the essence of the expression problem lies in the type-safe incremental modification to both the data types and their corresponding operations, without recompilation and with the support/use of static typing.
At the heart of the expression problem is the Separation of Concerns principle. Since its inception about forty years ago by Edsger Wybe Dijkstra, the Separation of Concerns principle has been elevated to one of the cornerstones of software engineering. In plain words what it states is that when tackling a problem we have to identify the different concerns that apply to the specific problem and then try to separate them. By separating the concerns, we produce untangled, clearer code, thus reducing the software complexity and increasing maintainability.
Of course, separation of concerns is only half the truth. We can identify our concerns and successfully separate them, but at some point we will need to recombine them: after all, they are parts of the original problem.
So, what exactly do we separate and then recombine in the expression problem? Data and operations are two different dimensions. Incremental modifications to these dimensions should be done independently and in an extensible way.
Continuing our investigations on path territory in Chapter 8, the natural evolution is to touch some of the actual file abstraction. As a historical note, complete operating systems have been built based on this abstraction, Plan 9 being one that used it quite extensively. Plan 9 was designed by, among others, Ken Thompson and Rob Pike who also designed UTF-8. Throughout the book, we are using files as they are defined and implemented by a standard Java Development Kit distribution for an obvious reason: they use a pragmatic approach and at the same time make a very successful utility for everyday programming. In this chapter, we will try to tickle a few of our brian neurons around this design.
The java.io.file class of the Java platform already provides a file abstraction, via the java.io.File class. Despite the official online documentation, which states that File is
An abstract representation of file and directory pathnames,
the implementation plays the dual role of handling both generic paths and their physical counterparts in the native file system. We have already treated path representation and composition. The most common mapping of paths to underlying system resources is that related to native files.
So, we will develop here a small library for accessing files. But, instead of just providing a Scala wrapper around Java's File, we will abstract away common operations. The goal is for the design to accommodate not only the native file system but also a variety of other virtual file systems.
The constructs that have been presented in the previous chapter are enough for the creation of simple software systems. On the other hand, it is quite possible to create very complex software systems with these constructs, but the design and implementation processes will be really difficult. Fortunately, Scala provides many advanced features and constructs that facilitate programming as a mental activity. In this chapter we will describe most of these advanced features, while a few others like parsing combinators and actors will be presented thoroughly in later chapters.
Playing with trees
In the previous chapter we presented many important data types, but we did not mention trees, which forma group of data types that have many uses. Also, from the discussion so far, it is not clear whether Scala provides a facility for the construction of recursive data types, that is data types that are defined in terms of themselves. For example, a binary tree is a typical example of a recursively defined data structure that can be defined as follows.
Definition 1 Given the type node, a binary tree over the type node is defined in the following way.
It is quite probable that most of us are not consciously aware of an ever-appearing design pattern, which goes far beyond the design patterns in the normal sense of [24]. This pattern has to do with how we organize our data and, sometimes as a consequence, how we access these data. What we are talking about is the hierarchical data organization pattern that we can abbreviate in short as: Hierarchies are everywhere!
A file system is the canonical example of hierarchical organization. Its structure is a collection of files and directories, with the directories playing the role of containers for other files and/or directories. The Unix tradition has more to say about files, since the file system “pattern” has been extended to support other use cases than the traditional ones. For example, in Linux, /proc is a special mounted file system which can be used to view some kernel configuration and parameters. In fact, normal file system I/O calls can be used to write data into this special file system, so that kernel and driver parameters can be changed at runtime.
XML advocates will feel pleased to recognize that XML has been promoting such hierarchical organization. We are not sure how many of them were aware of the real essence of the general “Hierarchies are everywhere” pattern mentioned above, but the pattern itself is ubiquitous. Strangely enough, hierarchical databases have not survived, but probably XML strikes back on their behalf.