2016-03-06 70 views
17
{-# LANGUAGE DeriveFoldable #-} 
{-# LANGUAGE DeriveFunctor #-} 
{-# LANGUAGE DeriveTraversable #-} 
import Control.Comonad 
import Data.Functor.Reverse 
import Data.List (unfoldr) 

首先,一些上下文(哈哈)的方式。我有一個zipper在非空列表。Comonadically找出所有專注於網格

data LZipper a = LZipper (Reverse [] a) a [a] 
    deriving (Eq, Ord, Show, Read, Functor, Foldable, Traversable) 

mkZipper :: a -> [a] -> LZipper a 
mkZipper = LZipper (Reverse []) 

你可以沿着拉鍊任一方向走,但你可能會掉下來。

fwd, bwd :: LZipper a -> Maybe (LZipper a) 
fwd (LZipper _ _ []) = Nothing 
fwd (LZipper (Reverse xs) e (y:ys)) = Just $ LZipper (Reverse (e:xs)) y ys 
bwd (LZipper (Reverse []) _ _) = Nothing 
bwd (LZipper (Reverse (x:xs)) e ys) = Just $ LZipper (Reverse xs) x (e:ys) 

重複一個拉鍊向您顯示了所有可以看到的方式,重點介紹了當前查看方式。

instance Comonad LZipper where 
    extract (LZipper _ x _) = x 
    duplicate z = LZipper (Reverse $ unfoldr (step bwd) z) z (unfoldr (step fwd) z) 
     where step move = fmap (\y -> (y, y)) . move 

例如:

ghci> duplicate (mkZipper 'a' "bc") 
LZipper (Reverse []) 
     (LZipper (Reverse "") 'a' "bc") 
     [LZipper (Reverse "a") 'b' "c",LZipper (Reverse "ba") 'c' ""] 
-- Abc -> *Abc* aBc abC 

ghci> fmap duplicate (fwd $ mkZipper 'a' "bc") 
Just (LZipper (Reverse [LZipper (Reverse "") 'a' "bc"]) 
       (LZipper (Reverse "a") 'b' "c") 
       [LZipper (Reverse "ba") 'c' ""]) 
-- aBc -> Abc *aBc* abC 

(我使用的資金和星號來表示拉鍊的焦點。)


我想有兩個工作用焦點表示的三維網格,表示爲拉鍊的拉鍊。每個內部拉鍊是一排網格。我的最終目標是通過從鄰居跳轉到鄰居來尋找通過網格的路徑。

在網格中移動會保持所有行集中在同一個索引上的不變量。這使得您可以輕鬆關注您的任何鄰居。

type Grid a = LZipper (LZipper a) 

up, down, left, right :: Grid a -> Maybe (Grid a) 
up = bwd 
down = fwd 
left = traverse bwd 
right = traverse fwd 

extractGrid :: Grid a -> a 
extractGrid = extract . extract 
mkGrid :: (a, [a]) -> [(a, [a])] -> Grid a 
mkGrid (x, xs) xss = mkZipper (mkZipper x xs) $ map (uncurry mkZipper) xss 

例子:

ghci> let myGrid = mkGrid ('a', "bc") [('d', "ef"), ('g', "hi")] 
ghci> myGrid 
LZipper (Reverse []) 
     (LZipper (Reverse "") 'a' "bc") 
     [LZipper (Reverse "") 'd' "ef",LZipper (Reverse "") 'g' "hi"] 
-- +-------+ 
-- | A b c | 
-- | d e f | 
-- | g h i | 
-- +-------+ 

ghci> return myGrid >>= right >>= down 
Just (LZipper (Reverse [LZipper (Reverse "a") 'b' "c"]) 
       (LZipper (Reverse "d") 'e' "f") 
       [LZipper (Reverse "g") 'h' "i"]) 
-- +-------+ 
-- | a b c | 
-- | d E f | 
-- | g h i | 
-- +-------+ 

我要的是LZipperduplicate對電網等價的:採用一個網格和生產的,你可以看一下電網的所有方式的網格功能,重點是您正在查看的目前的

duplicateGrid :: Grid a -> Grid (Grid a) 

我很期待:

duplicateGrid myGrid 
+-------------------------------+ 
| ********* +-------+ +-------+ | 
| * A b c * | a B c | | a b C | | 
| * d e f * | d e f | | d e f | | 
| * g h i * | g h i | | g h i | | 
| ********* +-------+ +-------+ | 
| +-------+ +-------+ +-------+ | 
| | a b c | | a b c | | a b c | | 
| | D e f | | d E f | | d e F | | 
| | g h i | | g h i | | g h i | | 
| +-------+ +-------+ +-------+ | 
| +-------+ +-------+ +-------+ | 
| | a b c | | a b c | | a b c | | 
| | d e f | | d e f | | d e f | | 
| | G h i | | g H i | | g h I | | 
| +-------+ +-------+ +-------+ | 
+-------------------------------+ 

我試過duplicateGrid = duplicate . duplicate。這有正確的類型,但(假設我正確解釋show輸出,我可能沒有),只給了我格架的地方主要集中在第一列:

(duplicate . duplicate) myGrid 
+-------------------------------+ 
| ********* +-------+ +-------+ | 
| * A b c * | a b c | | a b c | | 
| * d e f * | D e f | | d e f | | 
| * g h i * | g h i | | G h i | | 
| ********* +-------+ +-------+ | 
| +-------+ +-------+ +-------+ | 
| | A b c | | a b c | | a b c | | 
| | d e f | | D e f | | d e f | | 
| | g h i | | g h i | | G h i | | 
| +-------+ +-------+ +-------+ | 
| +-------+ +-------+ +-------+ | 
| | A b c | | a b c | | a b c | | 
| | d e f | | D e f | | d e f | | 
| | g h i | | g h i | | G h i | | 
| +-------+ +-------+ +-------+ | 
+-------------------------------+ 

我也試過duplicateGrid = duplicate . fmap duplicate。再次假設我能夠解釋的show輸出的,這給了我的東西,既包含了錯誤的網格,不得不行的錯位的焦點,使得向下移動也將打動你一起:

(duplicate . fmap duplicate) myGrid 
+-------------------------------+ 
| ********* +-------+ +-------+ | 
| * A b c * | D e f | | G h i | | 
| * a B c * | d E f | | g H i | | 
| * a b C * | d e F | | g h I | | 
| ********* +-------+ +-------+ | 
| +-------+ ********* +-------+ | 
| | A b c | * D e f * | G h i | | 
| | a B c | * d E f * | g H i | | 
| | a b C | * d e F * | g h I | | 
| +-------+ ********* +-------+ | 
| +-------+ +-------+ ********* | 
| | A b c | | D e f | * G h i * | 
| | a B c | | d E f | * g H i * | 
| | a b C | | d e F | * g h I * | 
| +-------+ +-------+ ********* | 
+-------------------------------+ 

這感覺對於那些知道的人來說這將是一個簡單的問題,但它讓我感到頭暈目眩。我想我可以手動啓動一個叫做up,down,leftright的功能,但我覺得這個共同機器應該能夠爲我做到這一點。什麼是duplicateGrid的正確實施?

+2

FYI,您可能會對其他位感興趣。請參閱http:// stackoverflow。com/a/25572148/1477667 – dfeuer

+2

而[this one](http://stackoverflow.com/questions/12963733/writing-cojoin-or-cobind-for-n-dimensional-grid-type/13100857#13100857)哪些地址你的問題具體。我不知何故錯過了它,而我的回答在它的光芒中是相當多餘的,但至少我有機會爲自己弄清楚它的一部分。 –

+0

@AndrásKovácsD'oh,我的問題似乎與該問題完全相同。不知道爲什麼我在搜索時找不到它。我會做正確的事情,並關閉這一個。 –

回答

9

這裏有點問題,我們試圖自己編寫Grid,因爲這個設置給我們太多的錯誤方法來實現duplicate與正確的類型。考慮一般情況下組合的連接器不一定相同是很有用的。

假設我們有fg comonads。該類型的duplicate變爲:

duplicate :: f (g a) -> f (g (f (g a))) 

我們可以得到單獨使用Comonad情況如下:

duplicate . fmap duplicate :: f (g a) -> f (f (g (g a))) 

從這個很明顯,我們需要換fg在中間。

有一個叫做Distributive的類型類,它有我們想要的方法。

class Functor g => Distributive g where 
    distribute :: Functor f => f (g a) -> g (f a) 

尤其是,我們需要實現Distributive g,然後duplicate對於組成的comonad可以實現爲:

duplicate = fmap distribute . duplicate . fmap duplicate 

然而,在Distributive的文件說的g值必須有確切相同的形狀,所以我們可以將任意數量的副本壓縮在一起而不會丟失任何信息。

爲了說明這一點,如果Vec n an大小的矢量,那麼distribute :: [Vec n a] -> Vec n [a]只是矩陣轉置。事先必須確定內部向量的大小,因爲在「不整齊」矩陣上的換位必須放棄一些元素,這不是合法的行爲。無限的流媒體和拉鍊也可以分發,因爲它們也只有一種可能的尺寸。

Zipper不是合法的Distributive,因爲Zipper包含具有不同大小上下文的值。不過,我們可以實現假設統一上下文大小的不當分佈。

下面我將針對Grid針對底層列表的不當分佈實施duplicate

或者,您可以直接捲起袖子並在Zipper (Zipper a)上實現換位功能。實際上,我做到了這一點,但這讓我很頭疼,而且我很難確信它是正確的。儘可能使類型儘可能通用,以縮小可能實現的空間,所以減少出錯的可能性更大。

爲了減少語法噪音,我將省略Reverse;我希望你能原諒我。

{-# language DeriveFunctor #-} 

import Control.Comonad 
import Data.List 
import Control.Monad 

data Zipper a = Zipper [a] a [a] deriving (Eq, Show, Functor) 

lefts, rights :: Zipper a -> [a] 
lefts (Zipper ls _ _) = ls 
rights (Zipper _ _ rs) = rs 

bwd :: Zipper a -> Maybe (Zipper a) 
bwd (Zipper [] _ _) = Nothing 
bwd (Zipper (l:ls) a rs) = Just $ Zipper ls l (a:rs) 

fwd :: Zipper a -> Maybe (Zipper a) 
fwd (Zipper _ _ []) = Nothing 
fwd (Zipper ls a (r:rs)) = Just $ Zipper (a:ls) r rs 

instance Comonad Zipper where 
    extract (Zipper _ a _) = a 
    duplicate z = 
    Zipper (unfoldr (fmap (join (,)) . bwd) z) z (unfoldr (fmap (join (,)) . fwd) z) 

如果事先知道它們的長度,我們可以分發清單。由於Haskell列表可以是無限的,我們應該用可能無限的懶惰自然來衡量長度。測量長度的另一種解決方案是使用「指南」列表,我們可以壓縮其他列表。但是,我不想在分配函數中假設這樣的虛擬列表總是可用的。

data Nat = Z | S Nat 

length' :: [a] -> Nat 
length' = foldr (const S) Z 

distList :: Functor f => Nat -> f [a] -> [f a] 
distList Z  fas = [] 
distList (S n) fas = (head <$> fas) : distList n (tail <$> fas) 

當然,如果我們的長度假設不正確,這會失敗並伴隨運行時異常。

我們可以通過分發他們的焦點和上下文分配Zipper S,只要我們知道上下文的長度:

distZipper :: Functor f => Nat -> Nat -> f (Zipper a) -> Zipper (f a) 
distZipper l r fz = Zipper 
    (distList l (lefts <$> fz)) (extract <$> fz) (distList r (rights <$> fz)) 

最後,我們可以在我們以前看到的方式複製Grid S,但首先我們必須確定內部Zipper的形狀。由於我們假定所有內部Zipper■找相同的形狀,我們只看看Zipper的重點:

duplicateGrid :: Grid a -> Grid (Grid a) 
duplicateGrid [email protected](Zipper _ (Zipper ls _ rs) _) = 
    fmap (distZipper (length' ls) (length' rs)) $ duplicate $ fmap duplicate grid 

測試這個(因爲你必須已經經歷)是很可怕的,我還沒有手忙腳亂地檢查一個兩乘二的案例。

不過,我對上述實現還是比較有信心的,因爲定義受到類型的高度限制。

+0

爲什麼不假設「虛擬列表」可用?我相信你可以在任何時候寫出一個通用的'replicate'來獲得其中的一個,並且我認爲你在做的事情在這個範圍之外是有意義的。 – dfeuer

+2

我對此並沒有多少考慮,我只是傾向於將最弱的假設作爲習慣。現在你指出了這一點,我不得不想出一些令人費解的反例。到目前爲止,我提出了這個問題:如果'f'是一個常量仿函數,那麼我們應該能夠「分發」任意長度。但是如果'f [a]'中的'a'是底部,我們只能生成'[]'作爲虛擬列表(總語言)。 –

+0

你讓共同體不同的訣竅是照亮;我並沒有將'Grid'想象爲'Zipper'的_composition_(及其相應的'Comonad'實例)。如果您有權訪問更強大的「Applicative」和「Traversable」約束條件,那麼您的解決方案重複兩次,然後分發似乎與[@ pigworker的答案](http://stackoverflow.com/a/13100857/1523776)一致 - 我們做 - 然後'分佈'是'sequenceA'。 –

5

所以有一個密切相關的comonad,可能有助於引導你。我們有:

newtype MC m a = MC { unMC :: m -> a } 

instance Monoid m => Comonad (MC m) where 
    extract (MC f) = f mempty 
    duplicate (MC f) = MC (\x -> MC (\y -> f (x <> y))) 

instance Functor (MC m) where 
    fmap f (MC g) = MC (f . g) 

所以雙向無限陣列將MC (Sum Integer) a和雙向無限電網將MC (Sum Integer, Sum Integer) a。當然,MC m (MC n a)通過柯里格同構於MC (m,n) a

在任何情況下,你想要重複網格的功能將類似於(忽略NEWTYPE包裝和鑽營)到:

duplicateGrid g x y dx dy = g (x + dx) (y + dy) 

duplicate爲一維數組的樣子:

duplicate f x y = f (x+y) 

所以duplicate . duplicate是:

(duplicate . duplicate) f x y z 
    = duplicate (duplicate f) x y z 
    = duplicate f (x+y) z 
    = f (x + y + z) 

不是想要的。是什麼fmap duplicate樣子:再次

fmap duplicate f x y z = f x (y + z) 

很顯然這樣做duplicate會給我們帶來同樣的事情duplicate . duplicate(這是它應該,因爲這是一個comonad法)。不過,這有點更有希望。如果我們這樣做 fmap小號...

fmap (fmap duplicate) f x y z w 
    = fmap duplicate (f x) y z w 
    = f x y (z + w) 

現在,如果我們這樣做duplicate我們會得到

(duplicate . fmap (fmap duplicate)) f x y z w = f (x+y) (z+w) 

但是,這仍然是錯誤的。更改變量名稱,其f (x+y) (dx + dy)。所以我們需要交換兩個內部變量......我們想要的分類理論名稱是分配律。 Haskell的名字是TraversablesequenceA看起來像函數(函數形式爲Applicative仿函數,實際上是MonadReader monad)看起來像什麼?該類型說明了所有。

sequenceA :: (a -> b -> c) -> (b -> a -> c) 
sequenceA f x y = f y x 

所以最後:

fmap sequenceA g x y z = g x z y 

(duplicate . fmap (fmap duplicate) . fmap sequenceA) g x y dx dy 
    = (duplicate . fmap (fmap duplicate)) g x dx y dy 
    = g (x + dx) (y + dy) 

我還沒有真正嘗試類似的代碼,所以我不知道,如果它的工作原理,但數學說,它應該。

7

您遇到的根本問題是zippers don't natively support 2-d structures。答案非常好(其他答案基本上就是你的Grid的定義),我鼓勵你閱讀它,但要點是,拉鍊標識有路徑的元素到達那裏,並且在2-d空間中這樣的標識是有問題,因爲有很多途徑可以達到某一點。

因此,你會發現,而Grid是你updown功能拉鍊方面完全定義,你需要使用Traversable機械定義leftright。這也意味着leftright不會享受與updown相同的性能屬性,因爲您可以這麼說「違背穀物」。

由於您Comonad實例是使用您的拉鍊的功能來限定,它僅僅在duplicate是由你的拉鍊,即fwdbwd(通過擴展updown)限定的方向即可。

編輯:經過一些更多的思考後,我認爲你的方法將成爲根本問題。我已經保存了下面的原文,但還有一個更明顯的問題。

如果你想穿過你的拉鍊,就好像它們像任何其他二維結構一樣,你將繼續使用duplicate得到Nothing。讓我們注意一下,如果您真的嘗試在表面上沒有問題的duplicate (mkZipper 'a' "bc")上使用up, down, left, right函數,會發生什麼情況。

*Main> let allRows = duplicate $ mkZipper 'a' "bc" 
*Main> down allRows -- This is fine since we're following the zipper normally 
Just (LZipper (Backwards [LZipper (Backwards "") 'a' "bc"]) (LZipper (Backwards "a") 'b' "c") [LZipper (Backwards "ba") 'c' ""]) 
*Main> right allRows 
Nothing -- That's bad... 
*Main> down allRows >>= right 
Nothing -- Still nothing 

移動rightleft要求您的子拉鍊的每一個是在結構均勻(如你及時與您不變的提注),否則traverse過早損壞了。這意味着如果你真的想要使用leftright,那麼這將與duplicate打好關係的唯一方法是如果你使用最統一的duplicate可能。

duplicate z @ (LZipper left focus right) = 
    LZipper (fmap (const z) left) z (fmap (const z) right) 

另一種方法是隻使用拉鍊附帶的功能。這意味着只使用fwdbwd,然後extract重點並繼續使用fwdbwd獲得與leftright相同的結果。當然,這意味着放棄說「再低」和「低於正確」的能力,但正如我們已經看到的,拉鍊不能很好地適應多種途徑。

現在讓我們仔細檢查一下你的直覺,看看如何最好地解釋duplicate . duplicate $ myGrid發生了什麼。一個漂亮的廣場並不是真正考慮發生了什麼的最佳方式(如果將自己限制在extractfwdbwd之間,你會明白爲什麼。

*Main> let allRows = duplicate . duplicate $ myGrid 
*Main> fwd $ extract allRows -- Makes sense 
Just ... 
-- This *should* be the bottom-left of the grid 
*Main> let bottomLeft = extract <$> fwd allRows >>= fwd 
*Main> bottomLeft >>= fwd 
Nothing -- Nope! 
*Main> bottomLeft >>= bwd 
Just ... -- Wait a minute... 

我們實際上有一個衣衫襤褸的結構。

+---------------------------------------------------+ 
|      ********* +-------+ +-------+ | 
|      * A b c * | a b c | | a b c | | 
|      * d e f * | D e f | | d e f | | 
|      * g h i * | g h i | | G h i | | 
|      ********* +-------+ +-------+ | 
|   +-------+ +-------+ +-------+   | 
|   | A b c | | a b c | | a b c |   | 
|   | d e f | | D e f | | d e f |   | 
|   | g h i | | g h i | | G h i |   | 
|   +-------+ +-------+ +-------+   | 
| +-------+ +-------+ +-------+      | 
| | A b c | | a b c | | a b c |      | 
| | d e f | | D e f | | d e f |      | 
| | g h i | | g h i | | G h i |      | 
| +-------+ +-------+ +-------+      | 
+---------------------------------------------------+ 

這個衣衫襤褸的結構裏面的方塊實際上並不是正方形,它們也會是破舊的。等價地,你可以將fwd想象成對角線。或者完全放下2-d結構的拉鍊。

根據我的經驗,當與樹狀物配對時,拉鍊確實工作得最好。如果一個Haskell專家能夠想出一個使用拉鍊的方法,並且像循環圖或甚至僅僅是普通的舊DAG那樣使用拉鍊和所有更新/訪問優點,我不會感到驚訝,但我想不出任何關閉我微薄的頭頂:)。

因此,故事的寓意,拉鍊對於二維結構而言頗爲頭痛。 (空閒的想法:也許鏡頭可能會很有趣?)

對於好奇,下面的方法也只適用於如果你記住我們正在處理的結構的粗糙;即fwd兩次,然後提取將得到相當於OP在其網格的右下角而不是在左下角的OP所需的內容。

原始

所以,你需要的是某種方式之間切換的純拉鍊基於duplicate和你Traversable基於重複。最簡單的方法是採取你已經寫好的duplicate函數,並簡單地在中間添加一個traverse

duplicateT :: Traversable t => t (LZipper a) -> LZipper (t (LZipper a)) 
duplicateT z = LZipper (Backwards $ unfoldr (step bwd) z) z (unfoldr (step fwd) z) 
    -- Everything's the exact same except for that extra traverse 
    where step move = fmap (\y -> (y, y)) . (traverse move) 

現在,我們有一個更一般的duplicateT我們可以擺脫一些討厭的重複的代碼在你的Comonad例如重新定義duplicate是:

-- requires import Data.Functor.Identity 
duplicate = fmap runIdentity (duplicate' (Identity z)) 

那麼下面獲取你想要的

什麼
duplicateGrid = duplicate . duplicateT 

或者,如果你想切換的行和列的順序,你可以做相反的事情。

注:如果哈斯克爾讓你本身定義的類型類類型約束,這樣你可以有Comonad的不同實例(所有帶導newtype手機可能)爲您LZipper是改變你的duplicate方向它甚至會更好。問題是你會想要像instance Comonad LZipper (LZipper a) where ...或等效的newtype,你根本無法在Haskell中寫入。你可以想像做類似this類型的家庭,但我懷疑這可能是這個特殊情況的矯枉過正。

編輯:事實上,你甚至不需要duplicateT如果你給適當Applicative實例LZipper

instance Applicative LZipper where 
    pure x = LZipper (Backwards (repeat x)) x (repeat x) 
    (LZipper leftF f rightF) <*> (LZipper left x right) = LZipper newLeft (f x) newRight 
     where 
     newLeft = (Backwards (zipWith ($) (forwards leftF) (forwards left))) 
     newRight = (zipWith ($) rightF right) 

現在乾脆把原來的duplicate你有過和使用traverse

duplicateGrid = duplicate . (traverse duplicate)