On this page:
12.1 What, if anything, is a zebra?
12.2 Adding stuff to our language
12.2.1 More list/  pair functions
12.2.2 Quotes
12.2.3 Strings
12.2.4 equal?
12.2.5 Recursive functions
12.3 Writing a zebra-program
12.3.1 Some lists
12.3.2 Some helper functions
12.3.3 The requirements
12.3.4 Return a list with all the lists
12.4 Running the zebra-program
12.5 Done?
7.2

12 Towards zebras

If we’ve been through the previous part, we can keep using the same file. Or we can use 7-amb.rkt as our starting point.

If we add a few more things to our language, we can use amb to figure out who owns the zebra.

12.1 What, if anything, is a zebra?

There exists multiple variations of the Zebra Puzzle. We will use the one we find on Wikipedia:
  1. The Englishman lives in the red house.

  2. There are five houses.

  3. The Spaniard owns the dog.

  4. Coffee is drunk in the green house.

  5. The Ukrainian drinks tea.

  6. The green house is immediately to the right of the ivory house.

  7. The Old Gold smoker owns snails.

  8. Kools are smoked in the yellow house.

  9. Milk is drunk in the middle house.

  10. The Norwegian lives in the first house.

  11. The man who smokes Chesterfields lives in the house next to the man with the fox.

  12. Kools are smoked in the house next to the house where the horse is kept.

  13. The Lucky Strike smoker drinks orange juice.

  14. The Japanese smokes Parliaments.

  15. The Norwegian lives next to the blue house.

Now, who drinks water? Who owns the zebra?

12.2 Adding stuff to our language

There’s a few things that are necessary, or at least convenient, for solving the zebra puzzle.

12.2.1 More list/pair functions

Can add some primitives:

12.2.2 Quotes

In Racket, e.g. 'a is translated to (quote a) by the reader. If we want to support quotes in the language we’re making, we can match on (list 'quote exp) and continue with the quoted syntax, exp.

Quoting can be convenient for list-laterals, like '() and '(1 2 3). Also for for symbols, like 'foo.

12.2.3 Strings

Maybe we want strings, for values such as "red" and "zebra". (If we have added quotes to the language, we can choose to use values like 'red and 'zebra instead, and skip the strings.)
If we’re adding strings, we want to recognize string-literals in eval-exp, We can use string? for this, same way we use number? and boolean? for other literals.
We don’t really need to do any fancy string-operations in order to find the zebra. We can totally add some string-functions to our primitives, like string-append and, say, string-upcase, but we don’t have to.

12.2.4 equal?

If we add equal? to the primitives we can use it for checking if things (e.g. strings, symbols) are equal.

12.2.5 Recursive functions

The list is a recursive data structure. We might need recursive functions. Since our lists are Racket lists, our strings are Racket strings, and so on, we can probably get away with writing the recursive functions in Racket and just adding them as primitives. But it would be neat to add support for recursive functions to our language.
One thing we can do is to combine it with adding support for more rackety function definitions, the ones that go (define (function-name arguments ...) body ...). We can add a clause to the match in eval-sequence:

[(list (list 'define (list name params ...) body ...) rest ...) your code here]

Here we must extend the environment with the function definition. To make the possibly recursive function, we make a helper-function:
(define (make-named-function env name parameters body)
  (λ (continue fail . arguments)
    your code here))

This should behave mostly like make-function, only the function should add “itself” to the environment before adding the arguments to the environment. It can create a copy of “itself” by applying make-named-function again. (We’re really using Racket’s support for recursive functions to build our own support for it.)

12.3 Writing a zebra-program

One way to go about this:

12.3.1 Some lists

We make a list of colours, a list of nationalities, and so on. Each element in the each list is an amb-expression with the possible values. (We can optimize a bit: E.g. since the puzzle tells us that the norwegian lives in the first house, we don’t need an amb for the first element in the list of nationalities, and we don’t need to include norwegian as a possible value for the other elements.)

12.3.2 Some helper functions

We probably want some helper functions for stuff like:

12.3.3 The requirements

With the lists and the helper functions we can specify the different requirements.

In order to make the program go faster, we should add the different requirements as soon as possible:

12.3.4 Return a list with all the lists

Return a list with all the lists.

12.4 Running the zebra-program

We can run the program with evaluate to find one solution, and maybe run it with evaluate* to check that there is only one solution.

12.5 Done?

OMG.