COS 333: Chapter 15, Part 3

Willem van Heerden
1 Sept 202055:48

Summary

TLDRThis lecture concludes the chapter on functional programming languages, focusing on Scheme and its list operations, numeric predicate functions, and control flow. It delves into recursive function implementations, tail recursion, and the efficiency benefits of converting non-tail recursive functions. The power of first-class functions is demonstrated through 'map' and 'eval', and a brief comparison with Common Lisp is provided. The lecture also touches on real-world applications of functional languages, particularly in AI, and contrasts functional with imperative programming, highlighting their respective advantages.

Takeaways

  • 📚 The lecture series concludes with a discussion on functional programming languages, focusing on Scheme and its capabilities for list operations and recursion.
  • 🔍 The script delves into numeric predicate functions, control flow, and list data structure functions in Scheme, emphasizing the language's pure functional nature without loop constructs.
  • 📝 An in-depth look at the 'equal?' and 'equal-simp' functions illustrates how to recursively compare elements in lists, highlighting base cases and recursive cases in Scheme.
  • 🔧 The 'append' function is explored, demonstrating how to concatenate lists in Scheme using recursion and the 'cons' function, rather than appending atom by atom.
  • 🔄 Tail recursion in Scheme is mandatory for the compiler to optimize recursive calls into iterative constructs, improving efficiency and reducing memory usage.
  • 🛠️ The 'apply-to-all' functional form showcases the power of first-class functions in Scheme, allowing functions to be passed as parameters and returned as results.
  • 🤖 Common Lisp is contrasted with Scheme, presenting a more complex language with additional features like iterative control structures and a rich set of data structures.
  • 🏢 Applications of Lisp and Scheme are discussed, including their historical use in AI, knowledge representation, machine learning, and natural language processing.
  • 🏫 Scheme's role in education is highlighted, being used to teach introductory programming concepts in universities and colleges.
  • 💡 A comparison between functional and imperative programming languages is made, noting the simplicity and mathematical nature of functional languages versus the efficiency and complexity of imperative ones.
  • 📝 The script provides guidance for students preparing for tests and exams, emphasizing the importance of understanding the provided code examples and practical exercises.

Q & A

  • What is the main focus of the third and final lecture on chapter 15?

    -The lecture concludes the discussion on functional programming languages, specifically focusing on Scheme functions, tail recursion, first-class functions, and a brief overview of Common Lisp and real-world applications of functional programming languages.

  • What are predicate functions in Scheme?

    -Predicate functions in Scheme are functions that evaluate to a boolean result, typically used for testing certain conditions or properties of data.

  • How does Scheme handle control flow within its functions?

    -Scheme handles control flow through the use of recursion, as it is a pure functional programming language and does not have traditional loop structures.

  • What is the purpose of the 'equal simp' function in Scheme?

    -The 'equal simp' function is used to compare two simple lists (lists containing only atoms, no nested sublists) and returns true if the lists are equal in terms of their elements, and false otherwise.

  • What is tail recursion and why is it important in Scheme?

    -Tail recursion is a form of recursion where the recursive call is the last operation performed in a function. It is important in Scheme because the language definition requires all tail recursive functions to be automatically converted to iterative structures, making them more efficient in terms of speed and memory usage.

  • How can a non-tail recursive function be converted into a tail recursive function in Scheme?

    -A non-tail recursive function can be converted into a tail recursive function by introducing an additional parameter, often called an accumulator or a 'partial' result, which carries the ongoing result through each recursive call, allowing the recursive call to be the last operation.

  • What is the significance of first-class functions in Scheme?

    -First-class functions in Scheme mean that functions can be treated like any other data type. They can be passed as arguments to other functions, returned as results, and assigned to variables. This feature is demonstrated through functions like 'map car' and 'adder', which utilize functional forms.

  • What is Common Lisp and how does it differ from Scheme?

    -Common Lisp is a dialect of the Lisp programming language that combines features from many popular Lisp dialects in the early 1980s. It is a large and complex language with a rich set of data structures and powerful I/O capabilities, unlike Scheme, which is designed to be simple and easy to understand.

  • What are some real-world applications of functional programming languages?

    -Functional programming languages have been used in various fields such as artificial intelligence for knowledge representation, machine learning, and natural language processing. They have also been used in end-user software systems like the Emacs text editor and the Maxima mathematics system.

  • What are the advantages of functional programming languages over imperative programming languages in terms of concurrency?

    -Functional programming languages naturally divide programs into functions, which can be executed concurrently or in a distributed fashion without the programmer having to explicitly manage mutual exclusion locks or other concurrency control mechanisms.

  • What type of questions can be expected in tests and exams related to functional programming languages?

    -Tests and exams may include theory questions, questions asking for the result of provided Scheme code, analysis and correction of Scheme code, and implementation of Scheme functions with a complexity level similar to the examples covered in lectures.

Outlines

00:00

📚 Concluding Functional Programming Languages

This paragraph wraps up the discussion on functional programming languages, focusing on Scheme's capabilities with list and predicate functions, control flow, and recursive operations. It introduces the concept of tail recursion and its efficiency benefits, as well as first-class functions, which are a cornerstone of Scheme's power. The paragraph also briefly mentions Common Lisp and real-world applications, concluding with a comparison between functional and imperative programming languages, highlighting functional programming's advantages.

05:00

🔍 Deep Dive into Scheme's equal? Function

The second paragraph provides an in-depth look at Scheme's 'equal?' function, which compares two lists element by element. It discusses the function's implementation using recursion and the 'cond' expression to handle base cases, including mismatches and list exhaustion. The paragraph also explains the recursive case, where elements are compared, and the function is reapplied to the remaining lists. The explanation includes the handling of both simple lists ('equal?') and lists with nested structures.

10:03

📝 Implementation of Scheme's append Function

This paragraph discusses the 'append' function in Scheme, which combines two lists into one. It explains the strategy of prepending elements from the first list to the second in reverse order. The paragraph details the recursive implementation of 'append', which involves eliminating elements from the first list one by one until an empty list is reached, serving as the base case. It also poses questions to the viewer about the limitations of appending lists atom by atom and suggests experimenting with the 'list' function as an alternative to 'cons'.

15:06

🔄 Tail Recursion in Scheme

The fourth paragraph delves into tail recursion, explaining its importance in functional programming due to its efficiency and the potential for compilers to convert tail recursive functions into iterative constructs. It contrasts this with the general inefficiency of recursive solutions and provides a practical example of converting a non-tail recursive 'factorial' function into a tail recursive version using a helper function, demonstrating how this can improve performance and resource usage.

20:07

🤖 First-Class Functions and Functional Forms in Scheme

This paragraph explores the concept of first-class functions in Scheme, where functions can be passed as parameters and returned as results. It introduces 'mapcar', a functional form that applies a given function to every element in a list, constructing a new list with the results. The paragraph also discusses the power of functions being able to build and execute code, using 'eval' to interpret lists as Scheme code, illustrating the flexibility and expressiveness of Scheme.

25:08

🤹‍♂️ Common Lisp and Applications of Functional Programming

The sixth paragraph provides an overview of Common Lisp, highlighting its complexity and rich feature set, which includes data structures, IO capabilities, and packages. It contrasts Common Lisp with Scheme in terms of simplicity and ease of understanding. The paragraph also touches on the application areas of Lisp and Scheme, particularly in artificial intelligence, knowledge representation, machine learning, and natural language processing, as well as their use in end-user software systems like the Emacs text editor and Lisp machine.

30:08

🏢 Real-World Usage of Lisp and Scheme

