【问题标题】:Recursion schemes using `Fix` on a data-type that's already a Functor?在已经是 Functor 的数据类型上使用“Fix”的递归方案?
【发布时间】:2017-05-22 08:28:24
【问题描述】:

仍在使用我的文本编辑器Rasa

目前我正在构建用于跟踪视口/拆分的系统(类似于 vim 拆分)。对我来说,将这个结构表示为一棵树似乎很自然:

data Dir = Hor
         | Vert
         deriving (Show)

data Window a =
  Split Dir SplitInfo (Window a) (Window a)
    | Single ViewInfo a
    deriving (Show, Functor, Traversable, Foldable)

这很好用,我将Views 存储在树中,然后我可以遍历/fmap 对它们进行更改,它也与镜头包非常吻合!

我最近一直在学习Recursion Schemes,这似乎是一个适合他们的用例,因为树是一个递归数据结构。

我设法弄清楚它足以构建 Fixpoint 版本:

data WindowF a r =
  Split Dir SplitInfo r r
    | Single ViewInfo a
    deriving (Show, Functor)

type Window a = Fix (WindowF a)

但是,现在 Functor 实例被 r 用完了;

我尝试了几种变体

deriving instance Functor Window

但它会窒息,因为 window 是一个类型的同义词。

还有:

newtype Window a = Window (Fix (WindowF a)) deriving Functor

这也失败了;

• Couldn't match kind ‘* -> *’ with ‘*’
    arising from the first field of ‘Window’ (type ‘Fix (WindowF a)’)
• When deriving the instance for (Functor Window)
  1. 是否仍然可以在a 上定义 fmap/traverse?或者我是否需要使用recursion-schemes 原语来执行这些操作?我要实现双函子吗?实例实现是什么样的?

其余类型为 here,项目无法编译,因为我没有合适的 Functor 实例用于 Window...

谢谢!!

【问题讨论】:

  • 是的,这两个问题合二为一。请不要那样做。
  • 不知道我在想什么哈哈,既然你回答了第一个,我会把第二个拉到新的。谢谢顺便说一句!

标签: haskell functor recursive-datastructures recursion-schemes fixpoint-combinators


【解决方案1】:

经过一番折腾,我得出的结论是,更好的选择是定义两种数据类型;具有所需属性的标准数据类型(在本例中为 Bifunctor)和递归 Functor 数据类型,您可以为其定义 BaseRecursiveCorecursive 实例。

它是这样的:

