2016-04-09 5 views
2

Я написал некоторый рабочий оценщик выражений. Однако иногда я получаю исключение:Haskell, простой оценщик выражений с monad Reader

*** Exception: Maybe.fromJust: Nothing 

Я знаю, что это такое. Однако я не могу это решить. Моя цель - возвращение в таком случае Nothing.

Не могли бы вы мне помочь?

type Var = String 

data Exp = EInt Int 
    | EOp Op Exp Exp 
    | EVar Var 
    | ELet Var Exp Exp -- let var = e1 in e2 

data Op = OpAdd | OpMul | OpSub 

evalExpM :: Exp -> Reader (Map.Map String Int) (Maybe Int) 
evalExpM (EInt n) = return $ Just n 
evalExpM (EVar var) = ask >>= (\x -> return (Map.lookup var x)) 
evalExpM (EOp OpAdd e1 e2) = ask >>= (\x -> return (Just ((fromJust (runReader (evalExpM e1) x)) + (fromJust (runReader (evalExpM e2) x))))) 

evalExpM (EOp OpMul e1 e2) = ask >>= (\x -> return (Just ((fromJust (runReader (evalExpM e1) x)) * (fromJust (runReader (evalExpM e2) x))))) 

evalExpM (EOp OpSub e1 e2) = ask >>= (\x -> return (Just ((fromJust (runReader (evalExpM e1) x)) - (fromJust (runReader (evalExpM e2) x))))) 

evalExpM (ELet var e1 e2) = ask >>= (\x -> (local (Map.insert var (fromJust (runReader (evalExpM e1) x))) (evalExpM e2)) >>= (\y -> return y)) 

evalExp :: Exp -> Int 
evalExp exp = fromJust $ runReader (evalExpM exp) Map.empty 
+6

Вместо того чтобы делать 'Just (fromJust Foo + fromJust бар)', попробуйте '(+) <$> Foo <*> bar'. Использование данного приложения позволяет избежать небезопасного использования 'fromJust', который генерирует исключение, которое вы видите, когда оно встречается с« Nothing ». В то время как '(+) <$> foo <*> bar' правильно оценивает 'Nothing', когда foo или bar является' Nothing'. – hao

+0

Спасибо, существует ли способ без аппликативного? Аппликация кажется очень сложной. –

+4

Я согласен. «Аппликативный» может показаться немного сложным вначале - но дайте ему неделю реального использования, и вы, вероятно, найдете его вполне приемлемым (и это определенно стоит изучить). – epsilonhalbe

ответ

3

Во-первых, способ работы с монадических вычислений является не «запустить» вычисление каждый раз, когда вы сталкиваетесь значение, завернутые в монаде (в данном случае Reader ..). Вы должны использовать оператор >>=. Кроме того, вы пытаетесь объединить два монадических эффекта: Reader и Maybe. «Стандартный» способ сделать это - с трансформатором монады, но к счастью для вас Reader (точнее, ReaderT) сам является монадным трансформатором.

Кроме того, вы выиграли бы от некоторой абстракции, то есть:

import Control.Monad.Reader 
import qualified Data.Map as M 

type Env = M.Map String Int 

type EvalM = ReaderT (M.Map String Int) Maybe 

lookupEnv :: String -> EvalM Int 
lookupEnv x = ask >>= lift . M.lookup x 

withBind :: String -> Int -> EvalM a -> EvalM a 
withBind x v = local (M.insert x v) 

Эти функции определяет интерфейс для работы со средами, в которых поиск может потерпеть неудачу. Вы должны написать эти функции один раз, а не указывать их определения, когда они вам понадобятся. Теперь базовые случаи вашей функции тривиальны:

evalExpM :: Exp -> EvalM Int 
evalExpM (EInt n) = return n 
evalExpM (EVar v) = lookupEnv v 

Многие люди (в том числе, вероятно сам) будет использовать аппликативные оператор для рекурсивных случаев, но вы можете избежать супа символов:

