I'm going through Write Yourself a Scheme in 48 Hours, and in it I've come across some seemingly redundant code; they use @-patterns and then return the value itself, let me explain.
Here's the relevant code:
data LispVal = Atom String
| List [LispVal]
| DottedList [LispVal] LispVal
| Number Integer
| String String
| Bool Bool
eval :: LispVal -> LispVal -- code in question starts here
eval val@(String _) = val
eval val@(Number _) = val
eval val@(Bool _) = val
eval (List [Atom "quote", val]) = val
It seems to me that the entire eval function could just as easily be re-written as
eval :: LispVal -> LispVal
eval (List [Atom "quote", val]) = val
eval val = val
And have the bottom case account for all the @-patterns in the original code.
Am I mistaken in thinking this, and is there an actual benefit of doing it the way they did? Or is the other way more concise?
One difference is that the original code is undefined for values constructed with Atom
, i.e. there is no line
eval val@(Atom _) = val
And whether this is a copy’n’paste error or not, it highlights the important difference in style:
The first style encourages you to think about each value individually, making an explicit assertion “this is the right equation for this”. If you later add more constructors t othe LispVal
type, you get runtime errors (or compiler warnings with -fwarn-incomplete-patterns
, which is good practice).
The second style asserts: eval
will only have to look at List
values, and all others can be treated individually. In particular, later additions to the data type should work just as well, and you don’t want to be bothered about this function then.
Operationally, they are equivalent.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With