Haskell:使准引用值严格/在编译时评估

use*_*536 4 haskell lazy-evaluation template-haskell quasiquotes

我有一个“月”类型,大致是

\n\n
newtype Month = Month Word8\n
Run Code Online (Sandbox Code Playgroud)\n\n

Month构造函数未导出的地方;相反,一个函数

\n\n
mon :: Word8 -> Maybe Month\nmon i = if i > 0 && i < 13\n        then Just $ Month i\n        else Nothing\n
Run Code Online (Sandbox Code Playgroud)\n\n

被导出,仅当输入值在 1 和 12 之间(含 1 和 12)时才会返回一个值。

\n\n

现在,使用Language.Haskell.TH.Quote,我定义了一个准引用 ... 运算符?...这允许我“在编译时”“创建” Month 的实例:

\n\n
month :: QuasiQuoter\nmonth = QuasiQuoter { quoteDec  = error "quoteDec not implemented"\n                    , quoteType = error "quoteType not implemented"\n                    , quotePat  = "quotePat not implemented"\n                    , quoteExp = (\\ s \xe2\x86\x92 \xe2\x9f\xa6 force $ __fromString @Month s \xe2\x9f\xa7)\n                    }\n\n\nm :: Month\nm = [month|3|]\n
Run Code Online (Sandbox Code Playgroud)\n\n

where__fromString解析字符串,然后返回一个值或调用error. force来自Control.DeepSeq

\n\n

现在这很好,但它的主要价值是尽早捕获坏值 - 但是,由于惰性求值,值 m 也不会在编译时求值(这将是理想的,但相当也许是一项艰巨的任务)或者至少在运行时的最早阶段。

\n\n

有什么方法可以注释该值(最好在下面的准引用内,以便每次使用都可以month免费获得它;但如果做不到这一点,注释)以强制对程序运行时进行m评估?m需要一个NFData约束或类似的约束就可以了。

\n\n

谢谢,

\n

Car*_*arl 6

您的准引用程序只是通过将所有内容放入引用中来将所有内容推迟到运行时。您需要将解析和验证移到引用之外。

我的概念快速证明:

{-# LANGUAGE TemplateHaskell, DeriveLift #-}
module A ( Month,
           mon,
           month
         ) where

import Text.Read
import Language.Haskell.TH
import Language.Haskell.TH.Syntax (Lift)
import Language.Haskell.TH.Quote

newtype Month = Month Int deriving (Show, Eq, Ord, Lift)

mon :: Int -> Maybe Month
mon n | n >= 1 && n <= 12 = Just $ Month n
      | otherwise = Nothing

monthExpImpl :: String -> Q Exp
monthExpImpl s = case readMaybe s of
  Nothing -> fail "Couldn't parse input as number"
  Just n -> case mon n of
    Nothing -> fail "Not a valid month"
    Just x -> [| x |]

month :: QuasiQuoter
month = QuasiQuoter { quoteDec  = error "quoteDec not implemented"
                    , quoteType = error "quoteType not implemented"
                    , quotePat  = error "quotePat not implemented"
                    , quoteExp = monthExpImpl
                    }
Run Code Online (Sandbox Code Playgroud)

请注意,monthExpImpl将所有逻辑放在引用之外。 fail是终止带有编译错误的操作的推荐方法Q,对于那些习惯于认为fail我们正在远离的历史事故的人来说,这感觉很奇怪。

这里最令人惊讶的部分是DeriveLift扩展及其用于添加到Lift的派生类列表中的用途MonthLiftTH 使用它来将值转换为生成该值的代码。没有它,编译器不知道如何将[| x |]引用变成代码。

您可能想知道 TH 生成调用构造函数的代码有多有效,而该构造函数从生成代码所在的编译单元中不可见。我也想知道。事实证明,只要在 TH 中创建构造函数的代码能够看到构造函数就可以了。在本例中,是Lift实例在执行此操作,并且它是在同一模块中定义的,因此它可以看到构造函数。这可能会让您在创建此类实例时犹豫不决,因为您无法阻止导出实例。这是一个合理的考虑。不过,在这种情况下,这很好,因为lift需要一个值来转换为代码,并且从模块外部获取此类值的唯一*方法mon无论如何都是通过,所以它不会引入任何新的方法来搞乱事情。(我说“仅*”是因为unsafeCoerce存在,但我们假装它不存在。当你使用它时,无论如何你都必须承担破坏一切的责任。)