Thinking in Patterns
We've learned how to get a description of all places, but how do we get just the description of just the place where our player is? We can apply our new knowledge:
- Let's write a function that checks the player location against a
placerecord to see if they're the same.
- Then, let's use that function with a list of place records!
Here's the first part:
(defun here? ((loc (match-place name place-name)) (when (== loc place-name)) 'true) ((_ _) 'false))
That looks pretty wild, so let's pause while we go through it.
LFE supports something called pattern matching thanks to its heritage from Erlang (which got it from Prolog). Many of the Lisp forms in LFE support pattern matching, and one of those is a function definition: you can put patterns in a function's arguments when you define it. However, when you do this, you need to make some changes. That's why the function above looks funny, because of these changes: an extra set of parentheses is needed (if you're wondering why, it's because instead of just one set of arguments and a function body, patterns allow for many sets of arguments and corresponding bodies ... so the extra set of parentheses is like a list of function definitions).
Functions without pattern matching in their arguments look like this, as we saw previously:
(defun <name> (<arg> ...) <body>)
Whereas functions with pattern matching in their arguments look like this:
(defun <name> ((<pattern>) body) ((<pattern>) body) ...)
You can have as many different patterns and associated function bodies as you want -- as long as they all have the same number of arguments (also known as arity). Our function has two arguments (2-arity): a location
loc and a pattern matching against a
place record. Our pattern was a call to one of the magical functions created by our
match-place. So what got filled in the
<pattern> slot was a
(match-place ...) call, and that explains why you saw three opening parentheses in a row.
But we've got something else new there, too: the
when form. When you see a
(when ...) after a pattern in LFE, it's called a guard. This guard is standing watch over the pattern, and will only let the pattern match if the location
loc that was passed as a regular function argument is the same as the
That takes care of the first pattern and function body. The second function body has a pattern that seems rather strange:
(_ _). Then the body simply returns
false no matter what! In this case, the pattern is saying "I don't care what the values are for the two arguments that are getting passed to me, just move on to the body." And, as we noticed, the body then just returns
false. Thus, the overall meaning of this function is "if the first pattern is matched, the definition of "here" is met and return
true; anything else does not meet the definition of "here", therefore return
Let's try it out against the first place in our
> (here? 'living-room (car (state-places state))) true > (here? 'attic (car (state-places state))) false
here? takes two arguments: a location name and a place record. Remember we wanted to be able to handle a list of places. We used
lists:map before, but that's not exactly what we want here. What would be perfect is if we could find a function that would only return the items of a list that met certain criteria (in this case, the criteria is that the player location and the place record name are the same!).
It turns out there is exactly this function in the Erlang standard library: the
lists:filter takes two arguments:
- A predicate function (a function that returns
false), given some input, and
- A list of inputs to give the predicate function.
Let's create a function which uses
lists:filter to only return the place that returns
true when we ask
(defun get-here (((match-state player-location player-loc places locs)) (car (lists:filter (lambda (loc) (here? player-loc loc)) locs))))
We used pattern matching again, but this time to do something a tiny bit clever: we used it to define the variables
locs. In other words, with our pattern above, we said "When you get a
state record coming through here, get its
player-location field and assign it to the
player-loc variable; also get its
places field and assign it to the
Now we can use this function to get the description of the player location:
(defun describe-location (game-state) (++ (place-description (get-here game-state)) "\n"))
Now let's use our new function:
> (describe-location state)
"You are in the living-room of a wizard's house. There is a wizard snoring loudly on the couch."
Perfect! Just what we wanted.