More Oz Basics

About this document
— More Oz Basics
Introduction
Pattern Matching
Introduction
Case (head and tail)
Case (every element)
Multiple case clauses
Implicit pattern matching
Procedures over Compounds
Introduction
Record HasFeature
Record arity
Combining records
Accessing a list element
Reverse list
Append lists
User-defined procedures
Higher-Order Programming
Introduction
Filter
ForAll
Map
Zip
Anonymous procedures
Sort (simple)
Sort (advanced)
Defining higher-order procedures
BACKGROUND
Unification
Introduction
Unifying two numbers
Unifying two lists
Recursive unification
BACKGROUND
Object-Oriented Programming
Introduction
Method call
Method arguments
Nesting marker $
Class definition
BACKGROUND
Concurrency
Introduction
Variables (single thread)
Variables (multiple threads)
Blocked programs
Detecting blocked progs (1)
Detecting blocked progs (2)

About this document

This file was automatically generated from the interactive Strasheela tutorial. Some aspects of the text only make sense in the original interactive tutorial application (e.g., buttons indicated to press, and positions specified on the screen), and not in this version of the text.

— More Oz Basics

Introduction

This chapter covers more basic info about Oz. However, despite still being "basic" information, it requires a solid understanding of material in the previous chapter.

Pattern Matching

Introduction

Pattern matching is a convenient way to access the elements contained in compund data types (i.e. records, lists, etc). Pattern matching decomposes such compund data, declares new variables, and binds these variables to parts of the compound data.

The primary pattern matching construct is the `case' statement, but some programming constructs (such as functions) allow pattern matching via an implicit case statement.

Case (head and tail)

In the section below, `case' declares the two variables H and T and binds them to the head and tail of the list Xs. Finally, the H and T are inspected. Please note that the pattern-matching expression H|T is written with the usual list syntax using the cons-operation (|).

This approach can be used to pattern match lists of arbitrary length, except empty lists (i.e. nil). Try changing the definition of Xs to a list of a different length and watch how the value of T changes.

local
   Xs = [1 2 3 4]
in
   case Xs of H | T
   then
      {Inspect H}
      {Inspect T}
   end
end

Case (every element)

The previous section matched the head and the tail of a list. Instead, we can also match individual list elements (or the elements of other data structures, such as records or tuples).

WARNING: if the length of the lists is not equal, an error occurs.

local
   Xs = [1 2 3 4]
in
   case Xs of [A B C D]
   then
      {Inspect A}
      {Inspect B}
      {Inspect C}
      {Inspect D}
   end
end

Multiple case clauses

Having only a single clause to match against (as in the previous sections), can be too restrictive. This section presents a typical `case' expression. It matches a list, which checks whether Xs is either a non-empty list, an empty list, or some other value. Additional case-clauses are introduced with the keyword `[]'. You may want to play around with the value of Xs to try out these different clauses.

local
   Xs = [1 2 3 4]
in
   case Xs of H|T then {Inspect nonEmptyList}
   [] nil then {Inspect emptyList}
   else {Inspect notList}
   end
end

Implicit pattern matching

The following function GetX expects a record as argument; this record must match test(x:X ...). The variable X is implicitly declared and bound to the value at the feature 'x' of the record given as argument to the function.

Please note that the record in the header of the function GetX is not even complete but contains three dots (...) to indicate that further record features are possible.

local
   R = test(x:value1 y:value2)
   fun {GetX test(x:X ...)} X end
in
   %% show the normal record
   {Inspect R}
   %% show the feature we want
   {Inspect {GetX R}}
end

Procedures over Compounds

Introduction

Oz provides a rich set of procedures for processing compound data such as lists and records. A few examples are shown here. More procedures are listed in the reference documentation at

http://www.mozart-oz.org/documentation/base/index.html

Record HasFeature

Test whether a record has a certain feature.

{Inspect {HasFeature unit(x:1 y:2 z:3) y}}

Record arity

