Programação Multiparadigma (2014/2015)



Teórica 03 (23/Set/2014)

All the elements of an infinite data structure must be accessible!
Controlling of the order of evaluation using non-strict evaluation.
Closures.
Implementing non-strict lists in Java.
The primitive non-strict mechanisms of Scala.
Reimplementing non-strict lists in Scala using call-by-need.
A sophisticated class for call-by-need lists in Scala.

All the elements of an infinite data structure must be accessible!

For example, consider the infinite list of the odd numbers and the infinite list of the even numbers: If you concatenate both lists, like this, you will get a new list where all the even numbers appear after the odd numbers. This means that if you want to reach the even numbers, you must first transverse all the odd numbers. But this amount to saying that the even numbers are not even there because they are beyond infinite!

In a computational setting, all the elements of an infinite data structure must be reachable. You will need to remember this whenever you build a data structure.

Usually, keeping the elements sorted will do the trick. For example, a suitable way of joining the lists zodd and zeven is by means of the function zmerge, like this:


Controlling of the order of evaluation using non-strict evaluation

This is the last application of non-strict evaluation we will study: defining new operations where we can control if and when should the parameter be evaluated.

In the following example, we pass the second argument in the form of a parameterless function to obtain non-strictness. This is the classic example of the cand operation (conditional-and):

Notice that the second parameters is evaluated only if necessary. See the following example of use: This technique works in any functional language, even a eager functional language like OCaml, for example.

Closures

The concept of closure is a sophisticated concept that lurks behind the seemingly simplicity of all functional languages.

Let us examine closely, again, the method zadd from the previous lesson:

Note the anonymous function that appears in the second branch of the match. This function depends on the non-local variables xs and n, so doubts may arise about what might happen when the function zadd returns because those variables seemingly will cease to exist. When the anonymous function is evaluated later, how will it obtain the correct values for xs and n?

What happens is that each function is internally represented by its code along with a representation of its surrounding environment. This surrounding environment will be kept alive as long as the anonymous function remains active. This pair of two elements <code, environment> is called a closure. Closures constitute a powerful mechanism that is available in functional languages.

The concept of closure was first implemented in the functional language Scheme, in the 1960s.



Implementing non-strict lists in Java

It is possible to implement non-strict lists in Java. The biggest problem is the representation of the delayed lists, because Java does not support functions. But a function can be represented using an object with a method inside and some features of full closures can be emulated in Java using anonymous classes and final variables and attributes. These two mechanisms were introduced in version 1.1 of Java in 1997 (Java 1.1).

Below, we define non-strict lists.

This is the type we will use instead of Unit=>ZList:

The type ZList is a sum type with two variants, ZNode and ZNil: Using Java 8, the two expressions of the form "new ZListDelayed() ..." can be replaced by lambda expressions. For example, the definition of "zints" becomes much more compact:

The primitive non-strict mechanisms of Scala

There are three primitive mechanism for direct support of non-strict evaluation in Scala:

def declarations [E:12]

A definition such as does not evaluate exp immediately. The expression exp will be re-evaluated, whenever x is used, or not evaluated at all if x is never used. So this definition behaves very much like a call-by-name parameter. Example:

lazy val declarations [E:109]

A definition such as lazy val x = exp does not evaluate exp immediately. The expression exp will be evaluated only once, the first time x is used, and then the result is stored in a cache for later use. So this definition behaves very much like a call-by-need parameter. Example:

call-by-name parameters [E:14]

Scala support regular call-by-name parameters, using parameter declarations of the form =>T. A call-by-name parameters is re-evaluated whenever the parameter is used, or not evaluated at all if he parameter is never used. Example: Another example:

Exercise: In this example, why do you need to pass the second parameter by-name?

Exercise: How do you simulate a call-by-need parameter, using the available non-strict mechanisms.


Reimplementing non-strict lists in Scala using call-by-need

Reimplementing non-strict lists using call-by-need will dramatically improve their efficiency in some cases.

The type of the ZLists do not change, but we will use the following auxiliary function that builds delayed lists that are evaluated using call-by-need instead of call-by-name:

The following method will as comes in handy for making ZNodes:

Examples

Using the slightly changed representation, here is how we reimplement our operations and constants:

Improved efficiency

The call-by-need implementation dramatically increases the efficiency in the following circumstances: For example, in the case of the circular definition of zints, the old technique gives rise of a solution with complexity O(n2) while the new technique gives rive to a solution with complexity O(n). To prove this, let us print all the add operations during the generation of values using the following auxiliary method: The output generated by the old call-by-need technique is the following: The output generated by the new call-by-need technique is the following: For reference, here is the code corresponding to the first experiment: Now, the code corresponding to the second experiment:

A sophisticated class for call-by-need lists in Scala

The following example shows how call-by-need lists can be nicely implemented in a modular way, using classes and singletons. This will be our final implementation of non-strict list in Scala.

This implementation is inspired by the implementation of lists in Scala: List.scala.



#15