{-# language DeriveFunctor, DeriveTraversable, TypeFamilies  #-}

import Data.Typeable
import Data.Bifunctor
import Data.Functor.Foldable

data BiTree b l =
  Branch b (BiTree b l) (BiTree b l)
    | Leaf l
    deriving (Show, Typeable, Functor, Traversable, Foldable)

instance Bifunctor BiTree where
  bimap _ g (Leaf x) = Leaf (g x)
  bimap f g (Branch b l r) = Branch (f b) (bimap f g l) (bimap f g r)

data BiTreeF b l r =
  BranchF b r r
    | LeafF l
    deriving (Show, Functor, Typeable)

type instance Base (BiTree a b) = BiTreeF a b
instance Recursive (BiTree a b) where
  project (Leaf x) = LeafF x
  project (Branch s l r) = BranchF s l r

instance Corecursive (BiTree a b) where
  embed (BranchF sp x xs) = Branch sp x xs
  embed (LeafF x) = Leaf x

您现在可以像往常一样在整个代码中使用您的基本类型 (BiTree);当您决定使用递归方案时,您只需要记住,在解包时您使用的是“F”版本的构造函数:

anyActiveWindows :: Window -> Bool
anyActiveWindows = cata alg
  where alg (LeafF vw) = vw^.active
        alg (BranchF _ l r) = l || r

请注意,如果您最终重建一组窗口,您仍将使用 = 右侧的非 F 版本。

我为我的场景定义了以下内容,效果很好;我有FunctorBifunctorWindow,我什至不需要使用新类型:

type Window = BiTree Split View

data SplitRule =
  Percentage Double
  | FromStart Int
  | FromEnd Int
  deriving (Show)

data Dir = Hor
        | Vert
        deriving (Show)

data Split = Split
  { _dir :: Dir
  , _splitRule :: SplitRule
  } deriving (Show)

makeLenses ''Split

data View = View
  { _active :: Bool
  , _bufIndex :: Int
  } deriving (Show)

makeLenses ''View

【讨论】:

    【解决方案2】:

    是的,你想使用Data.Bifunctor.Fix中的Fix的版本:

    newtype Fix p a = In { out :: p (Fix p a) a }
    
    instance Bifunctor p => Functor (Fix p) where
      fmap f (In x) = In (bimap (fmap f) f x)
    

    您必须更改您的 WindowF 类型以匹配:

    data WindowF r a =
      Split Dir SplitInfo r r
        | Single ViewInfo a
        deriving (Show, Functor)
    
    instance Bifunctor WindowF where
      bimap f _g (Split dir si x y) = Split dir si (f x) (f y)
      bimap _f g (Single vi a) = Single vi (g a)
    
    newtype Window a = Window (Fix WindowF a) deriving Functor
    

    可以使用 recursion-schemes 和辅助类型:

    import Data.Functor.Foldable hiding (Fix (..))
    import Data.Profunctor.Unsafe
    import Data.Coerce
    
    newtype Flip p a b = Flip {unFlip :: p b a}
    
    instance Bifunctor p => Bifunctor (Flip p) where
      bimap f g (Flip x) = Flip (bimap g f x)
    
    instance Bifunctor p => Functor (Flip p a) where
      fmap = coerce (first :: (x -> y) -> p x a -> p y a)
        :: forall x y . (x -> y) -> Flip p a x -> Flip p a y
    
    type instance Base (Fix p a) = Flip p a
    instance Bifunctor p => Recursive (Fix p a) where
      project = Flip #. out
      cata f = f . Flip . first (cata f) . out
    

    不幸的是,为 newtype-wrapped 版本定义 Recursive 有点棘手:

    newtype Window a = Window {getWindow :: Fix WindowF a} deriving (Functor)
    type instance Base (Window a) = Flip WindowF a
    
    instance Recursive (Window a) where
      project = coerce #. project .# getWindow
      cata = (. getWindow) #. cata
    

    【讨论】:

    • 我们需要新类型吗?在这种情况下它提供了什么? Functor 实例是否有必要?我现在在使用它时遇到了一些麻烦,例如以下(简单的)示例适用于常规修复,但不适用于新的 Bifunctor 修复,请查看allTree 此处:gist.github.com/ChrisPenner/aa6083478d2d1100f62a974860aae529 抱歉麻烦,我对所有这些 Fix 东西都很陌生,现在有两个不同的版本并没有让它变得更容易哈哈。
    • @ChrisPenner,这有帮助吗?
    • 这比我想象的要复杂得多;我想我可能会回到我简单的 Functor 解决方案,除非你有任何更简单的想法。我想学习递归方案,但如果像拥有 Functor 数据类型这样简单的东西这么复杂,那么我不知道我能胜任这项任务。我真的很感激这些解释!由于某种原因,堆栈拒绝安装“profunctors”库,所以我什至在尝试这个时都遇到了麻烦。尽管如此,即使我能弄清楚,我认为其他程序员也很难理解结果:(
    • @ChrisPenner,您当然可以使用recursion-schemes 方法并手动编写Functor 实例。我怀疑最漂亮的方法可能涉及编写recursion-schemes 的双功能克隆,但这似乎有点矫枉过正。
    • 嗯,有趣;我刚刚重新发现了镜头库的Plated 部分;它有 para,你可以使用 transformOf 作为 cata,它可能有点不同,但具有相似的功能,而不需要破坏你的底层数据结构,这很好。到目前为止,它可以满足我的需求。
    猜你喜欢
    • 2017-05-04
    • 1970-01-01
    • 1970-01-01
    • 2016-05-16
    • 2022-01-21
    • 1970-01-01
    • 2018-12-06
    • 2015-03-16
    • 2020-01-30
    相关资源
    最近更新 更多