Return the features of a record as a list.

{Inspect {Arity unit(a b x:1 y:2 z:3)}}

Combining records

"Merge" two records. Note that features and labels of the second record take precedence over the first.

{Inspect {Adjoin unit(x:1 y:2 z:3) test(foo:hi bar:there z:'overwrite!')}}

Accessing a list element

Return the nth element of a list. Note that the list is 1-indexed.

{Inspect {Nth [a b c d] 2}}

Reverse list

Reverse a list.

{Inspect {Reverse [a b c d]}}

Append lists

Append two lists.

{Inspect {Append [a b] [x y]}}

User-defined procedures

You can define procedures over lists and records as easily as numeric procedures. Following is the definition of the function Append.

local
  fun {Append Xs Ys}
    if Xs == nil then Ys
    else Xs.1 | {Append Xs.2 Ys}
    end
  end
in
  {Inspect {Append [a b] [x y]}}
end

Higher-Order Programming

Introduction

Oz procedures (and functions) are first-class values. This means that a procedure can be processed like any other value. For example, procedures can be given to other procedures as arguments.

This leads to highly flexible programming technique called higher-order programming. Procedures expecting procedures as arguments are called higher-order procedures. This concept is demonstrated be several sections.

Filter

The function Filter expects a list and a test function, and returns only those elements for which the test function returns true.

The function IsEven returns true for even integers and thus a list with only the even integers in [~4 ~3 ~2 ~1 0 1 2 3] is returned. Try replaying IsEven by IsOdd, IsNumber or IsNat (testing for natural numbers) to better understand this filtering.

(BTW: there is a bug in IsOdd concerning negative numbers)

{Browse {Filter [~4 ~3 ~2 ~1 0 1 2 3] IsEven}}

ForAll

The procedure ForAll applies a given procedure to any element of a list. In this section, the procedure Browse is applied to every list element.

{ForAll [a b c d e f] Browse}

Map

The function Map expects a list and a unary function (i.e. a function expecting a single value) as arguments. It applies the function to every list element, and returns the collected results in a list.

The section defines and uses the function square in order to square all numbers in the list. You may want to change this function to understand that any function can be given to a higher-order function as an argument. For example, replace Square by a function Double, which doubles its argument.

local
  fun {Square X} X * X end
in
  {Browse {Map [1 2 3 4 5 6] Square}}
end

Zip

Zip applies a procedure to elements from two lists. The lists must have the same size.

local
  proc {Max X Y Z}
     if X >= Y then Z = X else Z = Y end
  end
  A = [1 2 3 4 5]
  B = [5 4 3 2 1]
in
  {Browse {List.zip A B Max}}
end

Anonymous procedures

Sometimes we need a function only once — as the function Square in the previous section. In such cases we don't necessarily need to care about giving the function any name. Instead, we can define an anonymous function.

This section restates the previous section by defining the Square function 'inline' without giving it any name. In an anonymous function definition, the variable which names the function is replaced by $ (dollar sign).

{Browse
 {Map [1 2 3 4 5 6] fun {$ X} X * X end}}

Sort (simple)

The function Sort expects a list and a binary function (i.e. a function expecting two values) as arguments. This binary function compares two values, and Sort sorts the list values according to this comparison. For example, the function in the section compares two numbers and returns true if the first number is smaller. Consequently, this section sorts the list elements in ascending order. You may want to replace the < by > in the function definition to sort the numbers in decreasing order.

{Browse
 {Sort [1 5 3 2 0 7]
  fun {$ X Y} X < Y end}}

Sort (advanced)

You can actually sort the list elements in any way you want using the Sort function. For example, you may place all even numbers at the beginning and all odd numbers at the end of the list and sort all even and odd numbers in ascending order. This is done in the second (commented) Sort call. How does this sorting work?

{Browse
 {Sort
  [1 5 3 2 0 7]
  fun {$ X Y}
    if {IsEven X} then
      if {IsEven Y} then X < Y
      else true
      end
    else
      if {IsOdd Y} then X < Y
      else false
      end
    end
  end}}

