Haskell - 帮助简化具有高级类型功能的函数

时间:2013-02-24 19:51:08

标签: haskell interpreter dynamic-typing

我正在为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的高级类型系统功能来改进我的设计。

任何帮助将不胜感激。

2 个答案:

答案 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)

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

我的版本还会动态检查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。也许吗

ensureType中使用Maybe数据类型可以避免恼人的失败/捕获,但是你必须这样做 Just s <- ensureType p或使用maybe函数,所有工作至少与普通模式匹配一​​样多。

3。什么是聪明的,新的但不合适的

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