2014-10-16 47 views
1

我想編寫一些可以處理Haskell中的所有數據類型的函數(至少Data.Data中的所有Data實例)。我遇到的一個普遍問題是根據構造函數是否遞歸來選擇做什麼 - 通過遞歸構造函數我的意思是數據類型是D,並且存在構造函數C,其中一個參數是D.Generic Haskell:確定構造函數是否遞歸

我可以通過解決一個更簡單的問題來解決上述問題:給定一個數據,確定最外層的構造函數是否是遞歸的。

這裏是嘗試數1:

data B a = T | F 
gIsLeafCons :: (Data a) => a -> B a 
gIsLeafCons m = gfoldl (\x y -> F) (\x -> T) m 

的想法是,如果構造是遞歸的,那麼我們回到˚F否則我們返回T.這第一眼的偉大工程:

gIsLeafCons 1 ==> T 
gIsLeafCons ([] :: [Int]) ==> T 

然而,gfoldl是多態的,所以給定樹數據類型如

data Tree a = Leaf a | Node (Tree a) (Tree a) 

我們失敗了。

gIsLeafCons (Leaf 1) ==> F 

其原因是(1葉)不是一個真正空構造:它有參數1,依此構造函數(\ X Y - > F)被施加。

嘗試數2:

我們可以用「葉子構造」走的是一個所有兒童評估爲F.這是一個略有改善:

gIsLeafByChild (Leaf 1) ==> T 

但是,如果葉子舉行不同的遞歸結構;這將再次失敗。

gIsLeafByChild (Leaf (Leaf 1)) ==> F 

我真的想停在第一個「葉構造」,但gfoldl的多態特性使得它似乎很難做到這一點,因爲上面看到的。

最後,有沒有辦法來指出構造函數是否是「葉構造函數」。我目前的解決方案是讓用戶傳入一個葉構造函數列表([Constr]),然後檢查構造函數是否是其中之一。但是,如果能夠自動推斷出該列表中是否有東西,那就太好了。

+2

你會做什麼,如果有不同的數據類型的兩個相互遞歸的構造函數?數據T = T Int F;數據F = F Int T',例如。這可以合理地被認爲是遞歸或「葉」。 – amalloy 2014-10-16 18:01:48

+0

@amalloy:這是一個很好的問題。我沒有想太多這個,但我會說,如果摺疊構造函數需要調用摺疊,構造函數是遞歸的。在上述foldT t f(T n a)= t n(foldF f t a)中; foldF f t(F n a)= f n(foldT t f a)。所以我會稱它們都是遞歸的。這是合理的嗎? – 2014-10-16 18:31:15

+1

您可能對Transformers包中的'Data.Constant.Functor'中的'Constant'感興趣。 https://hackage.haskell.org/package/transformers-0.4.1.0/docs/Data-Functor-Constant.html它通常捕捉你的'B a'類型的想法,例如'B a'將會是'常量布爾a'。 – Cirdec 2014-10-16 19:17:00

回答

1

使用gfoldl您有一個很好的開始。我們需要做兩個改變。首先,當我們遍歷結構時,我們需要跟蹤每個遇到的類型,看看它們是否遞歸發生。其次,我們需要遞歸地使用gfoldl來查看我們在構造函數中發現的類型。

讓我們來看看一些實用工具。當我們完成時,bool將得到Bool結果,而bId將把B從一種類型轉換爲另一種類型。

{-# LANGUAGE DeriveDataTypeable #-} 
{-# LANGUAGE RankNTypes #-} 

import Data.Data 

data Tree a = Leaf a | Node (Tree a) (Tree a) 
    deriving (Data, Typeable) 

data B a = T | F 

bool :: B a -> Bool 
bool T = True 
bool F = False 

bId :: B a -> B b 
bId T = T 
bId F = F 

我們可以得到該類型的代表性,或TypeRep,從Typeable類。 TypeableData的超類,所以無論何時我們對某個a有一個Data a實例,我們也將有一個Typeable a實例。我們將通過在[TypeRep]中攜帶包含類型表示的列表來跟蹤這些內容。

recursivelyConstructed :: Data a => a -> Bool 
recursivelyConstructed = bool . rc [] 
    where 
     cf :: forall d. d -> B d 
     cf = const F   
     rc :: forall d. Data d => [TypeRep] -> d -> B d 
     rc ts d = gfoldl (rc' (typeOf d:ts)) cf d 
     rc' :: forall d b. Data d => [TypeRep] -> B (d -> b) -> d -> B b 
     rc' _ T _ = T 
     rc' ts F d = 
      if elem (typeOf d) ts 
      then T 
      else bId . rc ts $ d 

讓我們嘗試一些例子

infiniteTree = Node infiniteTree infiniteTree 

recursivelyConstructed (Leaf 1      :: Tree Int) = False 
recursivelyConstructed (Node (Leaf 1) (Leaf 2)  :: Tree Int) = True 
recursivelyConstructed (infiniteTree     :: Tree Int) = True 
recursivelyConstructed (Leaf (Leaf 1)     :: Tree (Tree (Int))) = False 
recursivelyConstructed (Just (Leaf 1)     :: Maybe (Tree Int)) = False 
recursivelyConstructed (Just (Node (Leaf 1) (Leaf 2)) :: Maybe (Tree Int)) = True 
recursivelyConstructed (Just infiniteTree    :: Maybe (Tree Int)) = True 
recursivelyConstructed (Just (Leaf (Leaf 1))   :: Maybe (Tree (Tree Int))) = False 
+0

基於此,您還可以定義\ n outerRecursive ::(Functor f,Data(f Int))=> f a - > Bool \ n outerRecursive = isRecursive。 fmap(const(1 :: Int))\ n 這也很好,因爲它允許你檢查外部構造函數是否遞歸。 – 2014-10-16 21:44:08

+0

@Jonathan如果你想知道它是否最終會達到最外層的類型,你可以用'TypeRep'替換''TypeRep'',並將內層的'TypeRep's只與最外層的'TypeRep'進行比較。如果要比較是否到達相同的構造函數,例如'節點(葉子1)(葉子1)'不會遞歸地到達相同的構造函數,而是'節點(節點(葉子1)(葉子1))(葉子1)',您可以將'[TypeRep]'改變爲' [(TypeRep,Constr)]',從'toConstr'獲取構造函數。 – Cirdec 2014-10-17 00:04:02