固定点上的Monoidal折叠

时间:2017-06-07 00:44:54

标签: haskell monoids recursion-schemes

给定一个具有固定点的任意数据结构,我们可以构造一个幺半代数而无需手动指定所有情况吗?

假设我们获得了如下的数据类型recursion-schemes。使用ExprF库,我们可以派生一个基本仿函数Functor,它还会自动生成FoldableTraversable{-# LANGUAGE DeriveFunctor, DeriveFoldable, DeriveTraversable #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TemplateHaskell #-} import Data.Semigroup (Sum(..)) import Data.Functor.Foldable import Data.Functor.Foldable.TH import Prelude hiding (fail) data Expr = Var String | Const Int | Add [Expr] | Mult [Expr] deriving Show $(makeBaseFunctor ''Expr) expr :: Fix ExprF expr = ana project $ Add [Const 1, Const 2, Mult [Const 3, Const 4], Var "hello"] 个实例。

expr

现在,让我们说我们要计算alg (ConstF _) = 1 alg (VarF _) = 1 alg (AddF xs) = sum xs alg (MulF xs) = sum xs 中的叶数。我们可以轻松地为这么小的数据结构编写代数:

cata alg expr

现在,我们可以致电5,返回Expr,这是正确的结果。

让我们假设cata变得非常庞大和复杂,我们不想手动为所有数据构造函数编写案例。 Monoid如何知道如何结合所有案例的结果?我怀疑这可以使用Const s,可能与fail = getSum $ foldMap (const (Sum 1) . unfix) $ unfix expr 仿函数一起使用(尽管不完全确定最后一部分)。

fail

4返回5,而我们实际上有Fix个离开。我假设问题出在固定点上,因为我们只能剥离Mult [..]一层,因此Monoid只算作一片叶子。

是否有可能以某种方式通常折叠整个固定点并将结果收集到foldMap - 类似结构中,而不用手动指定所有实例?我想要的是Table: Servers Columns: ServerUrl ServerName Rows: //server0001/space0/ Telescope1 //server0001/space1/ Space //server0001/space2/ Planet Table: Projects Columns: ServerUrl Name Entity Type Rows: //server0001/space0/ Field1 E T1 //server0001/space1/ Field2 T T1 //server0001/space2/ Field3 E T3 ,但是更通用。

我有一种感觉,我错过了一些非常明显的东西。

1 个答案:

答案 0 :(得分:10)

这是解决方案的本质。我已经开启了

{-# LANGUAGE DeriveFunctor, DeriveFoldable, DeriveTraversable, PatternSynonyms #-}

让我们回顾一下固定点和咒语。

newtype Fix f = In {out :: f (Fix f)}

cata :: Functor f => (f t -> t) -> Fix f -> t
cata alg = alg . fmap (cata alg) . out

代数alg :: f t -> t采用一个节点,其中子项已被t值替换,然后返回父项的tcata运算符通过解压缩父节点,递归处理其所有子节点,然后应用alg来完成作业来工作。

所以,如果我们想在这样的结构中计算叶子,我们就可以这样开始:

leaves :: (Foldable f, Functor f) => Fix f -> Integer
leaves = cata sumOrOne where
  -- sumOrOne :: f Integer -> Integer

代数sumOrOne可以看到父节点的每个子节点中的叶子数。我们可以使用cata,因为fFunctor。由于fFoldable,我们可以计算孩子们的叶子总数。

  sumOrOne fl = case sum fl of
    ...

有两种可能性:如果父母没有孩子,它的叶子总和将是0,我们可以检测到,但这意味着父亲本身就是一片叶子,所以1应该是回。否则,叶子总和将是非零的,在这种情况下父叶子不是叶子,因此它的叶子总和确实是其子叶子的总叶子总和。这给了我们

leaves :: (Foldable f, Functor f) => Fix f -> Integer
leaves = cata sumOrOne where
  sumOrOne fl{- number of leaves in each child-} = case sum fl of
    0 -> 1  -- no leaves in my children means I am a leaf
    l -> l  -- otherwise, pass on the total

一个简单的例子,基于Hutton的Razor(带有整数和加法的表达式语言,这通常是说明这一点的最简单的事情)。表达式是从Hutton的仿函数生成的。

data HF h = Val Int | h :+: h deriving (Functor, Foldable, Traversable)

我介绍了一些模式同义词来恢复定制类型的外观。

pattern V x    = In (Val x)
pattern s :+ t = In (s :+: t)

我做了一个快速示例表达式,其中有一些深度为三级的叶子。

example :: Fix HF
example = (V 1 :+ V 2) :+ ((V 3 :+ V 4) :+ V 5)

果然

Ok, modules loaded: Leaves.
*Leaves> leaves example
5

另一种方法是在感兴趣的子结构中进行编织和折叠,在这种情况下,是叶子上的东西。 (我们得到了完全免费的monad。)

data Tree f x = Leaf x | Node (f (Tree f x)) deriving (Functor, Foldable)

完成基本构造的叶子/节点分离后,您可以使用foldMap直接访问叶子。扔了一点Control.Newtype,我们得到了

ala' Sum foldMap (const 1)  :: Foldable f => f x -> Integer

低于Fairbairn门槛(即,足够短,不需要名称,所有更清楚,因为没有名称)。

当然,问题在于数据结构在“感兴趣的子结构”中通常是多种有趣但相互矛盾的方式。 Haskell并不总是最好的让我们访问“找到的functoriality”:我们不得不预测在声明时参数化数据类型时我们需要的功能。但现在还有时间改变这一切......