两个方面打结(是:用comonad打结)

时间:2015-02-14 14:54:06

标签: haskell comonad tying-the-knot

编辑:最初的问题是"用comonad"结束了结,但真正有用的是与来自{U2Graph的{​​{1}}的二维结。 {3}}。 原始问题(直到Anwser):

我希望将结点与来自comonad

的数据联系起来
data U a = U [a] a [a]

进入更丰富的数据结构

data FullCell = FullCell {
   vision   :: [[Int]],
   move     :: Int -> Maybe FullCell -- tie the knot here!
}

带有功能

tieKnot :: U Int -> U FullCell
然而,我的大脑遇到了一个"发生检查"当我尝试填写undefined时:

tieKnot :: U Int -> U FullCell
tieKnot u = u =>> (\z -> FullCell {
      vision = limitTo5x5 z,
      move = move'
})  where
         move'   1  = Just undefined -- tie the knot to neighbor here
         move' (-1) = Just undefined -- ...
         move'   _  = Nothing
         limitTo5x5 = undefined -- not of interest, but the cause why a comonad is used

我无法解决的问题是,我需要参考我正在构建的东西,它深埋在一个comonad中。而且我想确定圈子实际上指向同一个圈子。

解决问题的最佳方法是什么?这是comonad U a的方法吗?双向链表data T a = T (Maybe (T a)) a (Maybe (T a))似乎遇到了同样的问题,但要扩展到2维更难。


背景:我尝试在haskell中实现cirdec。因此,由于知道了,我想通过耗时的计算来引用相同的thunk。

答案

解决方案来自codegolf's rat race。它只是错过了一小步,我不想插入评论。

是什么导致我的大脑遇到"发生检查"是:构建一个FullCell并在其字段move上打结。我需要已经构建的U2Graph FullCell。现在我说了,这个要求很容易写成:

toU2Graph :: (U2Graph b -> a -> b) -> U2 a -> U2Graph b

其中第一个参数是构造我的FullCell的函数。 Cirdec的功能可以轻松调整。最后一步是将comonad带回来:

toU2GraphW :: (U2Graph b -> U2 a -> b) -> U2 a -> U2Graph b
toU2GraphW f u = toU2Graph f (duplicate u)

2 个答案:

答案 0 :(得分:3)

可以从拉链构建图形,以便在图形上移动并不需要分配新内存。如果您要保留多个指向结构的指针,这可以提高性能。

我们将从列表的拉链开始。

data U a = U [a] a [a]

相应的图表保存对左侧和右侧节点的引用(如果存在)。

data UGraph a = UGraph {
    _left :: Maybe (UGraph a),
    _here :: a,
    _right :: Maybe (UGraph a)
    }

这种结构的任何实例都应遵守以下法律,即向一个方向然后向另一个方向回到你开始的地方。

_right >=> _left  == \x -> (_right >=> const (return x)) x
_left  >=> _right == \x -> (_left  >=> const (return x)) x

UGraph数据类型并没有强制执行此操作,因此将它放在模块中并且不导出UGraph构造函数是明智的。

要将拉链转换为图形,我们从中间开始,逐步完成拉伸。我们在已经构建的图形部分和尚未构建的图形部分之间绑定递归结。