This paragraph discusses the real-world usage of Lisp and Scheme, noting their prevalence in academic settings for teaching programming concepts. It also mentions the historical use of Lisp in AI and its application in various fields such as robotics and algebraic processing systems. The paragraph emphasizes the influence of Lisp's design on its suitability for low-level operations and its adaptability for concurrent execution.

35:11

📉 Comparative Analysis of Functional and Imperative Programming

The eighth paragraph compares functional programming languages with imperative programming languages, focusing on their underlying philosophies, execution efficiency, and approach to concurrency. It points out the simplicity and mathematical nature of functional languages versus the complex semantics and syntax of imperative languages, which are more closely tied to hardware. The paragraph also addresses the ease of implementing concurrency in functional languages due to their natural division into independently executable functions.

40:12

📝 Study Guide for Functional Programming Concepts

The final paragraph serves as a study guide, advising students on how to prepare for tests and exams related to functional programming. It emphasizes the importance of understanding the provided code examples and completing practical exercises. The paragraph reassures students that exam questions will not exceed the complexity of the examples covered in the lectures and encourages the use of additional notes and resources for a deeper understanding of the material.

Mindmap

Keywords

💡Functional Programming Languages

Functional programming languages are a category of programming paradigms that treat computation as the evaluation of mathematical functions and avoid changing-state and mutable data. In the video, the theme revolves around discussing various aspects of these languages, particularly Scheme, and their application in programming tasks such as working with lists and implementing recursive functions.

💡Scheme

Scheme is a minimalist, general-purpose, multi-paradigm programming language that supports functional programming. The script delves into Scheme's approach to handling control flow, list processing, and recursion, showcasing its unique features such as first-class functions and tail recursion optimization.

💡Predicate Functions

Predicate functions are functions that return a boolean value, typically used for evaluating conditions. In the script, numeric predicate functions are mentioned, which are used to evaluate to a boolean result within the context of Scheme programming language, playing a crucial role in control flow and decision-making within programs.

💡Control Flow

Control flow refers to the order in which individual statements, instructions, or function calls are executed or evaluated in a program. The script discusses how Scheme, lacking traditional loop structures, manages control flow within its functions using recursion and conditional expressions like 'cond'.

💡First-Class Functions

First-class functions are a concept in programming languages where functions are treated as first-class citizens, meaning they can be passed as arguments, returned from other functions, and assigned to variables. The script illustrates the power of first-class functions in Scheme through examples like 'mapcar' and 'apply to all', emphasizing their flexibility and utility.

💡Tail Recursion

Tail recursion is a specific form of recursion where the recursive call is the final operation in the function. The script explains the importance of tail recursion in Scheme, noting that it allows for more efficient execution by enabling the compiler to convert recursive calls into iterative loops, thus saving memory and improving performance.

💡List Data Structures

List data structures are used in Scheme to organize collections of elements. The script provides examples of functions that operate on lists, such as 'equal?', 'equal-simp', and 'append', which manipulate and compare list structures, demonstrating Scheme's capabilities in handling complex data manipulations.

💡Recursion

Recursion in programming is a method where a function calls itself to solve smaller instances of the same problem. The script discusses the use of recursion in Scheme for tasks such as list processing and factorial computation, emphasizing the need for a recursive approach due to the absence of loop structures in the language.

💡Cons Function

The 'cons' function in Scheme is used to create a new pair or add an element to the front of a list. The script explains how 'cons' is used in the context of the 'append' function to build a new list by prepending elements from one list to another in a specific order, illustrating the fundamental operations on lists in Scheme.

💡Lambda Expression

A lambda expression is a way to create anonymous functions, i.e., functions without a name. In the script, a lambda expression is used in the 'mapcar' function to apply an operation to each element of a list, demonstrating the flexibility of defining and using short-lived, unnamed functions in Scheme.

💡Eval Function

The 'eval' function in Scheme evaluates the Scheme expression passed to it. The script shows an example where 'eval' is used to interpret and execute Scheme code that has been constructed as a list, highlighting the meta-programming capabilities of the language where code can be dynamically created and executed.

Highlights

Concluding the discussion on functional programming languages with a focus on Scheme.

Introduction to numeric predicate functions and control flow in Scheme functions.

Exploring list data structure operations and predicate functions for object equivalence in Scheme.

The 'let' function and its role in Scheme's recursive list operations.

Three examples of Scheme functions demonstrating functional programming concepts.

Tail recursion in Scheme and its implications for efficiency.

Conversion of non-tail recursive functions to tail recursive for improved performance.

First-class functions in Scheme and their applications in 'map' and 'apply to all' functions.

Building and executing code with Scheme functions, illustrating the power of first-class functions.

Brief overview of Common Lisp as another dialect of the Lisp programming language.

Real-world applications of functional programming languages in various domains.

General comparison between functional and imperative programming languages.

Advantages of functional programming highlighted in the comparison.

Detailed implementation of the 'equal simp' function for list equality in Scheme.

Recursive approach to the 'equal' function handling both atoms and nested sublists.

Implementation and strategy of the 'append' function in Scheme.

Discussion on the inefficiency of non-tail recursive solutions and the benefits of tail recursion.

Conversion of a non-tail recursive 'factorial' function to a tail recursive version.

The 'map car' function as an example of a functional form in Scheme.

Constructing and executing code with the 'eval' function in Scheme.

Common Lisp's features and its contrast with Scheme's simplicity.

Applications of Lisp in artificial intelligence, knowledge representation, and machine learning.

Scheme's use in education for teaching introductory programming concepts.

Differences in philosophy, efficiency, and concurrency between functional and imperative programming languages.

Study advice for tests and exams, including the use of provided slides and practical exercises.

Transcripts

play00:01

this is the third and final lecture

play00:04

on chapter 15 where we will conclude our

play00:07

discussion

play00:08

on functional programming languages

play00:12

in the previous lecture we continued our

play00:15

discussion on the scheme

play00:16

programming language we looked at

play00:19

numeric

play00:19

predicate functions where predicate

play00:22

functions

play00:22

are functions that evaluate to

play00:26

a boolean result we then looked

play00:29

at how scheme deals with control flow

play00:32

within its functions and then we looked

play00:36

at a number of functions

play00:38

including predicate functions which work

play00:41

with list data structures in scheme

play00:44

we then looked at some predicate

play00:47

functions

play00:48

for testing equivalence between

play00:50

different objects

play00:52

in scheme and then we considered the

play00:56

let function we finished the lecture off

play00:59

by looking at our first example of

play01:02

a fully implemented scheme function

play01:06

that performs recursive operations on

play01:09

a list in this lecture we

play01:12

will further continue our discussion on

play01:15

scheme

play01:17

we'll look at three more examples of

play01:20

scheme functions

play01:21

and then move our discussion on to tail

play01:24

recursion in scheme

play01:26

and what tail recursion implies as well

play01:29

as how to convert

play01:31

a non-tail recursive function into a

play01:34

tail

play01:34

recursive function we'll then look at

play01:38

and apply to all function as well as

play01:41

functions that build code and

play01:44

these two demonstrations will illustrate

play01:48

the power of first class

play01:51

functions within scheme then we'll look

play01:54

very briefly

play01:55

at common lisp which is another dialect

play01:58

of the lisp

play01:59

programming language and we'll also

play02:02

consider some real-world applications

play02:05

of functional programming languages

play02:08

right at the end of this lecture

play02:10

we will look at a general comparison

play02:13

of functional and imperative programming

play02:15

languages

play02:16

we will highlight some of the advantages

play02:19

associated with functional programming

