3

我正在为 Haskell 中的动态类型语言编写解释器。

像大多数动态类型的语言解释器一样,我的程序也需要在运行时检查类型。我的程序中最常用的代码之一是:

interpreterFunction :: Value -> SomeMonadicContext ReturnType
interpreterFunction p = do
    VStr s <- ensureType p TString
    ..
    some code that uses s

在这里我确保它p有 type TString,然后我用VStr s <- .... 这永远不会失败,因为VStr它是唯一具有 type 的值TString

我的数据结构基本上是这样的:

data Value = VStr String | VInt Int | VBool Bool
data Type  = TStr | TInt | TBool

所以我根据它们的类型来区分我的值。IE。我只有一个具有TStras 类型的值构造函数。

现在我想知道是否有办法简化我的ensureType函数和解构代码。例如,这样的事情是否可能:

interpreterFunction p = do
    s <- ensureType p
    ..
    same code that uses s

这里从后面的代码s <-可以推导出它s有类型String,并且静态知道只有Value构造函数有一个String部分VStr,所以动态检查是否是a后ensureType返回。StringpVStr

我实际上不知道这是否有意义或可能。我只是想借助 Haskell 的高级类型系统功能来改进我的设计。

任何帮助将不胜感激。

4

2 回答 2

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
于 2013-02-25T00:04:23.540 回答
2

1.干净,清晰的简单方法:

我认为您定义的标记联合类型

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<-之后的代码可以推断出s的类型是String,并且静态知道只有带有String部分的Value构造函数是VStr,所以ensureType在动态检查p是否为VStr后返回String。

我的版本还动态检查 p 是否为 VStr。静态地知道,唯一具有 String 部分的 Value 构造函数是 VStr,但这实际上很难利用。

2. 不太干净的基于类型类的方式

我们需要创建一个 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。也许?

在其中使用 Maybe 数据类型ensureType可以避免烦人的失败/捕获,但是您必须执行 Just s <- ensureType p或使用该maybe函数,所有这些至少与普通模式匹配一​​样多。

3. 一些聪明而新颖但不合适的东西

或者,如果您的主要目标是使用更酷的和更新的类型系统功能,您可以使用 GADT 来复制存在类型,或者 Dynamic 以更结构化、动态类型的方式来实现,但不要这样做;这些都没有直接模式匹配的清晰、简单和优雅。

于 2013-02-25T02:21:54.850 回答