Context
У меня есть несколько функций monadi c для интерпретатора, которые я пытаюсь проверить с помощью HSpe c. Они работают со следующим стеком монад:
type AppState = StateT InterpreterState (ExceptT Events IO)
type AppReturn a = Either Events (a, PState)
runApp :: AppState a -> IO (AppReturn a)
runApp f = runExceptT (runStateT f new)
Вот простой пример:
mustEvalExpr :: Expr -> S.AppState (S.Value)
mustEvalExpr e = do
val <- evalExpr e
case val of
Just val' -> return val'
Nothing -> throw $ S.CannotEval e
Проблема в том, что HSpe c имеет свой собственный контекст (IO ()
), поэтому мне приходится переводить между двумя контекстами.
Текущий подход
Я использую HSpe c, и я написал функцию преобразователя для получения контекста runApp
изнутри контекст HSpe c.
-- imports omitted
extract :: S.AppReturn a -> a
extract st = case st of
Right (a, _) -> a
Left ev -> throw ev
run :: (S.AppReturn a -> b) -> S.AppState a -> IO b
run extractor fn = do
state <- S.runApp fn
return $ extractor state
Итак, мой Spec
выглядит следующим образом:
spec :: Spec
spec = do
describe "mustEvalExpr" $ do
let badExpr = D.VarExpr $ D.Id "doesntExist"
goodExpr = D.IntExpr 1
val = S.IntValue 1
it "should evaluate and return expression if its a Just" $ do
(run extract $ do
I.mustEvalExpr goodExpr
) >>= (`shouldBe` val)
it "should throw error if it gets a Nothing" $ do
(run extract $ do
I.mustEvalExpr badExpr
) `shouldThrow` anyException
Вопрос
Это лучшее, что я могу сделать? Я чувствую, что run extract $ do
- это хорошо, и я думаю, что хорошо быть понятным, когда все сложно.
Но мне было интересно, есть ли способ, которым я могу интегрироваться с HSpe c, или есть ли наилучшая практика для этой проблемы, которая не требует специального кода?