Warning: differential calculus. I do not consider this answer completely seriously, since it is rather a sledgehammer. But this is a sledgehammer that I support, so why not play sports? Besides the fact that he is more likely than the investigator wanted to know what I apologize for. This is an attempt to dig a deeper structure for the reasonable answers that have already been proposed.
The class of differentiable functors offers at least the following bits and fragments.
class (Functor f, Functor (D f)) => Diff (f :: * -> *) where type D f :: * -> * up :: (I :*: D f) :-> f down :: f :-> (f :.: (I :*: D f))
I guess I better unpack some of these definitions. They are the basic kit for combining functors. This thing
type (f :-> g) = forall a. fa -> ga
abbreviation for polymorphic types of functions for operations with containers.
It shows the constant, identity, composition, amount and product for the containers.
newtype K ax = K a deriving (Functor, Foldable, Traversable, Show) newtype I x = I x deriving (Functor, Foldable, Traversable, Show) newtype (f :.: g) x = C {unC :: f (gx)} deriving (Functor, Foldable, Traversable, Show) data (f :+: g) x = L (fx) | R (gx) deriving (Functor, Foldable, Traversable, Show) data (f :*: g) x = fx :*: gx deriving (Functor, Foldable, Traversable, Show)
D computes the derivative of the functor by the usual calculus rules. It describes how to present a sibling context for an element. Let's read the types of these operations again.
up :: (I :*: D f) :-> f
says we can make an integer f from a pair of one element and context for that element in f . It is βupβ because we are moving up in the hierarchical structure, focusing on the whole, and not on one element.
down :: f :-> (f :.: (I :*: D f))
In the meantime, we can decorate each element in a differentiable functional structure with its context, calculating all the ways of going down to one element in particular.
I will leave Diff instances for the base components until the end of this answer. For lists we get
instance Diff [] where type D [] = [] :*: [] up (I x :*: (xs :*: ys)) = xs ++ x : ys down [] = C [] down (x : xs) = C ((I x :*: ([] :*: xs)) : fmap (id *:* ((x :) *:* id)) (unC (down xs)))
Where
(*:*) :: (fa -> f' a) -> (ga -> g' a) -> (f :*: g) a -> (f' :*: g') a (ff' *:* gg') (f :*: g) = ff' f :*: gg' g
So for example
> unC (down [0,1,2]) [I 0 :*: ([] :*: [1,2]),I 1 :*: ([0] :*: [2]),I 2 :*: ([0,1] :*: [])]
selects each item in context in turn.
If f also Foldable , we get a generalized operator !! ...
getN :: (Diff f, Foldable f) => fx -> Int -> (I :*: D f) x getN fn = foldMap (: []) (unC (down f)) !! n
... with an added bonus, which we get both the context of the element and the element itself.
> getN "abcd" 2 I 'c' :*: ("ab" :*: "d") > getN ((I "a" :*: I "b") :*: (I "c" :*: I "d")) 2 I "c" :*: R ((I "a" :*: I "b") :*: L (K () :*: I "d"))
If we want the functor to propose a replacement of two elements, it would be better to differentiate twice, and its derivative would fold better. Here it goes.
swapN :: (Diff f, Diff (D f), Foldable f, Foldable (D f)) => Int -> Int -> fx -> fx swapN ijf = case compare ij of { LT -> go ij ; EQ -> f ; GT -> go ji } where go ij = up (I y :*: up (I x :*: f'')) where I x :*: f' = getN fi -- grab the left thing I y :*: f'' = getN f' (j - 1) -- grab the right thing
Now itβs easy to remove the two elements and reconnect them again. If we number the positions, we just need to be careful about how to remove the elements of the renumbers of the position.
> swapN 1 3 "abcde" "adcbe" > swapN 1 2 ((I "a" :*: I "b") :*: (I "c" :*: I "d")) (I "a" :*: I "c") :*: (I "b" :*: I "d")
As always, you cannot dig too far below the ridiculous editing operation to find some kind of differential structure at work.
For completeness. The following are other examples described above.
instance Diff (K a) where -- constants have zero derivative type D (K a) = K Void up (_ :*: K z) = absurd z down (K a) = C (K a) instance Diff I where -- identity has unit derivative type DI = K () up (I x :*: K ()) = I x down (I x) = C (I (I x :*: K ())) instance (Diff f, Diff g) => Diff (f :+: g) where -- commute with + type D (f :+: g) = D f :+: D g up (I x :*: L f') = L (up (I x :*: f')) up (I x :*: R g') = R (up (I x :*: g')) down (L f) = C (L (fmap (id *:* L) (unC (down f)))) down (R g) = C (R (fmap (id *:* R) (unC (down g)))) instance (Diff f, Diff g) => Diff (f :*: g) where -- product rule type D (f :*: g) = (D f :*: g) :+: (f :*: D g) up (I x :*: (L (f' :*: g))) = up (I x :*: f') :*: g up (I x :*: (R (f :*: g'))) = f :*: up (I x :*: g') down (f :*: g) = C (fmap (id *:* (L . (:*: g))) (unC (down f)) :*: fmap (id *:* (R . (f :*:))) (unC (down g))) instance (Diff f, Diff g) => Diff (f :.: g) where -- chain rule type D (f :.: g) = (D f :.: g) :*: D g up (I x :*: (C f'g :*: g')) = C (up (I (up (I x :*: g')) :*: f'g)) down (C fg) = C (C (fmap inner (unC (down fg)))) where inner (I g :*: f'g) = fmap wrap (unC (down g)) where wrap (I x :*: g') = I x :*: (C f'g :*: g')