toUGraph :: U a -> UGraph a
toUGraph (U ls h rs) = g
    where
        g = UGraph (build ugraph' g ls) h (build UGraph g rs)
        ugraph' r h l = UGraph l h r
        build _ _    []          = Nothing
        build f prev (here:next) = Just g
            where
                g = f (Just prev) here (build f g next)

结合my other answer,您可以使用

构建U Int的可见部分的图表
tieKnot :: U Int -> UGraph [[Int]]
tieKnot = toUGraph . extend limitTo5x5

二维

最终你想建立一个二维领域。像我们在二维中对一维列表拉链所做的那样构建图形要复杂得多,并且通常需要强制O(n^2)内存遍历长度为n的任意路径。

您计划使用two-dimensional list zipper Dan Piponi described,因此我们会在此处重现。

data U2 a = U2 (U (U a))

我们可能会试图为U2创建一个直线模拟的图表

data U2Graph a = U2Graph (UGraph (UGraph a))

这有一个相当复杂的结构。相反,我们会做更简单的事情。如果存在这些节点,则对应于U2的图的节点将保持对四个基本方向中的每个方向中的相邻节点的引用。

data U2Graph a = U2Graph {
    _down2  :: Maybe (U2Graph a),
    _left2  :: Maybe (U2Graph a),
    _here2  :: a,
    _right2 :: Maybe (U2Graph a),
    _up2    :: Maybe (U2Graph a)
    }

U2Graph的实例应遵守我们为UGraph定义的相同双向迭代器定律。再一次,结构本身并没有强制执行这些法则,因此U2Graph构造函数可能不应该被暴露。

_right2 >=> _left2  == \x -> (_right2 >=> const (return x)) x
_left2  >=> _right2 == \x -> (_left2  >=> const (return x)) x
_up2    >=> _down2  == \x -> (_up2    >=> const (return x)) x
_down2  >=> _up2    == \x -> (_down2  >=> const (return x)) x

在我们将U2 a转换为U2Graph a之前,让我们先看一下U2 a的结构。我将外部列表指定为左右方向,内部列表指定为上下方向。 U2的脊椎一直穿过数据,焦点位于脊柱的任何位置。每个子列表可以垂直于脊椎滑动,以便它专注于子列表中的特定点。使用过程中的U2可能看起来像是下面的。 + s是外部脊柱,垂直破折号|是内部刺,*是结构的焦点。

|
||     
|||   ||
|||| |||| |
+++*++++++++
 |||||| ||
  ||||   
   ||

每个内部刺都是连续的 - 它不能有间隙。这意味着如果我们正在考虑脊柱外的位置,如果靠近脊椎的位置在该侧也有邻居,则它只能有左侧或右侧的邻居。这导致我们将如何构建U2Graph。我们将沿着外部脊柱建立左右连接,将递归引用返回焦点,就像我们在toUGraph中所做的那样。我们将沿着内部脊柱上下建立连接,递归引用回到脊柱,就像我们在toUGraph中所做的那样。为了从内脊柱上的节点建立左右连接,我们将向外移动一步靠近外脊椎,在该节点处向侧面移动,然后再向远离外侧脊柱移动一步内心脊柱。

toU2Graph :: U2 a -> U2Graph a
toU2Graph (U2 (U ls (U ds h us) rs)) = g
    where
        g = U2Graph (build u2down g ds) (build u2left g ls) h (build u2right g rs) (build u2up g us)
        build f _    []          = Nothing
        build f prev (here:next) = Just g
            where
                g = f (Just prev) here (build f g next)
        u2up   d h u = U2Graph d (d >>= _left2 >>= _up2  ) h (d >>= _right2 >>= _up2  ) u
        u2down u h d = U2Graph d (u >>= _left2 >>= _down2) h (u >>= _right2 >>= _down2) u
        u2left r (U ds h us) l = g
            where
                g = U2Graph (build u2down g ds) l h r (build u2up g us)
        u2right l (U ds h us) r = g
            where
                g = U2Graph (build u2down g ds) l h r (build u2up g us)

答案 1 :(得分:2)

使用Comonad U实例可以更轻松地解决整个问题。我们将使用comonad中的Comonad类。

{-# LANGUAGE DeriveFunctor #-}

import Data.List
import Control.Comonad

data U a = U [a] a [a]
    deriving (Functor)

除了Comonad方法之外,拉链还有两个主要功能。您可以向左移动或向右移动。如果左侧或右侧没有任何东西,这两种情况都可能失败。

moveLeft :: U a -> Maybe (U a)
moveLeft (U (l:ls) h r) = Just $ U ls l (h:r)
moveLeft u              = Nothing

moveRight :: U a -> Maybe (U a)
moveRight (U l h (r:rs)) = Just $ U (h:l) r rs
moveRight u              = Nothing

Comonad的有趣部分是duplicate :: w a -> w (w a),它构建了一个在每个位置保存上下文的结构。我们可以在展开duplicateComonad方面为moveLeft个实例定义moveRight

instance Comonad U where
    extract (U _ here _) = here
    duplicate u = U (unfoldr (fmap dup . moveLeft) u) u (unfoldr (fmap dup . moveRight) u)
        where
            dup x = (x, x)

我们将解决您tieKnot隐含的问题。我们为duplicate Comonad实例编写的U将解决您的所有问题 - 我们根本不需要FullCell数据类型。您有一些函数limitTo5x5 :: U Int -> [[Int]],一个特定子类型U a -> b的实例。

limitTo5x5 :: U Int -> [[Int]]
limitTo5x5 = undefined

如果我们首先duplicate U Int我们将拥有一个拉链,在每个位置保存完整的上下文。如果我们fmap limitTo5x5然后我们将有一个拉链保存结果

tieKnot :: U Int -> U [[Int]]
tieKnot = fmap limitTo5x5 . duplicate

此模式fmap f . duplicateComonadMonad绑定的双重关系>>=。在Comonad课程中,它被称为extend f = fmap f . duplicate

tieKnot :: U Int -> U [[Int]]
tieKnot = extend limitTo5x5

现在是我们进行批判性观察的时候了。当我们在U中构建外部duplicate时,我们只构建了一个U _ _ _ :: U (U a)。它们中只有一个,它不会递归地引用任何其他东西。我们可以沿着拉链自由地左右移动而不需要任何大的成本。每次移动时,我们都需要分配一个U和一个列表缺点(:),同时释放一个U和一个列表缺点。