Defining higher-order procedures

Higher order procedures are defined like any other procedure: some arguments are simply procedures — which are then usually applied in the definition. This section defines a higher-order function Find which expects a list Xs and a test function Fn: Find returns the first element in Xs for which Fn returns true.

This section also demonstrates the pattern-matching case statement with multiple clauses operating on the list Xs. In case Xs is the empty list nil, then Find returns nil. Otherwise (multiple clauses are separated with the keyword []), Xs is matched with X|Xr, where X is bound to the first element of Xs and Xr to the list's tail or rest. The function Find then checks whether {Fn X} returns true. In that case, the searched for list element has been found and is returned. Otherwise, Find is called recursively with the rest of the list.

local
   fun {Find Xs Fn}
      case Xs
      of nil then nil
      [] X|Xr
      then if {Fn X} then X
           else {Find Xr Fn}
           end
      end
   end
in
   {Browse {Find [1 2 3 4 5 6] IsEven}}
end

BACKGROUND

Terms: first-class value, first-class function/procedure, higher-order function/procedure.

A first-class value is a value (also object, or citizen) which can be stored in data structures, given to procedures as arguments and returned as results. Functions which are first-class values are also called first-class functions.

First-class functions are a central concept of functional programming. They allow for higher-order programming, which make procedures more general and in effect can greatly reduce the length of a program. For example, instead of defining a sort function for every kind of data you need to sort (e.g., numbers, strings, score objects) you only define a single sort function and specify how it compares values.

Reading:

Unification

Introduction

The operator = performs unification of two variables. The variables share all the information they have about their values. A variable without a name (an anonymous variable) is written as an underscore ( _ ).

Unifying two numbers

Unification shares information between two variables, including the domain of possible values. This example introduces finite domain integers (FD), discussed below in more detail.