play02:24

so let's jump straight into our next

play02:27

complete scheme function implementation

play02:30

and again this function like the member

play02:33

function that we discussed in the

play02:35

previous lecture

play02:36

will perform operations on

play02:39

list structures so this function is

play02:43

called equal simp

play02:44

and it is applied to two parameters list

play02:47

one

play02:48

and list 2 and both of these parameters

play02:51

are simple lists so again this means

play02:55

that the lists contain only atoms

play02:58

in other words neither list contains

play03:00

nested sub-lists

play03:02

the equal sim function should yield a

play03:05

true result

play03:06

if the two lists are equal in other

play03:08

words both lists contain

play03:10

the same elements and false otherwise

play03:14

so how will we approach this problem

play03:17

well

play03:18

again the problem specification implies

play03:21

that we need

play03:22

to work through both of our lists

play03:25

element by element so this implies then

play03:28

some kind of repetitive structure

play03:32

and again as we know scheme is a pure

play03:34

functional programming language

play03:36

which means that it has no loop

play03:39

structure

play03:40

and therefore we must approach the

play03:42

problem in a

play03:43

recursive fashion so we will recursively

play03:47

then work

play03:47

through both lists and we will compare

play03:51

elements within the two lists to each

play03:54

other so there are pairs of elements

play03:56

that we compare

play03:58

and we then yield a true result

play04:01

if both of the lists are exhausted

play04:04

simultaneously so in other words

play04:06

we have a series of matches between

play04:09

element pairs

play04:11

and we reach the end of list 1 and list

play04:14

2

play04:14

at the same time we yield a

play04:17

false result if we find any mismatch

play04:21

between a pair of elements or

play04:24

if we run out of elements in one of the

play04:27

two lists

play04:27

before running out of elements in

play04:31

the other list so this implies then that

play04:34

we have

play04:35

four base cases we have a

play04:38

case where we find a mismatch

play04:41

a case where we run out of elements in

play04:44

the two lists at the same time

play04:46

and then two cases for either list one

play04:49

or list two

play04:50

running out of elements before the other

play04:53

list

play04:54

does so over here we have

play04:57

a definition for this function here

play05:00

we've used again

play05:01

the define function to bind a

play05:04

name to our function the name is equal

play05:06

simp and the two parameters are list one

play05:10

and list two we then have a

play05:13

cond in the body of our function and

play05:16

again

play05:16

the cond will decide whether one of the

play05:19

four base cases

play05:20

are executed or the recursive case

play05:24

is executed so over here then

play05:28

we have three of the base cases

play05:31

handled two of the base cases are

play05:33

handled by

play05:34

one condition so first of all we use the

play05:38

null function to check

play05:40

whether list one is empty

play05:43

now if list one is empty then one of two

play05:45

things might be the case

play05:47

either list two might also be empty in

play05:50

which case we've exhausted the elements

play05:52

in both lists at the same time

play05:54

all is 2 might not be empty in which

play05:57

case we've run

play05:58

out of elements in list 1 before running

play06:01

out of elements

play06:02

in list 2. so what

play06:05

this then means is that the result

play06:08

is dependent on whether list two

play06:11

is null if list2 is null in other words

play06:15

this predicate evaluates to true

play06:18

then this means that we've exhausted

play06:20

elements in both lists at the same time

play06:23

and therefore we can conclude that the

play06:26

result

play06:27

of the cond as well as the result of the

play06:30

equal simp function should then be

play06:32

true if this predicate ends up being

play06:36

false so in other words we

play06:39

have an empty less one but a non-empty

play06:43

list

play06:43

two then it means that we've run out of

play06:46

elements

play06:47

in list one before running out of

play06:49

elements in list two

play06:51

therefore this predicate will evaluate

play06:53

to false which means the entire cond

play06:55

evaluates to false

play06:57

and therefore the equal simp function

play06:59

application will also evaluate to false

play07:02

so that's two of our four base cases out

play07:04

of the way

play07:05

then we move on to the next condition

play07:08

over here which deals

play07:09

with a further third base case

play07:13

and this test then is to see whether

play07:16

list two is null in other words empty

play07:19

now we've already eliminated the case

play07:22

where

play07:23

list one is null or empty so this means

play07:26

that list one contains at least one

play07:28

element so therefore

play07:30

if list two is null in other words empty

play07:34

then this means that we have run

play07:38

out of elements in list two before

play07:40

running out of elements in

play07:41

list one in this case we can then simply

play07:44

conclude that the result is false and

play07:46

again

play07:47

this then means that the entire con

play07:49

devaluates to false

play07:50

which means that our equal sim function

play07:52

application also

play07:54

evaluates to false so

play07:57

next we have our recursive case and then

play08:00

lastly we deal with our fourth base case

play08:04

so the recursive case then finds the car

play08:07

of each of the two lists so in other

play08:10

words the element at the head of

play08:11

each list and it compares those two

play08:14

elements

play08:15

to each other if they are equivalent to

play08:18

each other

play08:19

then we know that we have found a match

play08:22

so we need to then

play08:24

continue matching pairs between the two

play08:27

lists

play08:28

we then recursively apply equal simp

play08:31

but we apply it then to the cuda of list

play08:34

one

play08:34

and the cuda of list two that will then

play08:37

eliminate

play08:38

the elements that we found that do match

play08:40

in other words the cause of the two

play08:42

lists

play08:42

and we then continue searching on the

play08:45

remainder

play08:46

of the two lists finally

play08:49

we reach the else so this else will then

play08:52

be

play08:52

reached if we still have

play08:56

elements to compare but we have now not

play08:59

had

play08:59

a successful match between the cause

play09:02

of the two lists meaning that we found

play09:06

a mismatch and in this case we can then

play09:09

terminate our search we don't need to go

play09:11

any further through the two lists and we

play09:14

can simply then conclude with

play09:16

a false result again the entire cond

play09:19

will then evaluate

play09:20

to false and the entire application of

play09:24

the equal simp

play09:25

function will evaluate to false

play09:29

now of course there are a couple of

play09:30

different ways that we can specify the

play09:32

base cases

play09:33

so we could obviously rewrite this

play09:36

function

play09:37

to swap these two

play09:40

first tests um for one another but of

play09:44

course

play09:45

then we would have to initially check

play09:48

whether list two is null

play09:50

and then the result would be where the

play09:53

list one is null and then our second

play09:55

test would test to see whether list one

play09:58

is null and would conclude false in that

play10:02

case

play10:04

the next function implementation we'll

play10:06

look at is for a function

play10:08

named equal the equal function does the

play10:11

same thing as the equal simp function

play10:14

we've just discussed and the difference

play10:16

is

play10:17

that the equal function is applied to

play10:20

two general list parameters so in other

play10:23

words list one and list two

play10:26

can both contain a mixture of atoms

play10:29

as well as nested sublists

play10:32

so how will we approach solving this

play10:34

problem well over here we have an

play10:36

implementation

play10:37

for the equal function and we will be

play10:40

using the same core strategy

play10:43

as we used in the equal simp function so

play10:46

in other words

play10:47

we will recursively compare pairs of

play10:50

elements from the two lists

play10:52

and we have again four base cases

play10:55

one for if we find a mismatch between a

play10:58

pair of elements

play11:00

one for if we exhaust both lists

play11:03

at the same time one for if we

play11:06

exhaust the first list before the second

play11:09

list

play11:10

and a final case where we exhaust the

play11:13

second list

play11:13

before the first list so

play11:16

we can see then in our cond we have

play11:19

these two conditions over here

play11:21

and these deal with three of those base

