rex*_*rex 5 printf haskell polyvariadic
我正在尝试编写以下函数,以便可以将其与Text.Printf.printf的可变数量的参数一起使用。我尝试使用类型类,但找不到任何不逐个字符和逐个参数处理格式字符串的示例。我希望能够捕获所有参数并将其立即传递给 printf 。
-- panic :: PrintfType r -> Int -> String -> r
panic lvl fmt =
let fail :: String -> String -> a
fail s s' = error $ printf "%s: %s" s s'
in flip fail (show lvl) . printf fmt
Run Code Online (Sandbox Code Playgroud)
我想使用这样的函数:
panic 0 "Err" -- Exception: "Err: 0"
panic 1 "Err %s %d" "fatal" 42 -- Exception: "Err fatal 42: 1"
Run Code Online (Sandbox Code Playgroud)
我应该如何继续实施?
这是可以做到的,但正如评论中所指出的,一切Text.Printf都是黑客。黑客是通过类型类解析来完成的。printf有一个名为的类型类(其实现未公开),PrintfType它为基本类型(IO ()和String)实现,也为函数类型实现a -> r,这允许我们假装 Haskell 有可变参数。
就像我说的,类型类实现没有公开。但幸运的是,我们可以自己编写。首先,让我们把标题移开。
\n{-# LANGUAGE FlexibleInstances, FlexibleContexts, UndecidableInstances, TypeFamilies #-}\nRun Code Online (Sandbox Code Playgroud)\n与高级类型类功能一样,我们需要一些编译器扩展。现在这是我们要处理的类。
\nclass PanicType t where\n type Prev t\n spr :: (String -> Void) -> Prev t -> t\nRun Code Online (Sandbox Code Playgroud)\n我们的函数spr采用后处理函数,String -> Void该函数将通过调用 来获取“已编译”的字符串并触底error。然后我们进行计算的“上一步”并生成 a t,它可能需要一些参数并最终也会生成 a Void。(我们不会Void在类型类中对“最终”约束进行编码,但我们编写的实例将迫使我们无论如何都这样做)。
作为一个插曲,这是我们必须做出的一个让步。理想情况下,您的恐慌函数最终会返回forall b. b。我不知道有什么好方法可以让可变参数函数做到这一点,因为会有大量重叠的类型类(每个类型类实例都必然与我们的“包罗万象”的类型类实例重叠)。这也许是可行的,但每次我在 Haskell 的那OverlappingInstances一边闲逛时,都会以泪水结束。
现在我们需要这个类型类的两个实例。我正在使用所有不错的“灵活”GHC 扩展,因此我们不必像printf以前那样做太多的黑客工作。一个例子是 for Void,我们在传递参数结束时遇到的基本术语。另一个将支持a -> r并承认另一个论点。
instance PanicType Void where\n type Prev Void = String\n spr post k = post k\nRun Code Online (Sandbox Code Playgroud)\n当我们触及基础术语时,我们可以假设“我们已经建立的东西”延续参数是一个String。error因此,我们只需调用我们的后处理器(实际上,这将是-with-benefits)。
instance (PrintfArg a, PanicType r, PrintfType (Prev r)) => PanicType (a -> r) where\n type Prev (a -> r) = (a -> Prev r)\n spr post k a = spr post (k a)\nRun Code Online (Sandbox Code Playgroud)\n现在我们接受另一个论点。for 的“前一个”延续a -> r只是递归地定义为一个接受 ana并产生 a 的函数Prev r。然后我们的spr代表spr们PanicType r。
请注意,最困难的部分是确定类型。spr一旦我们决定了和的类型Prev,函数实现就主要是技术性的,只需自己编写即可。
现在对于panic. 我们需要调用spr结果r类型,并使用将调用的后处理参数error和启动printf. 像这样。
panic :: (PanicType r, PrintfType (Prev r)) => Int -> String -> r\npanic lvl fmt = spr (\\s -> error (printf "%s: %s" s (show lvl))) (printf fmt)\nRun Code Online (Sandbox Code Playgroud)\n就像我说的,我们最终确实得到了 a Void,而不是 a forall b. b,所以你必须在实践中得到absurd结果。
main :: IO ()\nmain = absurd (panic 0 "Err %s %d" "fatal" (42 :: Int))\nRun Code Online (Sandbox Code Playgroud)\n欢迎来到 Haskell 的阴暗面!在门口检查你的夹克,桌子上有饼干。
\n\n