A few days ago I asked students to use the Racket library for ASIP to control the movement of a MIRTO robot to explore an unknown area, as follows:
- The robot should start by moving forward.
- When the robot hits an obstacle, it should stop immediately and move backward for 0.5 seconds
- At this point, the robot should perform a left or a right rotation (randomly), and then restart and move forward until the next obstacle.
- As an additional feature, the amount of the rotation should be random as well, say between 0.3 and 1.5 seconds (not needed for this discussion).
A group of students wrote two functions, one to rotate left and one to rotate right, something similar to the following:
#lang racket (define moveLeft (λ () ;; code here to move left, using the ;; racket-asip library (printf "The robot moves left\n") ) ) (define moveRight (λ () ;; code here to move right, using the ;; racket-asip library (printf "The robot moves right\n") ) ) (list-ref (list (moveLeft) (moveRight)) (random 2))
The key part here is the very last line: the students defined a list of two functions with (list (moveLeft) (moveRight)) and then used list-ref to get one element from this list at a position which is randomly 0 or 1, depending on the result of (random 2). I really liked their idea and I was very happy to see that they were thinking “in a functional style“.
There is, however, a problem with the code above: if you try to run it, the robot will move both left and right. If you copy-paste the code above in DrRacket, you will see that, when you run it, both statements “The robot moves left” and “The robot moves right” will be printed. The students called me and I suggested a solution that – I now realise – was affected by my many years of imperative-style programming… Instead of going through the solution I suggested in the lab, let’s see how we can fix the code above in a more appropriate way. Let’s start from the original problem: try the following code in DrRacket
(list (+ 1 2) (+ 2 3))
You should obtain a list with values 3 and 5. That is to say: the list contains the results of the operation. The same thing happens in the code above: writing (list (moveLeft) (moveRight)) produces a list that contains the result of invoking moveLeft and moveRight. Effectively, this means printing something on screen, because the functions have a side effect. But what is the content of the list? It is just the value returned by the two functions: in this case, it is void, because printf returns void, and as a result choosing a random value from a list of voids returns nothing!
What should been done is to build a list of references to the functions, and this is very easy: just remove the brackets around them, and the references will not be executed! Try the following in DrRacket:
(list moveLeft moveRight)
Now you should see ''(#<procedure:moveLeft> #<procedure:moveRight>): that’s correct! This is a list of two procedures! What happens if you try now to get a random value? Try the following code a few times:
(list-ref (list moveLeft moveRight) (random 2))
Sometimes you will get a reference to moveLeft, and sometimes you will get a reference to moveRight. Hey, it works! But wait, we are not done yet: we have a reference to a function, but how do we call it now? This is the easy part… If you have a function, how do you ask Racket to “execute” it? You put it in brackets, so overall you just need to surround the list-ref command with another pair of brackets, and the reference to the function selected randomly from a list will be executed:
( (list-ref (list moveLeft moveRight) (random 2)) )
If you had an actual robot, you would see that the robot now moves sometimes left, and sometimes right. If you don’t have robot, try the code above a few times and you will see that sometimes it prints “The robot moves left”, and sometimes it prints “The robot moves right”. You can obviously extend the pattern to a list of functions of arbitrary length and obtain a much shorter code compared to using a switch statement.
this is Piotr, SLA for first year Computer Science.
That’s how I would probably approach the problem:
(define moveRight (λ () (printf “Move right.”)))
(define moveLeft (λ () (printf “Move left.”)))
(define randomDirection (λ ()
(let ((dir (random 2)))
(cond [(equal? dir 0) (moveRight)]
[(equal? dir 1) (moveLeft)]))))