play11:23

cases once again

play11:24

so in exactly the same way as we saw in

play11:27

the equal

play11:28

simp function and then our final case

play11:31

where there is a mismatch between

play11:33

a pair of elements is handled by this

play11:36

else over here again so so far

play11:40

we have exactly the same implementation

play11:43

as the

play11:43

equal simp function our recursive case

play11:47

is also almost exactly the same as the

play11:50

equal simp function so again

play11:54

we compare the cause of the two lists to

play11:57

each other in other words the first

play11:59

elements within each of those two lists

play12:01

are compared

play12:03

and if they are found to be equal then

play12:05

we recursively

play12:06

apply the function to the cuda of

play12:10

each list thus eliminating the first

play12:12

element in each list

play12:14

and continuing the matching process

play12:17

now where the equal function has to

play12:19

differ is that this comparison

play12:22

between the two cars has to work

play12:25

for both atoms as well as

play12:29

nested sublists so how will we

play12:32

address this part of the problem well

play12:35

we know that the equal function is

play12:38

capable of comparing

play12:40

two lists to each other so if we use

play12:43

the equal function itself in order to

play12:45

perform the comparison

play12:47

between the cause of the two lists

play12:50

then we know that we can deal with a

play12:52

situation where

play12:53

these elements that we are comparing are

play12:56

in fact

play12:57

lists so this only leaves the situation

play13:00

where the car

play13:01

of each list is then an atom

play13:04

or where one of the cause of the two

play13:07

lists

play13:08

is an atom so we essentially then

play13:11

need to extend the implementation of the

play13:14

equal simple function

play13:16

so that it can also deal with atoms

play13:20

so this is where the first two

play13:22

additional

play13:23

conditions within the cond come in and

play13:26

essentially what we have to do

play13:28

here is test to see whether one of

play13:31

the two parameters is a

play13:34

list or not so we can use then

play13:38

the list predicate in order to do this

play13:41

and the list predicate will then yield

play13:45

a true result if the list that is being

play13:48

checked is in fact a list

play13:50

and if it is not it will yield a false

play13:53

result

play13:53

so in other words a false result will be

play13:56

produced

play13:57

if we have a situation where either one

play14:00

of the two parameters

play14:02

is not a list and is in other words an

play14:05

atom

play14:05

of some sort so first of all

play14:08

we check to see whether list one is

play14:12

not a list so if this is the case then

play14:16

we know that list one

play14:17

must be an atom so in this case we can

play14:21

then perform

play14:22

an equality check between the two lists

play14:26

now what we've seen with our built in

play14:28

equality predicates

play14:30

is that they will yield a true result

play14:34

if we are comparing two atoms to each

play14:37

other and those atoms are the same

play14:38

and a false result will be produced if

play14:41

the two atoms are not the same

play14:43

or if we are comparing an atom to a list

play14:46

or a list

play14:47

to an atom so we know at this point

play14:50

that list one must be an atom

play14:54

which means that this equality check

play14:56

will only produce

play14:57

a true result if list two is the same

play15:00

atom if it's not

play15:01

then we will get a false result and if

play15:05

list two

play15:06

is a list then again we will get a false

play15:09

result because we are comparing an atom

play15:12

to a list next we have this condition

play15:16

over here

play15:17

so at this point we've eliminated the

play15:19

first condition

play15:21

so we then know that list one must be

play15:24

a list so in this case we can then check

play15:27

to see whether list

play15:28

2 is not a list

play15:31

if it is not a list then it means list 2

play15:34

must be

play15:35

an atom so in this case we are then

play15:38

comparing a

play15:39

list which is list one to an atom

play15:42

which is list two and so in this case

play15:45

we obviously then have a mismatch and

play15:49

the

play15:49

result that we evaluate to is then a

play15:52

false

play15:53

so essentially what we've now done is

play15:55

we've modified the

play15:56

equal simp function so that it can

play15:59

compare

play16:00

both atoms and lists and then we've

play16:03

changed our recursive case

play16:05

so that the comparison between two

play16:08

elements is performed by the equal

play16:10

function itself

play16:11

which we know can now deal with both

play16:14

atoms

play16:15

and lists

play16:19

the last function implementation we'll

play16:21

look at

play16:22

in this lecture is for a function named

play16:25

append

play16:26

the append function is again applied to

play16:29

two parameters

play16:30

and both of these parameters are lists

play16:33

we will once again refer to these lists

play16:36

as list 1

play16:37

and list 2 respectively the append

play16:40

function

play16:41

should then yield a new list and this

play16:44

new list should contain all of the

play16:46

elements of

play16:47

list 1 followed by all of the elements

play16:50

contained

play16:51

in list two so

play16:54

over here we have an implementation for

play16:57

our append function we can see that it's

play16:58

a fairly short function

play17:00

but the logic underlying it is a little

play17:03

more complex

play17:05

so what strategy then will we use to

play17:08

perform

play17:08

this appending operation so we'll

play17:12

prepend

play17:13

elements in list one one by one

play17:16

to list two but we'll do so in

play17:19

reverse order so in other words we will

play17:21

prepend

play17:22

the last element in list one to list two

play17:26

then we will prepend the second last

play17:28

element in list one

play17:30

then the third last element in list one

play17:32

and so we will continue

play17:34

and the very last prepending operation

play17:36

will perform

play17:37

is for the first element in list one

play17:42

so let's look at this example at the

play17:44

bottom here here we are performing an

play17:46

append

play17:47

between two lists we have the list that

play17:49

contains the atoms a

play17:51

and b and a list that contains the atoms

play17:54

c and d so our strategy

play17:58

will then prepend the atom b

play18:01

to the list cd creating the list

play18:04

bcd and then it will prepend the atom a

play18:08

to the list bcd to produce the list abcd

play18:14

so how will we then actually go about

play18:16

implementing this

play18:17

strategy well let's look at our function

play18:20

implementation

play18:21

again the body of this function is a

play18:24

cond

play18:25

and we'll start off by looking at the

play18:28

recursive

play18:29

case which is included in the

play18:32

else condition within our cond

play18:36

so this else condition then recursively

play18:39

applies the append function

play18:41

and it applies the append function to

play18:44

the cuda

play18:45

of list one and list two

play18:48

so the cuda of list one is the tail of

play18:51

list one

play18:52

in other words we discard the first

play18:55

element

play18:56

in list one we then

play18:59

assume that this append function will

play19:02

generate the correct result

play19:04

so the result then of this entire

play19:07

recursive call

play19:08

will be the remaining elements in list

play19:12

one

play19:12

excluding the first element followed by

play19:15

the elements within list two all

play19:18

contained within

play19:19

a new list all that we then need to do

play19:23

is add the first item in list one

play19:27

so we do that by retrieving the car of

play19:30

list

play19:30

one which is then the first element

play19:32

contained in that list

play19:34

and then we use an application of the

play19:36

cons function

play19:37

to add this first element

play19:40

in list one to the result of appending

play19:44

the cuda of list one and list

play19:47

two this will then produce a

play19:50

fully appended list containing all of

play19:52

the elements in list one

play19:54

and list two so

play19:57

it's important then to understand that

play20:00

the procedure that we will be following

play20:02

is we will eliminate elements from list

play20:04

one one at a time so we will eliminate

play20:07

the first element

play20:08

then the second element then the third

play20:10

element and so on

play20:11

until eventually we end up with

play20:14

a list containing a single element

play20:18

now recall when we were discussing the

play20:20

cuda function

play20:21

that the cuda of a list containing only

play20:24

a single element

play20:25