local
  X = {FD.int 1#5} % declare an integer with domain {1, ..., 5}
  Y = {FD.int 0#3}
in
  X = Y          % unify X and Y
  {Inspect X}    % X now has the domain {1, ..., 3}
end

Unifying two lists

Compound data structures such as lists may contain free variables. Such lists are then partially bound and they can be unified.

local
  X = [a _ _]
  Y = [_ b _]
in
  X = Y          % unify X and Y
  {Inspect X}
end

Recursive unification

Unification also works recursively. The Inspector and Browser windows show two different ways for representing this.

The Inspector can be configured to show either way: under the options menu, structure tab, below representation, select between tree and relation mode.

local X = unit(x:X) in
  {Browse X}
  {Inspect X}
end

BACKGROUND

Terms: logic variables, unification.

Unification is the core operation of logic programming (besides search). Variables which allow for unification are therefore also called "logic variables". Unification is also the first constraint we meet. Constraint programming is discussed further below.

Reading:

Object-Oriented Programming

Introduction

Oz supports object-oriented programming (OOP). This programming paradigm introduces the notion of objects. Objects encapsulate internal data and understand methods (or messages) which somehow process this internal data. Different 'kinds' of objects are defined by defining classes: objects are class instances. A class specifies what data are contained in its instances and what methods these instances understand. A method is effectively a procedure which is defined for instances of specific classes only.

Method call

The following section creates a graphical user interface button. You do not need to understand the code which creates the window itself (i.e., the call to QTk.build). For our purposes here, only this single line is important:

{Window show}

`Window' is an object, and `show' is the name of the method understood by this object. This method results in showing the window with the button.

Please note that the syntax of a method differs clearly from the procedure syntax shown before. If `show' was a procedure, then we would write:

{Show Window}

Internally, objects are actually procedures which expect a single argument — hence this syntax. When the object is sent a message (i.e. the procedure is called with a specific argument) it processes the message according to its definition.

local
   Window = {QTk.build lr(button(text:"Hello world!"
                                 action:toplevel#close))}
in
   {Window show}
end

Method arguments

Class methods are actually records which can contain method arguments. For example, the following statement sends the following message to the object Window. This changes the width of the border around the button and sets the background of this border to the color blue.

{Window set(borderwidth:1.0#c background:blue)}

In general, the record denoting a message can wrap multiple arguments, as in the following example where the method myMethod with multiple arguments is send to the class MyObject.

{MyObject myMethod(Arg1 Arg2 ...)}

We will later see many more method application sections in the context of Strasheela's music representation.

local
   Window = {QTk.build lr(button(text:"Hello world!"
                                 action:toplevel#close))}
in
   {Window show}
   %% change to button background color after 1000 msec
   {Delay 1000}
   {Window set(borderwidth:1.0#c
               background:blue)}
end

Nesting marker $

Oz provides a special construct for marking return values. This construct is the nesting marker which is notated $ (dollar sign). In a previous example (repeated below), the nesting marker was used for defining an anonymous function. In this case, $ returns the function value itself.

{Map [1 2 3 4 5 6] fun {$ X} X * X end}

More generally, the nesting marker transforms any statement into an expression. For example, method calls are statements by default — they return nothing. The nesting marker can turn methods into expressions. Using the nesting marker can make code more concise. Instead of writing

local X in {Obj get(X)} {Browse X} end

we can write

{Browse {Obj get($)}}

The following example creates a text entry widget. Whenever you edit the widget text, it is (re-)browsed. Again it is not important to understand the full example; just note the code segment which does the browsing (see below). Entry is the entry widget object, and the method get(X) binds its argument X to the present widget text (as string).

{Browse {String.toAtom {Entry get($)}}}

local
   Entry
   Window = {QTk.build
             lr(entry(init:"Change this text..."
                      handle:Entry
                      action:proc{$} {Browse {String.toAtom {Entry get($)}}} end))}
in
   {Window show}
end

Class definition

This example defines a simple counter class. It stores internally a counter value in its attribute i. This value is accessed with the method get, and the method incr raises the counter value by one. Every class defines an initialisation method: the method init expects the initial counter value as argument. Note that the syntax for accessing and overwriting attribute values is very similar the the syntax of cells (see above). In fact, object attributes are also stateful data, like cells.

The example then creates a counter object (MyCounter) with the function New, which expects a class and an initialisation method. The rest of the example uses this object. Its initial counter value 3 is first browsed. The object is then incremented (so the counter becomes 4), and then its value is browsed again. Note the use of the nesting marking for returning the counter value.

local
   class Counter
      attr i                    % define attribute i
      meth init(I) i := I end   % bind i to arg I
      meth get(I) I=@i end      % return i
      meth incr i := @i+1 end   % increment i by one
   end
   MyCounter = {New Counter init(3)}
in
   {Browse {MyCounter get($)}}
   {MyCounter incr}
   {Browse {MyCounter get($)}}
end

BACKGROUND

Terms: class, object (class instance), method

Strasheela's music representation makes extensive use of object-oriented programming. Even basic Strasheela programs commonly require writing method calls. Extending Strasheela often means writing new Strasheela classes.

Nevertheless, object-oriented programming is a complex programming paradigm. This section therefore presented only the very basics of this paradigm. For more details, please refer to other Oz documentation:

Reading:

Please note that object-oriented programming is often used as a stateful programming paradigm: methods often change the internal state of objects (cf. the counter example above). By constrast, most methods in Strasheela are stateless which is important in the context of constraint programming. The Strasheela music representation is introduced later in this tutorial.

Concurrency

Introduction

Oz provides excellent support for concurrent programming, where computations run in parallel in multiple threads. We will only touch on this subject and discuss aspects relevant for Strasheela. In general, however, concurrent programming plays a major role in Oz programming.

The computations in different threads can communicate with each other via variables. Multiple threads can use the same variable in a computation. If the value of a variable does not present enough information for performing a specific operation, then the thread simply blocks and waits for more information. As soon as more information about the variable value is available, the thread resumes its execution. This approach makes concurrent programs highly declarative and thus easy to write and understand.

The downside of this concurrency model is that it can result in an unintended blocking of a program which is not explicitly signalled (e.g. no error message is shown when a program blocks, because this is a normal program behaviour). There are a few pragmatic tricks we can use to avoid this downside; these are covered later in this chapter.

This section demonstrates concurrent programming, but does not show a typical application (a typical application would be a program split in a server and one or more clients). In the context of Strasheela, we will seldomly write concurrent programs explicitly. Nevertheless, it is very important to know how concurrent programming works in Oz. Even if we are not explicitly writing a concurrent program, constraint programming in Oz always results in a concurrent program. Concurrent programming forms one of the foundations of Oz' constraint programming model, where each constraint (i.e. each propagator) is a concurrent agent running in its own thread.

Variables (single thread)

Variables may be used to communicate information between different parts of a program — even if the information is not available yet. Browse can handle unknown information, but other parts of the program may wait (i.e. block) until the information is available.

Here, `IsEven' binds the variable B. Please note the order of computations in this section:

  1. B is browsed.
  2. IsEven binds B to false.

However, `Browse' does indeed show the correct value of B. WARNING: not all procedures can handle unknown information.

local
  B
in
  {Browse B}
  {Delay 3000}
  {IsEven 4 B}
end

Variables (multiple threads)

Variables may also be used to communicate between different threads. This example declares X and then browses 'hello' (just to show that the browser works in principle). However, the addition X+3 cannot be executed immediately and blocks. Because this computation is executed in its own thread, the top-level thread continues regardless, and calls the procedure Delay, which waits for 3000 msec. After that time, the top-level thread determines X to 4. This awakes the other thread: it can now compute X+3 and browse the sum.

In the section below, the addition X+3 cannot be performed as long as the value of X is unknown.

local
  X
in
  {Browse hello}
  thread {Browse X + 3} end  % compute and browse X+3 concurrently
  {Delay 3000}
  X = 4
end

Blocked programs

This section demonstrates a buggy program which does not signal any error but simply does nothing. The section is very similar to the previous section, but does not place the blocking X+3 in its own thread. As a result, the whole program blocks at that point and never executes X = 4.

local
  X
in
  {Browse hello}
  %% !! blocks
  {Browse X+3}
  X = 4
end

Detecting blocked progs (1)

This section demonstrates a pragmatic approach which checks for blocking programs. The section ends with the statement {Browse endOfProgram}. A non-blocking program will always execute this last line of code and show 'endOfProgram' in the Browser. However, a blocking program (as the present one) does not do that and thus indicates that it is blocking. Although this little trick does not tell us where the program blocks, the information that we wrote a blocking program can prove very helpful already. You may get a feel for this trick by changing the section so that the message 'endOfProgram' is shown (e.g. comment the blocking statement out, or surrounding it with a 'thread .. end' statement).

Of course, this technique is most effective for programs which should complete immediately. By contrast, if we call procedures like Delay in a program (see above), the browsing of 'endOfProgram' will also be delayed accordingly.

local
  X
in
  {Browse hello}
  %% !! blocks
  {Browse X+3}
  X = 4
end
{Browse endOfProgram}

Detecting blocked progs (2)

This section demonstrates a technique which causes an error (raises an exception) when the current thread blocks. The function `Thread.this' returns the current thread, and `Debug.setRaiseOnBlock' activates this debugging feature. The error message can even tell you which variable caused the blocking (although in this section the deduced variable name is simply _).

Note that you must load Debug first, before using `Debug.setRaiseOnBlock' (which this tutorial does implicitly). For example, include the following line at the beginning of your program.

declare [Debug] = {Module.link ['x-oz://boot/Debug']}

local
  X
in
  {Debug.setRaiseOnBlock {Thread.this} true}
  %% !! blocks
  {Browse X+3}
  X = 4
end