簡體   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