繁体   English   中英

编写递归模板haskell函数

[英]Writing recursive template haskell functions

是否有可能将递归TH函数转换为将编译的等效形式? 以下定义不起作用,因为为了编译fact您必须首先编译fact

fact :: ExpQ -> ExpQ
fact n = [|
    case $n of 
      1 -> 1
      _ -> $n * $(fact [| $n - 1 |]) |]

这个简单的例子很容易解决( fact n = [| product [ 1.. $n] |] )但是在一般情况下,如果不能将给定函数重写为循环,可以定义递归TH函数? 是否还有一个可行的例子?

为了澄清未来的读者:这个问题具体是关于编写递归 TH函数 - 而不是'如何拼接阶乘函数'。

我的问题的答案非常简单:

{-# LANGUAGE TemplateHaskell #-}

import Control.Monad.Fix (fix)
import Language.Haskell.TH

fact = [| \f x -> $([| 
     case x of 
       1 -> 1 
       _ -> f $([| x - 1 |]) * x  |]) |]

factorial = [| fix $fact |]

fact可以编译,因为它不再是递归的, [| fix $fact |] [| fix $fact |]是在以后编译的,所以不再有无限的递归定义。

这个版本的fact看起来与原版略有不同,但是您可以将旧fact完全与旧fact编写并稍后进行转换:

fact' recurse n = [|
        case $n of 
          1 -> 1
          _ -> $n * $(recurse [| $n - 1 |]) |]

fact = [| \x -> $((\f -> [| \x -> $(fact (\x -> [| $f $x |]) [| x |]) |]) [| x |]) |]

代码的根本问题不在于它是递归的,而是它不会终止。 factn论证不断变得越来越大,因为[| $n - 1 ]| [| $n - 1 ]| 是表示应用于n1的操作(-)的表达式树。

任何非终止拼接都会以相同的方式挂起编译器,例如,以下行为就像拼接时的fact一样:

broken :: ExpQ -> ExpQ
broken n = return $ LitE (IntegerL (fromIntegral (length [1..])))

保证递归递归的递归函数保证终止并适用于适当的输入:

fact1 :: ExpQ -> ExpQ
fact1 n = do
    nBody <- n
    case nBody of
      LitE (IntegerL 1) -> [|1|]
      LitE (IntegerL nInt) | nInt > 1 ->
          let nMinusOne = return $ LitE (IntegerL (nInt-1))
          in [| $n * $(fact1 nMinusOne) |]

但是,如果输入不是合适的整数文字,它当然会失败。

您还可以将递归转换为运行时,以便不再使用更大的表达式树进行递归调用,而是使用运行时评估和收缩Int

fact2 :: ExpQ -> ExpQ
fact2 n =
   [|
    let factImpl n =
         case n of
          1 -> 1
          _ -> n * factImpl (n-1)
    in factImpl $n
   |]

当然在这段代码中我们没有对n的结构进行任何分析。 但我们可以将它与fact1放在一起,以获得在某些情况下执行编译时的版本,并将其他版本推迟到运行时:

fact3 :: ExpQ -> ExpQ
fact3 n = do
    nBody <- n
    case nBody of
      LitE (IntegerL 1) -> [|1|]
      LitE (IntegerL nInt) ->
          let nMinusOne = return $ LitE (IntegerL (nInt-1))
          in [| $n * $(fact3 nMinusOne) |]
      _ -> [|
            let factImpl n =
                  case n of
                    1 -> 1
                    _ -> n * factImpl (n-1)
            in factImpl $n
           |]

最终在您的实际代码中,您将需要应用这些技术的某些组合 - 确保您的编译时递归终止并将任何剩余的情况推迟到运行时评估。

是的,您可以使用以下方法:

fact :: Int -> ExpQ
fact 0 = [| 1 |]
fact n = [| $(lift n) * $(fact $ n - 1) |]

liftLanguage.Haskell.TH.Lift中的一个函数,它将基本的haskell值转换为模板haskell值(例如IntExpQ )。

请注意,您不需要生成案例代码,因为您知道编译时的数字。 上面的宏将扩展为一系列乘法。 例如$(fact 4)将扩展到4*3*2*1

请注意,在这种情况下,您可以做得更好。 模板haskell表达式在编译时运行,因此模板haskell fact函数只能返回它所代表的文字值。 例如$(fact 4)可以返回24 (而不是4*3*2*1 )。 这可以使用以下代码完成:

fact2 :: Int -> ExpQ
fact2 n = lift (product [1..n])

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM