C H A P T E R E I G H T Even More Functional Programming Programming Languages – Principles and...

24
C H A P T E R E I G H T C H A P T E R E I G H T Even More Functional Programming Programming Languages – Principles and Paradigms by Allen Tucker, Robert Noonan

Transcript of C H A P T E R E I G H T Even More Functional Programming Programming Languages – Principles and...

C H A P T E R E I G H TC H A P T E R E I G H T

Even More Functional Programming

Programming Languages – Principles and Paradigms by Allen Tucker, Robert Noonan

More on Haskell:

• The language is named for Haskell Brooks Curry, whose work in mathematical logic serves as a foundation for functional languages.

• Haskell is a modern, standard, non-strict, purely-functional programming language.

• It provides many features including polymorphic typing, lazy evaluation and higher-order functions. It also has an innovative type system which supports a systematic form of overloading and a module system.

• Haskell has an expressive syntax, and a rich variety of built-in data types, including arbitrary-precision integers and rationals, as well as the more conventional integer, floating-point and boolean types.

Values, Types, and Other Goodies:

• Because Haskell is a purely functional language, all computations are done via the evaluation of expressions (syntactic terms) to yield values (abstract entities that we regard as answers).

• Every value has an associated type. (Intuitively, we can think of types as sets of values.)

• Examples of expressions include atomic values such as the integer 5, the character 'a', and the function \x -> x+1, as well as structured values such as the list [1,2,3] and the pair ('b',4).

Values, Types, and Other Goodies:

• Just as expressions denote values, type expressions are syntactic terms that denote type values (or just types).

• Examples of type expressions include the atomic types Integer (infinite-precision integers), Char (characters), and Integer->Integer (functions mapping Integer to Integer).

• There are also structured types such as [Integer] (homogeneous lists of integers) and (Char,Integer) (character, integer pairs).

Values, Types, and Other Goodies:

• All Haskell values are "first-class"---they may be passed as arguments to functions, returned as results, placed in data structures, etc.

• Haskell types, on the other hand, are not first-class. Types in a sense describe values, and the association of a value with its type is called a typing.

• Using the examples of values and types above, we write typings as follows:

                           5  :: Integer                          'a' :: Char                          inc :: Integer -> Integer                     [1,2,3] :: [Integer]                      ('b',4) :: (Char,Integer)

Functions and Types:

• Functions in Haskell are normally defined by a series of equations. For example, the function inc can be defined by the single equation:

inc n          = n+1

• An equation is an example of a declaration. Another kind of declaration is a type signature declaration, with which we can declare an explicit typing for inc:

inc            :: Integer -> Integer

Functions and Types:

• Haskell's static type system defines the formal relationship between types and values.

• The static type system ensures that Haskell programs are type safe; that is, that the programmer has not mismatched types in some way.

• The type system also ensures that user-supplied type signatures are correct.

• Haskell's type system is powerful enough to allow us to avoid writing any type signatures at all; we say that the type system infers the correct types for us.

• Nevertheless, judicious placement of type signatures such as that we gave for inc is a good idea, since type signatures are a very effective form of documentation and help bring programming errors to light.

Polymorphic Types:

• Haskell also incorporates polymorphic types---types that are universally quantified in some way over all types.

• Polymorphic type expressions essentially describe families of types. For example, (forall a)[a] is the family of types consisting of, for every type a, the type of lists of a.

• Lists of integers (e.g. [1,2,3]), lists of characters (['a','b','c']), even lists of lists of integers, etc., are all members of this family.

• Note, however, that [2,'b'] is not a valid example, since there is no single type that contains both 2 and 'b'.

Defining Functions:

• Here is an example of a user-defined function that operates on lists, counting the number of elements in a list:

length                   :: [a] -> Integer

length []                =  0

length (x:xs)         =  1 + length xs

• This example highlights an important aspect of Haskell that is yet to be explained: pattern matching. The left-hand sides of the equations contain patterns such as [] and x:xs.

• If the match succeeds, the right-hand side is evaluated and returned as the result of the application. If it fails, the next equation is tried. If all equations fail, an error results.

Polymorphic Functions:

• The length function is an example of a polymorphic function. It can be applied to a list containing elements of any type, for example [Integer], [Char], or [[Integer]].

length [1,2,3] => 3

length ['a','b','c'] => 3

length [[1],[2],[3]] => 3

• Here are two polymorphic functions on lists:head                    :: [a] -> ahead (x:xs)          =  x

tail                     :: [a] -> [a]tail (x:xs)             =  xs

• Unlike length, these functions are not defined for all possible values of their argument. A runtime error occurs when these functions are applied to an empty list.

Recursive Types:

Types can also be recursive, as in the type of binary trees: data Tree a  = Leaf a | Branch (Tree a) (Tree a) 

Here we have defined a polymorphic binary tree type whose elements are either leaf nodes containing a value of type a, or internal nodes ("branches") containing (recursively) two sub-trees.

Tree is a type constructor, whereas Branch and Leaf are data constructors. Aside from establishing a connection between these constructors, the above declaration is essentially defining the following types for Branch and Leaf:

Branch    :: Tree a -> Tree a -> Tree aLeaf        :: a -> Tree a

List Comprehensions and Arithmetic Sequences:

As with Lisp dialects, lists are pervasive in Haskell, and as with other functional languages, there is yet more syntactic sugar to aid in their creation. Haskell provides an expression known as a list comprehension that is best explained by example:

[ f x | x <- xs ]

