Yes, you can in fact do that with the help of type classes. Whether it's sensible or not is debatable (for your simple Value
type, pattern matching is probably a better solution), but it's still interesting :)
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeSynonymInstances #-}
module Main where
data Value = VStr String | VInt Int | VBool Bool
class FromValue a where
fromValue :: Value -> Maybe a
instance FromValue String where
fromValue (VStr s) = Just s
fromValue _ = Nothing
ensureType :: (Monad m, FromValue a) => Value -> m a
ensureType = maybe (fail "type error!") return . fromValue
interpreterFunction :: Value -> IO ()
interpreterFunction val =
do s <- ensureType val
putStrLn s
main =
do interpreterFunction (VStr "asd")
interpreterFunction (VInt 1)
Prints:
asd
*** Exception: user error (type error!)
You can also make use of the ScopedTypeVariables
extension to force a specific type when it cannot be inferred:
{-# LANGUAGE ScopedTypeVariables #-}
interpreterFunction2 :: Value -> IO ()
interpreterFunction2 val =
do (s :: String) <- ensureType val
print s
By the way, your initial approach seems a bit "unsound" to me:
VStr s <- ensureType p TString
Although you might be sure (by introspection) that ensureType x TString
never returns anything but a string, this is not enforced by the type system and the pattern match is non-exhaustive. That's not a big problem here, but you can easily erradicate the possibility of a runtime failure in that function by using a special "string extraction" function instead:
ensureString :: (Monad m) => Value -> m String
{- ... -}
s <- ensureString val