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

标签 haskell lazy-evaluation template-haskell quasiquotes

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

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解析字符串,并返回一个值或调用errorforce 来自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/

相关文章:

haskell - 什么是 "free variable"?

list - 在 Haskell 中将已知长度的列表转换为嵌套对的最简单方法是什么?

haskell - printf返回了什么?

编译用 haskell 和模板 haskell 编写的共享对象并将其与 c 中的 main 链接

list - Haskell 从文件列表中读取矩阵并使用它

haskell - 此列表如何理解其自身的单位?

scala - 合并两个流(有序)以获得最终排序的流

nhibernate - 强制为一个实例加载所有 nhibernate 代理

generics - 如何在 Haskell 中具体化/遍历记录定义

haskell - 如何规避 GHC 阶段限制?