is an empty list i mentioned that we

play20:29

should think about

play20:30

lists in scheme as containing a series

play20:33

of elements and then implicitly

play20:35

there is a last element which is an

play20:38

empty list so if we then

play20:42

find the cuda of this list containing a

play20:46

single element

play20:47

the result of that will then be an empty

play20:50

list

play20:51

and this then gives us the

play20:54

base case for our append function

play20:57

so the base case then is if the first

play21:00

list

play21:01

is empty in other words it is null so we

play21:04

can determine that

play21:05

by an application of the null function

play21:09

in this case the result of the

play21:12

recursive call is then simply list

play21:15

2. so let's look at how this append

play21:19

function

play21:20

would operate based on our example

play21:23

so first of all we apply our append

play21:25

function to

play21:26

the list a b and the list

play21:29

cd now we perform

play21:33

a recursive call to the append

play21:36

function because we don't have an empty

play21:39

list as the first list yet

play21:42

and we eliminate the first element

play21:45

namely

play21:45

the atom a which leaves us then

play21:49

with the list containing only a single

play21:52

atom

play21:52

namely b in that case we then perform

play21:56

another recursive call to the append

play21:59

function

play22:00

and this then eliminates the first

play22:02

element within

play22:03

our first list in other words the atom b

play22:06

and it leaves

play22:07

us with an empty list so now we are

play22:11

appending an

play22:11

empty list and the list cd and

play22:14

this is then our base case over here

play22:17

because

play22:18

our first list is now null and the

play22:21

result

play22:21

of this is just then simply the second

play22:23

list in other words the list

play22:25

c d in this case now we need to

play22:29

unwind our call stacks so we return to

play22:32

the previous call which we've done over

play22:35

here

play22:36

and we now have as a result

play22:40

of the append between the empty list

play22:43

and the list cd the

play22:46

list cd over here so now we need to

play22:50

perform

play22:50

a cons between that list

play22:54

and the first element of our first list

play22:57

which is then the atom b in that case

play23:01

so this const will then produce the list

play23:03

b c

play23:04

d we've now again finished

play23:08

a recursive call execution so we unwind

play23:11

our call stack

play23:12

again and we then return to the first

play23:15

call

play23:16

and here we then perform a const between

play23:19

the

play23:19

car of the first list which is then a

play23:22

as we can see over there and we are

play23:25

performing

play23:26

the cons with the result of the previous

play23:28

recursive call

play23:30

which as we've seen is the list b c d

play23:33

so the result of this const will then be

play23:35

the list

play23:36

a b c d and that is then our

play23:39

final result for the appending of the

play23:42

lists

play23:43

a b and c d

play23:47

right so that's how our append function

play23:49

will then execute

play23:51

i'd like you now to think about two

play23:53

questions

play23:54

and try to answer them pause the video

play23:56

if you need to at this point

play23:58

first of all why can't we append list

play24:02

two to list one atom by atoms if we look

play24:05

at our example over here

play24:07

can we append to the list a b

play24:11

the atom c to produce the list abc

play24:14

and then finally append the atom d to

play24:17

the list

play24:17

abc to produce the list abcd

play24:21

so the answer to that question is no we

play24:23

can't and what i'd like you to answer is

play24:26

why is that the case as a

play24:29

clue look at how the cons

play24:33

function application will work so in

play24:36

this case

play24:36

the cons function application will be

play24:40

between a list and then as the second

play24:43

parameter of the cons

play24:44

we will have an atom so look back at the

play24:48

previous lecture and see how the cons

play24:50

function

play24:51

behaves if we attempt to cons

play24:54

a list and an atom as the second

play24:58

parameter

play24:59

try to determine what the result of this

play25:02

series of cons operations will be

play25:05

through the series of recursive calls

play25:07

on our example lists and then

play25:11

if you are still not sure of the result

play25:14

implement

play25:14

this append function and see what the

play25:17

result is when you execute it

play25:21

secondly i would like you to consider

play25:23

the use of

play25:24

the list functions so again from the

play25:26

previous lecture

play25:28

recall that the list function can be

play25:30

used

play25:31

to build up lists so here what i am

play25:34

asking

play25:35

is is it possible to use the list

play25:37

function instead of the cons

play25:40

function over here again try to

play25:42

determine what the result

play25:44

of that modification would be and if you

play25:48

can't answer that question then try

play25:50

implementing the append function in that

play25:53

way

play25:54

and see what the result is and try to

play25:56

explain

play25:57

why you get that result

play26:01

next let's take a look at tail recursion

play26:04

within scheme

play26:05

so we've seen that scheme is a pure

play26:08

functional programming language

play26:10

and amongst other things this means that

play26:12

any repetition within a scheme program

play26:15

must be achieved by means of recursion

play26:19

now in general we know that a recursive

play26:21

solution

play26:22

is much slower than an equivalent

play26:25

iterative

play26:26

solution and also uses a lot more

play26:29

memory now the reason for the

play26:32

execution of a recursive solution being

play26:35

slower

play26:36

is that there are a variety of

play26:38

operations that need to be performed

play26:40

each time that

play26:41

a function is called there are a number

play26:44

of bookkeeping operations

play26:46

as well as memory allocation operations

play26:49

that have to take place

play26:51

and of course these operations take up

play26:53

time

play26:54

and therefore cumulatively over a series

play26:56

of recursive

play26:58

function calls the entire recursive

play27:00

solutions

play27:01

execution is slowed down

play27:05

additionally each time that a function

play27:07

is called

play27:08

a stack frame is allocated for that

play27:11

function

play27:12

and stack frames take up space in memory

play27:15

and so therefore each recursive function

play27:18

call

play27:19

will use up some additional memory

play27:22

iterative solutions are not subject to

play27:25

these problems so this brings us then to

play27:29

the idea

play27:30

of a tail recursive function and a tail

play27:33

recursive

play27:34

function is a function in which the

play27:36

recursive call

play27:37

is the last operation performed

play27:41

now the implication of tail recursive

play27:43

functions

play27:44

is that a compiler can automatically

play27:47

convert

play27:48

these into an iterative construct

play27:53

so this then makes the function much

play27:55

faster

play27:56

and more resource efficient if this

play27:59

conversion

play28:00

takes place and it turns out that the

play28:03

scheme language definition requires

play28:05

all tail recursive functions to be

play28:07

automatically converted

play28:09

to iterative structures so therefore

play28:13

it's worthwhile for us to take a look at

play28:15

tail recursive functions in scheme

play28:18

and how to convert non-tail recursive

play28:21

functions

play28:21

into tail recursive functions which are

play28:25

then much

play28:26

more efficient and will execute much

play28:29

faster and use less memory

play28:34

so let's look at a practical example of

play28:37

a non-tail recursive function and how we

play28:40

would convert it into a

play28:42

tail recursive function so over here we

play28:45

have

play28:46

a function implementation we've defined

play28:48

a function called factorial

play28:51

and it is applied to a single parameter

play28:54

which we assume is a numeric value

play28:58

inside the body of the factorial

play28:59

function we have an application of the

play29:02

if function and the if function selects

play29:05

between the base case

play29:07

or the recursive case we could have also

play29:10

used a cond in the if functions place

play29:14

as we did in the previous examples

play29:18

so then our if function is

play29:21

used to define a factorial in

play29:24

the usual way that a factorial is

play29:26

defined in mathematics

play29:28

so the factorial of zero is one

play29:32

and the factorial of any other value

play29:34

greater than zero

play29:36

is that value multiplied by

play29:39

the factorial of the value minus

play29:42

1. so let's look at how this function

