【问题标题】:Generic Haskell: Determine whether a constructor is recursive or notGeneric Haskell:确定构造函数是否是递归的
【发布时间】:2014-10-16 17:50:23
【问题描述】:

我想编写一些适用于 Haskell 中所有数据类型的函数(至少是 Data.Data 中的所有数据实例)。我遇到的一个普遍问题是根据构造函数是否递归来选择要做什么——递归构造函数是指数据类型是 D,并且有一个构造函数 C,其中一个参数是 D。

我可以通过解决一个更简单的问题来解决上述问题:给定一个数据,确定最外面的构造函数是否是递归的。

这是第 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

原因是 (Leaf 1) 不是一个真正的空构造函数:它有参数 1,因此应用了构造函数 (\x y -> F)。

第 2 次尝试:

我们可以使用“叶构造函数”,即所有子级都评估为 F。这是一个轻微的改进:

gIsLeafByChild (Leaf 1) ==> T

但是,如果叶子持有不同的递归结构;这将再次失败。

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

我真的很想在第一个“叶子构造函数”上停下来,但是 gfoldl 的多态特性使它看起来很难做到这一点,如上所示。

最后,有没有办法判断构造函数是否是“叶构造函数”。我目前的解决方案是让用户传入一个叶子构造函数列表([Constr]),我只是检查构造函数是否是其中之一。但是,如果有东西在这个列表中,自动推断会很好。

【问题讨论】:

  • 如果有两个不同数据类型的相互递归的构造函数,你会怎么做?例如data T = T Int F; data F = F Int T。这可以合理地被认为是递归或“叶”。
  • @amalloy:这是个好问题。我对此并没有想太多,但是如果折叠该构造函数需要调用折叠,我会说构造函数是递归的。在上面 foldT t f (T n a) = t n (foldF f t a);折叠F f t (F n a) = f n (折叠T t f a)。所以我会称它们都是递归的。这合理吗?
  • 您可能对变压器包中的Data.Constant.Functor 中的Constant 感兴趣。 hackage.haskell.org/package/transformers-0.4.1.0/docs/… 它概括了您的 B a 类型的概念,例如,B a 将是 Constant Bool a

标签: generics haskell constructor algebraic-data-types


【解决方案1】:

您已经开始使用gfoldl。我们需要做两处改变。首先,当我们遍历结构时,我们需要跟踪每个遇到的类型以查看它们是否递归出现。其次,我们需要递归地使用gfoldl 来查看我们在构造函数中发现的类型。

让我们使用一些实用程序。完成后bool 将得到Bool 结果,bId 会将Bs 从一种类型转换为另一种类型。

{-# 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

我们可以从Typeable 类中获取类型的表示,或TypeRepTypeableData 的超类,所以每当我们有一些aData 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

【讨论】:

  • 基于此还可以定义\n outerRecursive :: (Functor f, Data (f Int)) => f a -> Bool\n outerRecursive = isRecursive 。 fmap (const (1 :: Int))\n 这也很好,因为它允许您检查外部构造函数是否是递归的。
  • @Jonathan 如果你想知道它最终是否到达了最外层的类型,你可以将[TypeRep] 替换为TypeRep 并将内层的TypeReps 与仅最外层的进行比较。如果您想比较是否达到了相同的构造函数,例如Node (Leaf 1) (Leaf 1) 永远不会递归到达同一个构造函数,但 Node (Node (Leaf 1) (Leaf 1)) (Leaf 1) 会,您可以将 [TypeRep] 更改为 [(TypeRep, Constr)],从 toConstr 获取构造函数。
猜你喜欢
  • 2015-07-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-09-04
相关资源
最近更新 更多