Functor
不起作用,因为我用它来更改树的内容,而不是它的索引方案。看来我需要两个实例Functor
,一个用于更改内容,另一个用于更改数组索引。
您的直觉是正确的:作用于该Neighborhood a
领域的函子会做您需要的事情,将这样的东西称为“函子”是正确的。这是一种可能的重构applyPermutation
:
{-# LANGUAGE LambdaCase #-}
-- I prefer case syntax for this sort of definition; with it, there is less stuff
-- that needs to be repeated. LambdaCase is the icing on the cake: it frees me
-- me from naming the Tree a argument -- without it I would be forced to write
-- mapOverNeighborhoods f t = case t of {- etc. -}
mapOverNeighborhoods :: (Neighborhood a -> Neighborhood a) -> Tree a -> Tree a
mapOverNeighborhoods f = \case
Empty -> Empty
Leaf x -> Leaf x
Internal x ts -> Internal x (f (mapOverNeighborhoods f <$> ts))
applyPermutation :: Permutation -> Tree a -> Tree a
applyPermutation perm = mapOverNeighborhoods applyPermutation'
where applyPermutation' = ixmap (Xp, Zm) (perm !)
(您可能更愿意走得更远,使用UnitDirection -> UnitDirection
直接采用而不是Neighborhood a -> Neighborhood a
. - 在 an 中重新排列索引Array
并不像将任意函数应用于索引那样简单。)
定义另一个函子的这种尝试有两个限制:
这两个问题由诸如lens之类的光学库解决(但是,如果您最终在代码库中仅将光学用于这件事,那么您可能更喜欢microlens以获得较小的依赖足迹)。
{-# LANGUAGE TemplateHaskell #-} -- makeLenses needs this.
{-# LANGUAGE DeriveFunctor #-} -- For the sake of convenience.
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveTraversable #-}
-- Record fields on sum types are nasty; these, however, are only here for the
-- sake of automatically generating optics with makeLenses, so it's okay.
data Tree a
= Empty
| Leaf { _value :: a }
| Internal { _value :: a, _neighborhood :: Neighborhood a }
deriving (Eq, Show, Functor, Foldable, Traversable)
makeLenses ''Tree
applyPermutation :: Permutation -> Tree a -> Tree a
applyPermutation perm = over neighborhood applyPermutation'
where applyPermutation' = ixmap (Xp, Zm) (perm !)
over
(中缀拼写:)%~
字面意思是fmap
允许选择目标。我们通过传递一个适当的 optic 来做到这一点(在这种情况下,neighborhood
,它是Traversal
针对树中所有邻域的 ——over neighborhood
可以读作“所有邻域的地图”)。请注意,我们无法更改邻域类型的事实不是问题(而且,在其他情况下,可能会有类型更改的光学器件)。
最后一点,类型neighborhoods
是Traversal' (Tree a) (Neighborhood a)
. 如果我们扩展Traversal'
类型同义词,我们得到:
GHCi> :t neighborhood
neighborhood
:: Applicative f =>
(Neighborhood a -> f (Neighborhood a)) -> Tree a -> f (Tree a)
虽然进入原因会使这个答案太长,但值得注意的是,这很像traverse
for Tree
...
GHCi> :set -XTypeApplications
GHCi> :t traverse @Tree
traverse @Tree
:: Applicative f => (a -> f b) -> Tree a -> f (Tree b)
...除了它作用于邻域而不是值(参见 和 之间的平行线fmap
)mapOverNeighborhoods
。事实上,如果您要充分实现traverse
具有该类型的类比,您将能够使用它而不是由makeLenses
.