evalExpM (EOp op e1 e2) = liftM2 
    (case op of 
    OpAdd -> (+) 
    OpMul -> (-) 
    OpSub -> (-) 
) (evalExpM e1) (evalExpM e2) 
evalExpM (ELet var e1 e2) = evalExpM e1 >>= \e -> withBind var e $ evalExpM e2 

Running это то же самое, что и раньше - просто измените runReader на runReaderT - но теперь вы только когда-либо выполняете вычисления, когда действительно выполняетесь с контекстом.

evalExp :: Exp -> Int 
evalExp e = maybe (error "evalExp") id $ runReaderT (evalExpM e) M.empty 
2

, что вы собираетесь это использовать монаду Maybe, которая автоматически проходит Nothing вниз, так что вам не нужно, чтобы справиться с этим вручную.

поэтому, вы можете использовать тип (ReaderT r Maybe)(a) вместо (Reader r)(Maybe a).

, но я думаю, вы в конечном итоге хотите упростить это до r -> Maybe a.


в любом случае, вы используете Монадой Reader r, который только причудливый способ подачи параметра. потому что r->a является только синтаксическим сахаром для (->) r a и (->) r уже является читателем Monad, вы можете использовать его вместо этого: вы могли бы заменить ask на id.

Вы, вероятно, не хотите использовать глупую функцию ask все время, поэтому вы можете использовать reader, чтобы просто снять функцию.

evalExpM :: Exp -> Reader (Map.Map String Int) (Maybe Int) 
evalExpM (EVar var) = ask >>= (\x -> lift (Map.lookup var x)) 
evalExpM (EVar var) = reader $ \x -> Map.lookup var x 
evalExpM (EVar var) = reader $ Map.lookup var 

удобнее использовать Reader (или ReaderT) только тогда, когда вы хотите, чтобы автоматически передавать из параметров для внутренних функций.но даже тогда вы можете просто использовать Monad (->) r или просто передать параметр. вы получите лучшее чувство для этого, если вы всегда используете Monad (->) r и id вместо Reader r и ask. для ReaderT, с другой стороны, вы увидите его необходимость, когда вам нужно передать один параметр для большинства функций, которые вы используете в нотации .

evalExp :: Exp -> Map.Map String Int -> Maybe Int 

evalExp (EOp OpSub e1 e2) = ask >>= (\x -> return (Just ((fromJust ({-runReader-} (evalExp e1) x)) - (fromJust ({-runReader-} (evalExp e2) x))))) 

evalExp (EOp OpSub e1 e2) = do -- Monad ((->) (Map.Map String Int)) 
    x <- id -- hehe, same as ask 
    return (Just ((fromJust ({-runReader-} (evalExp e1) x)) - (fromJust ({-runReader-} (evalExp e2) x)))) 

evalExp (EOp OpSub e1 e2) x = -- directly with param x 
    (Just ((fromJust ((evalExp e1) x)) - (fromJust ((evalExp e2) x)))) 

evalExp (EOp OpSub e1 e2) x = do -- Monad (Maybe) 
    let a = fromJust ((evalExp e1) x) 
    let b = fromJust ((evalExp e2) x) 
    Just $ a - b 

evalExp (EOp OpSub e1 e2) x = do -- Monad (Maybe) 
    a <- return $ fromJust ((evalExp e1) x) 
    b <- return $ fromJust ((evalExp e2) x) 
    Just $ a - b 

-- and without that bug: 
evalExp (EOp OpSub e1 e2) x = do -- because return=Just 
    a <- evalExp e1 x 
    b <- evalExp e2 x 
    return $ a - b 

evalExp (EOp OpSub e1 e2) = runReaderT $ do -- Monad (ReaderT (Map...) Maybe) 
    a <- ReaderT $ evalExp e1 
    b <- ReaderT $ evalExp e2 -- this is a nice example to use ReaderT 
    return $ a - b 

-- this is ugly unless we need that param wrapped quite often: 
evalExpM :: Exp -> ReaderT (Map.Map String Int) Maybe Int 
evalExpM exp = ReaderT $ evalExp exp 
Смежные вопросы