haskell - Haskell:使准引用值严格/在编译时评估
问题描述
我有一个“月”类型,大致是
newtype Month = Month Word8
Month
没有导出构造函数的地方;相反,一个函数
mon :: Word8 -> Maybe Month
mon i = if i > 0 && i < 13
then Just $ Month i
else Nothing
被导出,仅当输入值介于 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|]
Where__fromString
解析一个字符串,并返回一个值或调用error
. force
是从Control.DeepSeq
。
现在这很好,但它的主要价值是尽早捕获坏值 - 但是,由于延迟评估,值 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
存在,但我们就假装它不存在。当你使用它时,无论如何你都必须承担破坏一切的责任。)
推荐阅读
- xamarin - 如何在 Xamarin.iOS 中使用共享
- spring-batch - Spring Batch Worker Pod 无法为 Spring Cloud 部署程序 kubernetes 选择自定义服务帐户
- python - Python3 f.write UnicodeEncodeError: 'utf-8' codec can't encode characters surroates not allowed
- mysql - MySQL Workbench - 执行
- java - 无法在 mockito 中模拟方法调用响应
- php - 如何根据PHP中的最新日期对数组进行排序
- android - 是否有适用于 Android 耳塞的开放 API
- python - 是否可以将 Kite 与 VScode 中的 jupyter notebook 扩展集成?
- mysql - 如何选择所有不与另一行为空的行共享列值的行?
- java - 获得两个异常: > 任务:spotlessGroovyGradleCheck FAILED > 任务:generateCucumberReport 在执行 ./gradlew build 时失败