Reputation: 73
I want to write a function that will collect the variable names that have been used in a lambda calculus term, both as a Variable and in a Lambda abstraction.
For example in the code I would get the following output:
*Main> used example
["a","b","x","y"]
I want to return them in an ordered list. I think I could achieve this using pattern matching and wild cards, is this possible?
import Data.Char
import Data.List
type Var = String
data Term =
Variable Var
| Lambda Var Term
| Apply Term Term
example :: Term
example = Lambda "a" (Lambda "x" (Apply (Apply (Lambda "y" (Variable "a"))
(Variable "x"))
(Variable "b")))
-- I'll use this function to merge two ordered lists together:
merge :: Ord a => [a] -> [a] -> [a]
merge xs [] = xs
merge [] ys = ys
merge (x:xs) (y:ys)
| x == y = x : merge xs ys
| x <= y = x : merge xs (y:ys)
| otherwise = y : merge (x:xs) ys
used :: Term -> [Var]
used [] = []
used Variable_ = _
used Lambda_ = _
used Apply_ = _
This is clearly not correct.
How do I collect the variable names and add them to a list? If pattern matching and wild cards are not correct, please can you suggest how I could go about this?
Upvotes: 1
Views: 915
Reputation: 1525
So used
is a function Term -> [Var]
. And so to write used
in a pattern matching style, we'll need to make a case for each of the different ways to be a Term
. Looking at the definition of Term
we see there are 3 ways to be a Term
: by being a variable, a lambda, or an apply. In particular being an empty list ([]
) is not a way to be a Term
, so that case should be removed.
Next lets look at the syntax of pattern matching. It should look like the following:
used :: Term -> [Var]
used (Variable var) = ...
used (Lambda var term) = ...
used (Apply term1 term2) = ...
Note the parenthesis around each set of data constructor and arguments. Also note that variables in Haskell must start with lowercase characters. Finally note that we haven't actually written any of the bodies of the cases for used
. The above is not actually valid Haskell.
Let's go over how to fill in all the right hand sides of these cases. While we do so let's keep in mind that used
is meant to return an ordered list of Var
without repeats. Most of that isn't captured in the type of used
.
Variable
case:used (Variable var) = ...
So the right hand side here has to be a list of Var
. Checking the declaration of Term
and the Variable
case, we see that var
must be a Var
. Thus, really the only option for the implementation is:
used (Varable var) = [var]
Lambda
caseused (Lambda var term) = ...
So again checking the type declaration of Term
we see that var
is a Var
and term
is a Term
. Maybe that's why we used those variable names ;). So some variables appear in var
and some variables appear in term
. If we knew all of those variables we could put those results together with merge
. For var
we can do the same thing as in the Variable
case. For term
we need a way to turn a Term
into a variable or a list of variables. Luckily we have such a way. It's the function we're currently defining: used
! So we can write this case as:
used (Lambda var term) = merge [var] (used term)
It might look a bit silly to use merge
to merge a singleton list into another list, but we need to keep the result ordered and merge
is the hammer we have so we turned this problem into a nail.
Apply
caseThis case will go quite quickly using the same idea as above. We have
used (Apply term1 term2) = ...
term1
and term2
are Term
's. We'll extract the variables from each of them using used
and merge them together using merge
.
used (Apply term1 term2) = merge (used term1) (used term2)
Overall this gives the implementation of used
:
used :: Term -> [Var]
used (Variable var) = [var]
used (Lambda var term) = merge [var] (used term)
used (Apply term1 term2) = merge (used term1) (used term2)
with
*Main> used example
["a","b","x","y"]
as desired.
Upvotes: 3