This expression can intuitively be read as "the list of all f x such that x is drawn from xs." The similarity to set notation is not a coincidence.

List Comprehensions and Arithmetic Sequences:

The phrase x <- xs is called a generator, of which more than one is allowed, as in:

[ (x,y) | x <- xs, y <- ys ]

This list comprehension forms the cartesian product of the two lists xs and ys. The elements are selected as if the generators were "nested" from left to right (with the rightmost generator varying fastest);

thus, if xs is [1,2] and ys is [3,4], the result is [(1,3),(1,4),(2,3),(2,4)].

"Infinite" Data Structures:

Non-strict constructors permit the definition of (conceptually) infinite data structures. Here is an infinite list of ones:

ones                    = 1 : ones

Perhaps more interesting is the function numsFrom:

numsFrom n         = n : numsFrom (n+1)

Thus numsFrom n is the infinite list of successive integers beginning with n. From it we can construct an infinite list of squares:

squares                 = map (^2) (numsFrom 0)

"Infinite" Data Structures:

• Of course, eventually we expect to extract some finite portion of the list for actual computation, and there are lots of predefined functions in Haskell that do this sort of thing: take, takeWhile, filter, and others.

• The definition of Haskell includes a large set of built-in functions and types---this is called the "Standard Prelude". See the language definition for full details.

• For example, take removes the first n elements from a list:

take 5 squares => [0,1,4,9,16]

Circularity:

• In many circumstances laziness has an important impact on efficiency, since sequences can be implemented as a circular list structures, thus saving space.

• For another example of the use of circularity, the Fibonacci sequence can be computed efficiently as the following infinite sequence:

fib  = 1 : 1 : [ a+b | (a,b) <- zip fib (tail fib) ]

•where zip is a Standard Prelude function that returns the pairwise interleaving of its two list arguments:

zip (x:xs) (y:ys)       = (x,y) : zip xs ys

zip  xs     ys          = []

Circularity:

Note how fib, an infinite list, is defined in terms of itself, as if it were "chasing its tail." Indeed, we can draw a picture of this computation as shown below:

Functional Wrap Up:

• Brevity -- Functional programs tend to be much more concise than their imperative counterparts. Quicksort is a rather extreme case, but in general functional programs are much shorter (two to ten times).

• Ease of understanding -- Functional programs are often easier to understand even without any previous knowledge of, say, Haskell. (but what about Scheme?)

• Strongly Typed -- Most functional languages, and Haskell in particular, are strongly typed, eliminating a huge class of easy-to-make errors at compile time. In particular, strong typing means no core dumps! There is simply no possibility of treating an integer as a pointer, or following a null pointer.

Functional Wrap Up:

• Code re-use -- Haskell's type system is much less restrictive than, say, Pascal's, because it uses polymorphism. For example, the qsort program will not only sort lists of integers, but also lists of floating point numbers, lists of characters, lists of lists; indeed, it will sort lists of anything which can be compared by the less-than and greater-than operations.

• Strong glue -- Non-strict functional languages only evaluate as much of the program as is required to get the answer – this is called lazy evaluation.

• Powerful abstractions -- In general, functional languages offer powerful new ways to encapsulate abstractions. One powerful abstraction mechanism available in functional languages is the higher-order function.

Functional Wrap Up:

• Built-in memory management -- Very many sophisticated programs need to allocate dynamic memory from a heap. In C this is done with a call to malloc, followed by code to initialize the store just allocated. The programmer is responsible for returning the store to the free pool when it isn't needed any more, a notorious source of "dangling-pointer" errors. Every functional language relieves the programmer of this storage management burden.

• Anything else you can think of?

When C/C++ is better:

• Functional Programs tend to use a lot of memory. The C quicksort uses an extremely ingenious technique, invented by Hoare, whereby it sorts the array in place; that is, without using any extra storage.

• In contrast, the Haskell program allocates quite a lot of extra memory behind the scenes, and runs rather slower than the C program.

• In applications where performance is required at any cost an imperative language like C would probably be a better choice than Haskell.

• This is because imperative languages provide more intimate control over the exact way in which the computation is carried out.

Functional vs. imperative:

• Few programs require performance at any cost! After all, we all stopped writing assembly-language programs, except perhaps for key inner loops, long ago.

• The benefits of having a more supportive programming model far outweigh the modest run-time costs.

• Functional languages take another large step towards a higher-level programming model. Programs are easier to design, write and maintain, but the language offers the programmer less control over the machine.

• For many programs the result is perfectly acceptable.

Does Anyone Use Functional Programming?

• Software AG, a major German software company, market an expert system (Natural Expert) which is programmed in a functional language.

• Ericsson have developed a new functional language, Erlang, to use in their future telephony applications. They have already written 130k-line Erlang applications, and find them very much shorter and faster to develop.

• Amoco ran an experiment in which they re-coded in a functional language a substantial fraction of their main oil-reservoir simulation code. The resulting program was vastly shorter, and its production revealed a number of errors in the existing software.

•Researchers at Durham University used a functional language in a seven-year project to build LOLITA, a 30,000-line program for natural-language understanding.

• Query is the query language of the O2 object-oriented database system. O2Query is probably the most sophisticated commercially-available object-oriented database query language – and it is a functional language.

• ICAD Inc market a CAD system for mechanical and aeronautical engineers. The language in which the engineers describe their design is functional, and it uses lazy evaluation extensively to avoid recomputing parts of the design which are not currently visible on the screen.

Next time…

Logic Programming