自定义数据类型的Monadic Haskell运算符(带有状态的+)

时间:2014-07-31 09:30:10

标签: haskell error-handling monads

我正在关注Write Yourself a Scheme in 48 Hours教程并给出下面的代码我稍微绕道而行,以便能够运行(+ 4 4.0)之类的东西(我添加了对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

我想知道我是否能以某种方式使+运算符等同于上面的plusLispVal函数,也就是说,使它成为monadic所以我可以用它传递错误状态,我想这会使我的代码更清洁,我也可以免费获得减法(和其他操作)。

示例:

*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"

2 个答案:

答案 0 :(得分:5)

没有。 +的类型为Num a => a -> a -> a,即如果您的信息未包含在其中一个参数中,则它也不会出现在结果中。您可以做的事情是解除它:liftM2 (+) :: (Monad m, Num a) => m a -> m a -> m a,或者您可以引入一个类似于+的函数,如果这是(+!) = plusLispVal之后的那个。 / p>

你可能想要提升+版本,否则你无法链接添加(和其他操作)(同样,你的Num实例似乎缺少fromIntegral实现)

答案 1 :(得分:0)

是的,通过重构代码以便LispError可以放在LispVal中,也许可以通过添加如下构造函数来实现:

data LispVal = Atom String
             | List [LispVal]
             | DottedList [LispVal] LispVal
             | Number Integer
             | Float Float
             | String String
             | Bool Bool
             | Error LispError

然后,您可以为Num撰写LispVal个实例。

或者,您可以为Num编写ThrowsError LispVal个实例,并像return x + return y一样使用它。