When circumstances allow, I'll be typing up bits of my lecture notes and posting them online. These may or may not bear any resemblance to the actual lectures.
Here's a flowchart describing another version of the maze game. Can you write a game that matches this flowchart?
This flowchart is similar to the haunted house flowchart. In the haunted house flowchart there are loops from the entryway procedure through the kitchen and living room procedures and back to the entryway procedure. In the new maze game flowchart there's a loop from turning left back to being lost in the maze. The easiest way to write a program that follows this flowchart is to write a procedure that is its own helper procedure.
to maze4
print [You are lost in a maze. Will you go left or right?]
ifelse (equalp readword "left)~
[maze4]~
[print [You fell in a pit! Do you want to play again?]
if equalp readword "yes [maze4]
]
end
Here, maze4 uses maze4 as a helper procedure.
Programs in which a procedure (or one of its helpers) uses itself as a
helper procedrue are called recursive. Recursive procedures
are the way functional programmers implement the tasks we recently
used for and while to perform. As with
the while command, recursive procedures can get caught in an
"infinite loop". They can also be difficult to debug -- you may need
to use the trace and step commands to figure out why
a recursive procedure isn't working.One common bug is that the "rest of the procedure" runs over and over. This is usually caused by using if rather than ifelse at a decision point in a recursive procedure. Remember that when the the if statement completes, the computer will move on to run the next line of Logo code, even if you don't want it to!
Below is a recursive version of a procedure which counts down to 0. Save your work before running it:
to countdown.recursive :number print :number countdown.recursive (:number-1) endUse the Logo menu to stop Logo. What went wrong?
Page 137 of Chapter 7 discusses the need for a stop rule that detects when a program has finished. Like while recursive procedures keep going until something stops them. Unlike while, recursive procedures don't have a built-in instruction that tells them when to stop. You have to figure out when to stop them and write code to do it yourself.
In the case of countdown.recursive, you want to stop when the count reaches zero. Since your input might not be a positive integer, it's a good idea to play it safe and stop when the count is less than or equal to zero:
to countdown.recursive :number
ifelse (greaterp :number 0)~
[print :number
countdown.recursive (:number-1)
]~
[print "Blastoff!]
end
Note that you have to check to see if it's time to
stop before the procedure runs itself as a helper procedure.
Otherwise it goes into an infinite loop and doesn't stop to check if
it's done. The procedure below provides an example of this common
mistake -- save your work before running it.
to countdown.recursive2 :number print :number countdown.recursive2 (:number-1) if (lessp :number 1) [print "Blastoff! stop] endWe used an if command to see if the countdown was finished. We could also have used ifelse, as in the example in the textbook.
to countdown.recursive3 :number
ifelse (greaterp :number 1)~
[print :number
countdown.recursive3 (:number-1)
]~
[print "Blastoff!]
end
To summarize: A recursive procedure is one that uses itself as a helper procedure.
Every recursive procedure must have a stop condition (to prevent infinite loops) and
a self-reference. See Chapter 7 for more examples of recursive procedures.
to initials.recursive :name
ifelse (emptyp :name)~
[output []]~
[output sentence (first (first :name)) (initials.recursive butfirst :name)]
end
This is almost as short as using map to solve this problem, but recursion is as powerful and flexible as while! No wonder functional programmers think this is great stuff. However, recursive procedures can be harder to read and understand than ones that use for or while.Compare this procedure to initials.for from Lecture 11.
to initials.for :name
; code copied from page 78 of Computer Science Logo Style
local "result
make "result []
for [i 1 [count :name]] ~
[make "result sentence :result first (item :i :name)]
output :result
end
In initals.for, we start with an empty list and add initials to it. In initials.recursive, we start with an initial -- (first (first :name)) -- and add the list of all the rest of the intials to it -- (initials.recursive butfirst :name). We're making a big assumption here; the author calls it a "leap of faith". We're assuming that procedure initials.recursive works, and that it will output the list of initials [L B] when it is given the input [Louise Burgiel]. This is typical of recursive procedures.In initials.recursive we end with the empty list []. This is the output we get if we run procedure initials.recursive with the empty list as input. This is our stop condition! Our recursive step uses intials.recursive, giving it as input a name minus its first item. The value of :name input to initials.recursive gets shorter and shorter until it is just []. At this point the stop condition is achieved and initials.recursive outputs the empty list -- the name [] has no initials.
Try using trace with initials.recursive to get a feel for how it works.