play29:46

would execute

play29:47

in terms of an example which we have

play29:49

over here

play29:50

here we are computing the factorial of

play29:53

three

play29:54

so this then results in our recursive

play29:57

step

play29:58

being executed which means that we are

play30:00

computing

play30:01

three multiplied by the factorial of two

play30:04

but we need to know what the factorial

play30:06

of two is so this results

play30:08

in a recursive call where we compute the

play30:10

factorial of 2

play30:11

which is 2 multiplied by the factorial

play30:14

of

play30:15

1. again we need to know what the

play30:17

factorial of 1 is so we perform

play30:19

another recursive call computing the

play30:21

factorial of 1

play30:22

which is then 1 multiplied by the

play30:25

factorial of

play30:26

0. at this point we've reached our base

play30:29

case

play30:30

so we know that the factorial of 0 is

play30:33

1 and now we return

play30:36

to the previous recursive call so the

play30:39

previous recursive call was computing

play30:41

the factorial of one

play30:43

we now have a result for the second

play30:45

parameter

play30:46

so we are now computing the result of

play30:48

one multiplied by

play30:49

one which is one we now return

play30:52

to the previous recursive call which was

play30:55

computing the factorial of two

play30:58

and so now we have the multiplication

play31:01

of two and one which produces a result

play31:04

of 2. finally we return to our first

play31:08

call

play31:08

2 factorial which was computing the

play31:10

factorial of 3

play31:12

and now we have the multiplication of 3

play31:16

and 2 which produces a result of

play31:19

6. this is then the final answer to

play31:22

our initial call

play31:25

so is this implementation of the

play31:28

factorial function

play31:29

tail recursive or not well if we look at

play31:32

the implementation we first of all have

play31:34

to compute the result of the subtraction

play31:37

then we use the result of the

play31:39

subtraction to compute

play31:41

the result of the factorial functions

play31:44

application and then the result

play31:47

of the factorial functions application

play31:49

is used

play31:50

in the multiplication so in other words

play31:53

the last function that is called in the

play31:55

recursive

play31:56

case is the multiplication function not

play31:59

the recursive application of the

play32:02

factorial function

play32:03

what this thing means is our

play32:05

implementation of the factorial function

play32:08

is not tail recursive and this then

play32:11

implies that the function will execute

play32:13

slowly

play32:14

and it will require a lot of memory

play32:16

resources

play32:18

we can also see that this function is

play32:20

not tail recursive because

play32:22

the result is computed as the

play32:25

stack unwinds through our series of

play32:28

recursive calls

play32:30

so how can we then convert the factorial

play32:33

function

play32:34

to a tail recursive function

play32:37

well we have such an implementation over

play32:39

here

play32:40

and very often the conversion of a

play32:44

non-tail recursive function to a tail

play32:46

recursive function

play32:47

requires the assistance of a helper

play32:50

function

play32:51

so that's exactly what we've done over

play32:52

here fact helper is our helper function

play32:56

and down here we've defined our

play32:58

factorial function

play32:59

once again applied to a single numeric

play33:02

parameter

play33:03

in and then we apply the

play33:06

fact helper function to in

play33:09

and one as parameters now if we look at

play33:13

our

play33:13

fact helper function over here we see

play33:16

then that we have two parameters

play33:18

in and partial in is used as

play33:21

a kind of counter so we will start off

play33:25

with n

play33:25

being equivalent to the value that we

play33:28

are attempting to compute

play33:29

the factorial of and then with each

play33:32

recursive call

play33:33

we decrement that value by one so in our

play33:36

example over here when we start

play33:38

by computing the factorial of three then

play33:41

in will initially have a value of three

play33:43

on the next

play33:44

recursive call it will have a value of

play33:46

two and then

play33:47

finally one at which point we then reach

play33:50

our base case

play33:52

our base case then simply returns the

play33:55

second parameter which is called partial

play33:59

now partial acts as a means

play34:02

of transmitting the partial solution for

play34:05

the factorial

play34:06

computation from one recursive call

play34:10

to the next so

play34:13

when we look then at our recursive case

play34:16

over here we are applying fact helper

play34:19

to first of all as a first parameter

play34:22

in minus one so that decrements are

play34:25

counted by

play34:26

one moving it closer to one

play34:29

and then we compute a new partial result

play34:32

which is in multiplied by the old

play34:35

partial

play34:36

result so let's look at how this

play34:39

factorial function works

play34:41

here we are again computing the

play34:43

factorial of three

play34:44

this then results in us applying fact

play34:47

helper to the parameters

play34:50

three and one which is this initial

play34:52

application of the fact helper function

play34:56

so this then results in the recursive

play34:58

case being

play34:59

executed so now we are applying fact

play35:02

helper

play35:03

to the parameter 2 which is the previous

play35:06

in parameter

play35:07

minus 1 and then the second parameter

play35:10

is 3 multiplied by 1 where 1

play35:14

is the previous partial result

play35:17

so this then results in an application

play35:20

of the fact helper function to the

play35:22

parameters 2

play35:23

and 3 and this then once again results

play35:27

in

play35:27

the recursive step being executed which

play35:30

means we are then applying fact helper

play35:33

to the first parameter of one which is

play35:36

the

play35:37

decrement of the previous in parameter

play35:40

which was two

play35:42

and then the second parameter our new

play35:44

partial

play35:45

is 2 multiplied by 3 where 3 was

play35:49

our previous partial result

play35:52

so now we have an application of the

play35:55

fact helper function

play35:56

to the parameters one and six we now see

play36:00

that we have matched our base case

play36:03

so now the result of this function

play36:05

application

play36:06

is simply partial which in this case is

play36:09

six

play36:10

so that is then the result of our

play36:13

final recursive call we now need to

play36:17

unwind the stack but now we see that we

play36:20

are not

play36:20

computing our solution as we unwind the

play36:24

stack

play36:24

we now just simply return the value six

play36:28

from one call to the next until

play36:31

eventually we get back to

play36:32

our original application of the

play36:35

factorial function

play36:36

to the value of three the result of that

play36:39

function application

play36:41

then is 6. so essentially what we've

play36:44

done

play36:44

here is we've simulated the operation

play36:47

of a loop as it would work in

play36:50

an imperative programming language we

play36:53

have simulated

play36:55

a loop counter variable by means

play36:58

of the n parameter over here which is

play37:01

decremented with each recursive call

play37:04

and then we have a result which is

play37:07

accumulated by means of the second

play37:10

parameter

play37:10

which is partial now what's important to

play37:13

understand here is that

play37:15

our solution by means of implementing

play37:18

the fact helper function

play37:20

is not iterative it is still a recursive

play37:23

solution

play37:24

however the scheme interpreter can then

play37:28

automatically behind the scenes

play37:30

convert this implementation into a

play37:33

standard loop structure and this will

play37:36

then be much more efficient to execute

play37:38

and will also use less memory

play37:43

in the first lecture on this chapter we

play37:45

briefly touched on the fact that

play37:47

functions in scheme are first class

play37:50

entities

play37:51

now amongst other things this means that

play37:54

functions can be passed as parameters to

play37:56

other functions

play37:58

and functions can also return functions

play38:01

as a result so we'll illustrate the

play38:04

power of this concept by means

play38:06

of two functions the first of which is

play38:10

the

play38:10

apply to all functional form so a

play38:13

functional form then

play38:14

is simply a function that either takes

play38:17

one or more

play38:18

functions as parameters or yields

play38:21

a function as a result or alternatively

play38:25

does both of these things so the apply

play38:28

to all functional form then

play38:30

