Reputation: 21975
I'm following the Write Yourself a Scheme in 48 Hours tutorial and given the code below I took a little detour to be able to run things like (+ 4 4.0)
(I added support for Floats):
import Control.Monad.Except
import Text.ParserCombinators.Parsec hiding (spaces)
data LispError = NumArgs Integer [LispVal]
| TypeMismatch String LispVal
| Parser ParseError
| BadSpecialForm String LispVal
| NotFunction String String
| UnboundVar String String
| Default String
type ThrowsError = Either LispError
data LispVal = Atom String
| List [LispVal]
| DottedList [LispVal] LispVal
| Number Integer
| Float Float
| String String
| Bool Bool
instance Show LispVal where
show = showVal
instance Show LispError where
show = showErr
showVal :: LispVal -> String
showVal (Number x) = show x
-- ...
showErr :: LispError -> String
showErr (TypeMismatch expected found) = "Invalid type, expected: " ++ expected ++ ", found: " ++ show found
showErr (Default message) = "Error: " ++ message
-- ...
instance Num LispVal where
(Number x) + (Number y) = Number $ x + y
(Float x) + (Float y) = Float $ x + y
(Number x) + (Float y) = Float $ (fromInteger x) + y
(Float x) + (Number y) = Float $ x + (fromInteger y)
plusLispVal :: LispVal -> LispVal -> ThrowsError LispVal
(Number x) `plusLispVal` (Number y) = return . Number $ x + y
(Float x) `plusLispVal` (Float y) = return . Float $ x + y
(Number x) `plusLispVal` (Float y) = return . Float $ (fromInteger x) + y
(Float x) `plusLispVal` (Number y) = return . Float $ x + (fromInteger y)
x `plusLispVal` (Number _) = throwError $ TypeMismatch "number" x
x `plusLispVal` (Float _) = throwError $ TypeMismatch "number" x
(Number _) `plusLispVal` x = throwError $ TypeMismatch "number" x
(Float _) `plusLispVal` x = throwError $ TypeMismatch "number" x
x `plusLispVal` y = throwError $ Default $ "+ expects numbers, given: " ++ show x ++ " and " ++ show y
I'm wondering if I could somehow make the +
operator equivalent to the plusLispVal
function above, that is, make it monadic so I can pass the error state with it, I think this would make my code a bit cleaner and also I could benefit of subtraction (and other operations) for free.
Example:
*Main> (Number 2) + (String "asd")
*** Exception: asd.hs:(51,5)-(54,56): Non-exhaustive patterns in function +
*Main> (Number 2) `plusLispVal` (String "asd")
Left Invalid type, expected: number, found: "asd"
Upvotes: 1
Views: 209
Reputation: 3202
Yes, by refactoring your code so that a LispError
can fit in a LispVal
, perhaps by adding a constructor like so:
data LispVal = Atom String
| List [LispVal]
| DottedList [LispVal] LispVal
| Number Integer
| Float Float
| String String
| Bool Bool
| Error LispError
Then, you can write up a Num
instance for LispVal
.
Alternatively, you could write a Num
instance for ThrowsError LispVal
and use it like return x + return y
.
Upvotes: 0
Reputation: 15673
No. +
has the type Num a => a -> a -> a
, that is if your information isn't contained in one of the parameters, it can't be in the result either. What you can do is lift it: liftM2 (+) :: (Monad m, Num a) => m a -> m a -> m a
, or you can introduce a function that kinda looks like +
if that's what you're after (+!) = plusLispVal
.
You might wanna have a lifted version of + lying around anyway because otherwise you can't chain additions (and other operations) (also, your Num
instance seems to be lacking a fromIntegral
implementation).
Upvotes: 5