我有一个“月”类型,大约是
newtype Month = Month Word8
其中 Month
构造函数未导出;相反,一个函数
mon :: Word8 -> Maybe Month
mon i = if i > 0 && i < 13
then Just $ Month i
else Nothing
被导出,仅当输入值在 1 和 12 之间(含 1 和 12)时才会返回值。
现在,使用Language.Haskell.TH.Quote
,我定义了一个准引用...运算符? ...这允许我“在编译时”“创建” Month 实例:
month :: QuasiQuoter
month = QuasiQuoter { quoteDec = error "quoteDec not implemented"
, quoteType = error "quoteType not implemented"
, quotePat = "quotePat not implemented"
, quoteExp = (\ s → ⟦ force $ __fromString @Month s ⟧)
}
m :: Month
m = [month|3|]
其中__fromString
解析字符串,并返回一个值或调用error
。 force
来自Control.DeepSeq
。
现在这很好,但是它的主要值(value)是尽早捕获坏值 - 但是,由于惰性求值,值 m 也不会在编译时求值(这将是理想的,但也许是一个相当艰巨的任务)或者至少在运行时的最早阶段。
有什么方法可以注释该值(最好在下面的准引用内,以便每次使用 month
都可以免费获得它;但如果失败的话,注释 m
) 在程序运行时强制对 m
求值?需要 NFData 约束或类似约束就可以了。
谢谢
最佳答案
您的准引用程序只是通过将所有内容放入引用中来将所有内容推迟到运行时。您需要将解析和验证移到引用之外。
我的概念快速证明:
{-# 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
}
请注意,monthExpImpl
将所有逻辑置于引用之外。 fail
是终止带有编译错误的 Q
操作的推荐方法,对于习惯于将 fail
视为历史事件的人来说,这感觉很奇怪我们正在远离的事故。
这里最令人惊讶的地方是 DeriveLift
扩展及其用于将 Lift
添加到 Month
的派生类列表中的用途。 Lift
TH 使用它来将值转换为生成该值的代码。没有它,编译器不知道如何生成 [| x |]
引用到代码中。
您可能想知道 TH 生成调用构造函数的代码有多有效,而该构造函数从生成代码所在的编译单元中不可见。我也想知道。事实证明,只要在 TH 中创建构造函数的代码可以看到构造函数,就可以了。在本例中,是 Lift
实例执行此操作,并且它是在同一模块中定义的,因此它可以看到构造函数。这可能会让您在创建此类实例时犹豫不决,因为您无法阻止导出实例。这是一个合理的考虑。不过,在这种情况下,没关系,因为 lift
需要一个值来转换为代码,而从模块外部获取此类值的唯一*方法是通过 mon
无论如何,所以它不会引入任何新的方法来把事情搞砸。 (我说“only*”是因为 unsafeCoerce
存在,但我们假设它不存在。当你使用它时,无论如何你都必须承担破坏一切的责任。)
关于Haskell:在编译时严格/评估准引用值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59399050/