【问题标题】:Haskell - help simplifying a function with advanced type featuresHaskell - 使用高级类型功能帮助简化功能
【发布时间】:2013-02-24 19:51:07
【问题描述】:

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

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

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

这里我确保p 具有TString 类型,然后我使用VStr s &lt;- ... 对其进行解构。这永远不会失败,因为VStr 是唯一具有TString 类型的值。

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

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

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

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

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

这里从s &lt;-之后的代码可以推导出s的类型是String,并且静态知道只有Value构造函数有String部分是VStr,所以@ 987654339@ 在动态检查p 是否为VStr 后返回String

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

任何帮助将不胜感激。

【问题讨论】:

  • 这可能有助于描述ensureType 在存在非TString 数据时的效果。
  • 目前看来您可以使用模式匹配将其重写为interpreterFunction (VStr s) = some code that uses s,后跟interpreterFunction _ = some error handling code。否则我认为您应该仔细查看pattern guards,因为它们可能会对您的代码产生澄清作用。
  • 您的要求是有道理的,听起来TypeFamilies 就是您正在寻找的(尽管不一定是唯一或最佳的解决方案)。它们允许您定义“类型函数”(例如,从 StringVStr,其中 VStr 是一种新类型),可以是单射的,也可以不是单射的。
  • @AndrewC,简单的模式匹配对我不起作用,因为我在问题中没有提到一些细节。基本上我也想从它到throwError $ TypeError expected found 做一些其他的事情。
  • @jberryman 谁能给我一些关于 TypeFamilies 如何提供帮助的建议?

标签: haskell interpreter dynamic-typing


【解决方案1】:

是的,您实际上可以借助类型类来做到这一点。是否明智值得商榷(对于您简单的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

【讨论】:

  • 太好了,这个解决方案对我很有用,也很简单。
【解决方案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之后的代码

我的版本还动态检查 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 &lt;- ensureType p 或使用 maybe 函数,至少与普通模式匹配一​​样有效。

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

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

【讨论】:

  • 非常感谢您的回答。也许我应该添加一两个词来说明为什么模式匹配在这里不能解决我的问题,但我只是想让问题简短一些。
  • @sinan 啊-也许在要求回答您的问题下的 cmets 后,坚持刷新页面一段时间,这样您就可以获得更多量身定制的答案。刚才没时间给你一个类型家庭的答案,但是很有趣。也许您可以在问题的底部添加“编辑:我也对是否存在类型族解决方案感兴趣”,以防引起其他人的兴趣。 Pob hwyl。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-02-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多