Paul
Paul

Reputation: 21975

Monadic Haskell operators on custom data types (+ that carries state)

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

Answers (2)

Boyd Stephen Smith Jr.
Boyd Stephen Smith Jr.

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

Cubic
Cubic

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

Related Questions