is a functional form that

play38:33

receives a function as a parameter

play38:37

and applies that function to every

play38:39

element

play38:40

within a list and builds up then

play38:43

a new list out of the results of these

play38:46

function

play38:47

applications so let's look at what an

play38:50

implementation of an apply to all

play38:52

functional form would look like

play38:54

here we have one which is a function

play38:58

named map car and it is applied

play39:01

to two parameters the first parameter

play39:04

is the function that we want to apply to

play39:07

the elements

play39:08

of our list and our list is specified as

play39:12

the

play39:12

second parameter so because we are

play39:16

attempting to

play39:17

apply the function to each element

play39:19

within the

play39:21

list this implies that we need to

play39:24

perform repeated operations

play39:26

on the list which means that we have to

play39:29

recursively process the list so that's

play39:31

exactly what we do with the conf

play39:33

function

play39:33

over here and the base case of the conf

play39:37

function

play39:38

is when the list is empty so we use the

play39:41

null function in order to test this

play39:44

if the list is empty then the result is

play39:46

simply an empty list which of course

play39:49

makes sense over here in the

play39:52

else part of the cond function

play39:55

we have the recursive case so the

play39:58

recursive case

play39:59

then simply applies the function that

play40:02

has been passed through

play40:03

as the first parameter for map car

play40:07

to the car of the list the core of the

play40:10

list is of course the first element

play40:12

within the list

play40:13

so this will simply result in the

play40:15

function whatever it is

play40:17

being applied to that first element

play40:20

we then recursively apply the map car

play40:23

function

play40:24

using the same first parameter in other

play40:27

words the same

play40:27

function that has been passed to map car

play40:30

and we

play40:31

apply map card to the cuda of

play40:34

the list so this means we then eliminate

play40:37

the

play40:38

first element the car of the list

play40:40

considering

play40:41

only the remaining elements in the list

play40:44

and we then

play40:45

recursively process those once we have

play40:48

recursively processed the remainder of

play40:50

the list

play40:50

all that remains for us to do is to

play40:52

apply the cons function

play40:54

so that we can add the result

play40:57

of applying the function to the car of

play41:00

the list

play41:01

to the result of applying map car

play41:04

to the remainder of the list

play41:08

so let's look then at how we would use

play41:10

the map car function

play41:12

over here we are applying map car to

play41:15

a function and in this case the function

play41:18

is a

play41:19

lambda expression so in other words it

play41:21

is a nameless

play41:23

function we then have

play41:26

as our second parameter a list of values

play41:29

and note that we have to quote this list

play41:31

of values

play41:31

again and these values then are the

play41:34

numeric

play41:35

atoms 3 4 2 and 6.

play41:39

so our lambda function then doesn't

play41:41

require a name because we are not going

play41:43

to

play41:44

reuse it we're simply applying this

play41:46

function

play41:47

to each of the numbers contained within

play41:49

our list

play41:50

and our lambda function then is applied

play41:53

to a single parameter

play41:54

which is called num and the result of

play41:57

the function application

play41:59

is num multiplied by itself so in other

play42:02

words

play42:02

this lambda function squares its

play42:05

parameter

play42:06

so the result of this thing is that this

play42:08

lambda function will be applied to each

play42:11

value

play42:11

in turn within the parameter list

play42:14

over here and then the map car function

play42:17

will

play42:18

evaluate to a list containing the

play42:21

results of those

play42:22

squaring operations so in other words

play42:24

the result will be

play42:26

a new list which will contain the values

play42:29

9 16 4

play42:32

and 36

play42:36

next we'll look at a simple example of

play42:40

a function that constructs code

play42:43

so a scheme function can actually build

play42:46

scheme code

play42:47

and then request the interpretation of

play42:50

this built scheme code

play42:52

and this is possible and in fact very

play42:55

easy

play42:56

because functions are simply represented

play42:59

as

play42:59

lists as we've previously seen and lists

play43:02

can be

play43:03

returned as results of function

play43:05

applications

play43:06

so therefore we can write functions that

play43:09

can

play43:10

return lists and the interpreter

play43:13

is also a built-in function called

play43:16

eval which the programmer can

play43:19

actually directly invoke

play43:24

over here we have a simple example of a

play43:27

function

play43:27

that builds code and then requests its

play43:31

execution by the interpreter

play43:34

so we define a function over here this

play43:36

function is named adder and it is

play43:38

applied to a single parameter

play43:40

called lis we assume that this list

play43:43

contains only numeric atoms

play43:48

so now we have in the body of our add a

play43:50

function a

play43:51

cond and the first condition within our

play43:54

cond

play43:55

is triggered if the list is empty

play43:58

or null in other words so if the list is

play44:01

then empty then the result

play44:03

of the adder function application is

play44:05

simply zero

play44:07

this of course makes sense because there

play44:09

are no values

play44:10

to add so this case is a trivial case

play44:14

but in all other cases we then trigger

play44:17

the

play44:18

else here so this will be triggered

play44:20

whenever there are

play44:21

numeric elements within the list

play44:25

what we then do is we apply the cons

play44:27

function

play44:28

to the symbol plus and the list that has

play44:32

been passed through as a parameter

play44:33

to the adder function so

play44:37

notice that the plus symbol over here is

play44:40

quoted

play44:41

this is to prevent the scheme

play44:43

interpreter

play44:44

from thinking that it is the function

play44:48

named plus which is built into scheme so

play44:51

in other words we are simply dealing

play44:53

with the

play44:54

symbol plus so the result then of this

play44:57

cons

play44:57

is that the plus symbol is added to the

play45:00

front

play45:01

of the list so for example if the list

play45:03

contains the numeric values

play45:06

2 4 and 8 then the result of the const

play45:10

would be

play45:11

plus 2 4 8. now of course

play45:14

that is a valid scheme

play45:18

function application it is in the form

play45:21

of

play45:22

a list but we can apply the interpreter

play45:25

to it

play45:26

in order to actually compute then the

play45:28

result of that edition

play45:30

so this is done then by applying the

play45:32

eval function to the results of the cons

play45:36

the cons has built up a valid function

play45:39

application and therefore eval when it

play45:42

is executed on that list

play45:45

will then actually execute the

play45:47

interpreter

play45:48

and the result of that will then be that

play45:50

all of the numeric atoms contained

play45:52

within the list

play45:54

will then be added together and we will

play45:57

get a single numeric value

play45:59

as a result so the important thing to

play46:02

understand here

play46:03

is that we use very basic operations to

play46:06

construct

play46:07

lists which have the form of a function

play46:10

application

play46:11

and then we can very easily apply the

play46:13

eval function

play46:14

to these lists and the result will then

play46:18

simply be that the function applications

play46:20

will be

play46:21

evaluated as if they had been provided

play46:24

by the programmer

play46:28

we'll now take a quick look at common

play46:30

lisp which we touched on very briefly in

play46:33

our discussion on

play46:34

chapter two of the textbook so common

play46:37

lisp

play46:37

combines a lot of the features that were

play46:40

included

play46:41

in the popular lisp dialects that were

play46:44

being actively used in the early 1980s

play46:47

because it takes features from a large

play46:49

number of different programming

play46:51

languages

play46:52

it's a very large and very complex

play46:54

language

play46:55

so in a sense it's the opposite of

play46:57

scheme because scheme was designed to be

play47:00

a very stripped down simple and easy to

play47:03

understand

play47:03

programming language common lisp

play47:06

includes

play47:07

a large number of features it has a

play47:09

fairly rich set

play47:10

of data structures which include records

play47:14

arrays and complex numbers which we'll

