我正在为Haskell中的动态类型语言编写解释器。
与大多数动态类型语言解释器一样,我的程序也需要在运行时检查类型。我的程序中最常用的代码之一是:
interpreterFunction :: Value -> SomeMonadicContext ReturnType
interpreterFunction p = do
VStr s <- ensureType p TString
..
some code that uses s
我确保p
的类型为TString
,之后我会用VStr s <- ...
对其进行解构。这永远不会失败,因为VStr
是唯一具有TString
类型的值。
我的数据结构基本上是这样的:
data Value = VStr String | VInt Int | VBool Bool
data Type = TStr | TInt | TBool
所以我根据他们的类型分离我的价值观。即。我只有一个值TStr
作为类型的值构造函数。
现在我想知道是否有办法简化我的ensureType
函数和解构代码。例如,这样的事情是可能的:
interpreterFunction p = do
s <- ensureType p
..
same code that uses s
在s <-
之后的代码中,可以推断s
具有类型String
,并且静态地知道只有Value
构造函数具有{{1} } part是String
,因此VStr
在动态检查ensureType
是否为String
后返回p
。
我实际上不知道这是否有意义或可能。我只是想借助Haskell的高级类型系统功能来改进我的设计。
任何帮助将不胜感激。
答案 0 :(得分:3)
是的,你可以在类型类的帮助下实现这一点。无论它是否合理都是有争议的(对于你的简单Value
类型,模式匹配可能是一个更好的解决方案),但它仍然很有趣:)
{-# 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)
打印:
asd
*** Exception: user error (type error!)
您还可以使用ScopedTypeVariables
扩展程序在无法推断时强制使用特定类型:
{-# LANGUAGE ScopedTypeVariables #-}
interpreterFunction2 :: Value -> IO ()
interpreterFunction2 val =
do (s :: String) <- ensureType val
print s
顺便说一句,你最初的做法对我来说似乎有些“不健全”:
VStr s <- ensureType p TString
虽然您可能确定(通过内省)ensureType x TString
永远不会返回除字符串之外的任何内容,但类型系统不会强制执行此操作,并且模式匹配不是详尽无遗的。这不是一个大问题,但您可以通过使用特殊的“字符串提取”功能轻松消除该函数中运行时失败的可能性:
ensureString :: (Monad m) => Value -> m String
{- ... -}
s <- ensureString val
答案 1 :(得分:2)
我认为您定义的标记联合类型
data Value = VStr String | VInt Int | VBool Bool
将您需要的所有运行时类型检查作为普通的旧模式匹配内置,并将其包装在某些高级类型系统功能中缺少一个干净清晰的解决方案:
interpreterFunction :: Value -> SomeMonadicContext ReturnType
interpreterFunction (Vstr s) = do
some code that uses s
interpreterFunction _ = do
some error handling code
你喜欢:
interpreterFunction p = do
s <- ensureType p
..
same code that uses s
说
“这里从s&lt;之后的代码 - 可以推断出s具有String类型,并且静态地知道只有具有String部分的Value构造函数是VStr,所以在动态检查p是否为true后,ensureType返回String一个VStr。
我的版本还会动态检查p是否为VStr。 静态地知道具有String部分的唯一Value构造函数是VStr,但实际上很难利用它。
我们需要创建一个String实例,所以我们需要
{-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-}
class FromValue a where
ensureType :: Value -> SomeMonadicContext a
示例实例:
instance FromValue String where
ensureType (VStr s) = return s
ensureType _ = fail $ unlines
["Yikes, I'd rather have thrown a Nothing than a fail,"
,"but then I'd have to have detagged the Just later"
,"and then I may as well have used solution 1 anyway!"]
instance FromValue Int where
ensureType (VInt i) = return i
ensureType _ = fail "Oh no, do I really have to use catch?"
哪个会给出
interpreterFunction :: Value -> IO String
interpreterFunction p = do
s <- ensureType p
return $ s ++ ", yay!" -- example String-specific code.
这样做的缺点是失败而不是为用户选择适当的响应。您可以使用catch
进行恢复,但同样,您只需要复制模式匹配解决方案提供的可自定义的案例处理功能,就会做很多工作。
<强> 2B。也许吗
在ensureType
中使用Maybe数据类型可以避免恼人的失败/捕获,但是你必须这样做
Just s <- ensureType p
或使用maybe
函数,所有工作至少与普通模式匹配一样多。
或者,如果您的主要目标是使用酷的和更新的类型系统功能,您可以使用GADT复制存在类型,或动态以更结构化,动态类型的方式执行,但不要;这些都没有直接模式匹配的清晰度,简洁性和优雅性。