play47:17

talk about a little bit

play47:18

later on in this course and then also

play47:21

character strings

play47:23

it also provides a set of very powerful

play47:26

io capabilities

play47:27

we saw that scheme provides a fairly

play47:30

simple set

play47:31

of output functions and then common lisp

play47:35

also provides packages

play47:36

which are used to organize functions and

play47:40

data that are related to one another and

play47:42

packages are also used

play47:44

for access control in common lisp

play47:48

common lisp also provides iterative

play47:50

control structures

play47:51

so in a sense it breaks to

play47:55

a degree the concept of a

play47:58

pure functional programming language

play48:00

which

play48:01

scheme does not do because it doesn't

play48:03

include iterative control

play48:05

structures if we look at

play48:09

application areas for functional

play48:11

programming languages

play48:13

we see that functional languages are

play48:15

typically

play48:16

fairly general purpose languages and

play48:19

therefore can be applied

play48:20

to many of the same problem domains

play48:24

as imperative programming languages can

play48:26

be

play48:28

in general however we see that fewer

play48:30

software development

play48:31

projects are implemented in functional

play48:33

languages than imperative languages

play48:36

and this is largely because imperative

play48:38

programming languages are much

play48:39

more popular than functional languages

play48:44

we also saw that pure functional

play48:46

programming languages

play48:48

are in general a little less efficient

play48:51

than

play48:51

imperative programming languages and

play48:54

this is another factor that has impacted

play48:58

on their use in the real world

play49:01

more modern functional programming

play49:03

languages have attempted to address this

play49:05

problem by

play49:06

incorporating features from imperative

play49:08

programming

play49:09

such as loop structures

play49:13

if we look at the lisp programming

play49:15

language we see that most of its

play49:16

application areas have historically been

play49:19

in the field of artificial intelligence

play49:22

and this

play49:22

is because artificial intelligence is

play49:24

the problem domain

play49:26

that lisp was designed for so lisp has

play49:29

been used

play49:30

for things such as knowledge

play49:31

representation which is where we

play49:34

represent knowledge in some way that a

play49:37

computer

play49:38

can process and attempt to understand

play49:42

this was also been used for machine

play49:44

learning applications

play49:46

and then also for natural language

play49:48

processing

play49:50

in natural language processing we try to

play49:52

get a computer

play49:54

to understand and interpret natural

play49:56

language as it is written

play49:58

and spoken by humans lisp has also been

play50:01

used

play50:02

for modeling speech and vision in

play50:05

certain application areas such

play50:07

as robotics we see that lisp has also

play50:10

been used

play50:11

for a few end user

play50:14

software systems so if we look for

play50:17

example at the emacs

play50:19

text editor we see that this text editor

play50:23

provides functionality for the user to

play50:26

modify the text editor and add

play50:29

additional functionality this

play50:31

functionality

play50:32

is added by means of a lisp dialect

play50:37

we also have the maximum mathematics

play50:39

system which

play50:40

is an algebraic processing system

play50:43

similar to

play50:44

the more modern mathematica and this

play50:47

is also programmed using a

play50:50

lisp-like syntax the

play50:53

lisp machine is a hardware platform in

play50:56

which

play50:57

all of the system software is

play50:59

implemented in

play51:00

lisp and this demonstrates that lisp can

play51:03

be used for

play51:04

fairly low level operations such as the

play51:07

implementation

play51:08

of operating system components

play51:12

then if we look at the scheme

play51:14

programming language as i've previously

play51:16

mentioned

play51:17

it has been designed for teaching

play51:19

purposes

play51:21

and as such it is used in a lot of

play51:24

universities and colleges to teach

play51:26

introductory programming concepts

play51:31

finally let's take a look at functional

play51:34

programming languages

play51:36

versus imperative programming languages

play51:39

so we saw in the previous slide that the

play51:40

application areas

play51:42

for imperative and functional languages

play51:45

are relatively

play51:46

similar to each other however the

play51:48

underlying philosophy behind

play51:50

imperative programming languages is

play51:52

fairly different to the philosophy

play51:55

of functional languages so imperative

play51:58

languages

play51:59

are characterized by very efficient

play52:01

execution and this is because they are

play52:04

largely

play52:05

direct reflections of the underlying

play52:07

hardware that they

play52:08

are meant to execute on the

play52:11

semantics and syntax for imperative

play52:15

programming

play52:15

languages are fairly complex and this is

play52:19

due to their reliance on variables

play52:22

also concurrency is not very easy to

play52:25

implement

play52:26

in imperative programming languages and

play52:29

has to be

play52:29

designed by the programmer so the

play52:31

programmer has to worry about things

play52:33

like mutual exclusion locks

play52:35

and so on if we now look at functional

play52:38

programming languages we see

play52:40

that the semantics and syntax for such

play52:43

languages are fairly simple

play52:46

and this is because they use a much more

play52:50

natural approach to describing programs

play52:53

which is much more mathematical

play52:55

rather than machine based however the

play52:58

execution of programs written in

play53:01

functional programming languages is

play53:03

generally a lot less efficient

play53:05

than for imperative programming

play53:07

languages and this

play53:09

is due to the reliance on

play53:12

recursive execution of functions

play53:16

in functional programming languages we

play53:18

also see that concurrency

play53:20

is fairly simple and this is because

play53:23

programs can be

play53:25

automatically made to be concurrent

play53:28

this is due to the fact that we

play53:30

naturally divide our programs up

play53:32

into functions and these functions can

play53:35

then

play53:36

be split up to be executed concurrently

play53:39

or in a distributed fashion

play53:44

so that concludes our discussion on

play53:46

functional programming

play53:47

i'd now like to close with a couple of

play53:50

additional notes

play53:51

related to tests and exams

play53:55

so firstly please note that the slides

play53:58

that i

play53:59

have used for these lectures have been

play54:02

posted

play54:03

on the course website these slides

play54:06

contain

play54:07

additional notes which are fairly

play54:09

extensive

play54:10

particularly in relation to the examples

play54:13

that we discussed within these lectures

play54:16

so if you need some additional

play54:18

explanation for these examples please

play54:20

refer

play54:20

to these notes where you will find

play54:23

step-by-step

play54:24

execution traces and a very detailed

play54:28

explanation

play54:30

then in tests and exams related to

play54:33

this chapter i can ask a couple of

play54:36

things

play54:36

so i can firstly ask theory questions

play54:39

these will

play54:40

not be in the majority because

play54:44

this course focuses more on insight and

play54:47

application i can also provide you with

play54:50

some scheme code and then

play54:52

ask you what the result will be either

play54:54

in terms of

play54:55

output or in terms of the execution

play54:59

of the scheme code i can also ask you to

play55:04

analyze scheme code and potentially

play55:07

correct

play55:08

it as well and then finally very

play55:11

importantly i can ask you to

play55:13

implement scheme functions now

play55:16

in these kinds of questions the

play55:18

implementations will

play55:20

not be more complex than the examples

play55:22

that we've covered

play55:23

in these lectures so be sure that you

play55:27

understand all of the code examples that

play55:29

we've covered

play55:30

and also be sure to do the practical on

play55:34

functional programming which includes

play55:37

a number of exercises and these serve as

play55:40

good examples of the kinds of questions

play55:43

that you can expect

play55:44

in tests and exams

Rate This

5.0 / 5 (0 votes)

Related Tags
Functional ProgrammingScheme LanguageLisp DialectsAI ApplicationsEducational ToolRecursive FunctionsTail RecursionFirst-Class FunctionsCode ConstructionImperative vs Functional