5

I would like to express the following Haskell code, using only functor algebra (i.e. - not relying on any specific container type, such as List):

ys = zipWith (+) (head xs : repeat 0)
                 (tail xs ++ [y])

It seems to me that there ought to be a way to do this, relying only on Foldable (or, maybe, Traversable), but I can't see it.

I'm wondering:

  1. Is there a general notion of first and rest for Foldable/Traversable functors?
  2. Is there an accepted idiomatic way, using only functor algebra, to shift the contents of a Foldable/Traversable functor? (Note that the computation above might be described in English as, "Shift in one value from the right, and add back the value that falls of on the left to the new first value.")
dbanas
  • 1,707
  • 14
  • 24
  • Not using the `Foldable` class, but much more convenient and very general: [`_head`](http://hackage.haskell.org/package/lens-4.16.1/docs/Control-Lens-Cons.html#v:_head) and `_tail` from the lens library. – leftaroundabout Jun 11 '18 at 12:16

4 Answers4

6

You can find the first or last element of a Foldable using the First or Last monoids from Data.Monoid.

foldMap (Last . Just)  :: Foldable t => t a -> Last a
foldMap (First . Just) :: Foldable t => t a -> First a

All Foldable are convertible to a list, and so because you can find the head and tail of a list, you can do so for any Foldable.

toList = foldr (:) [] :: Foldable t => t a -> [a]

That said, the tail will have a list type and not that of the Foldable (unless it was too a list). This is ultimately because not all that is Foldable can implement an uncons. For example:

data Pair a = Pair a a

This is Foldable, but you could not represent the tail of a Pair using a Pair.

erisco
  • 14,154
  • 2
  • 40
  • 45
  • 2
    While getting the head and tail may not make sense while staying within a given `Foldable` type, "rotating" any `Foldable` while keeping the spine exactly the same does seem like a well-defined operation. Just not one that's available, I don't think. e.g. for `Pair`, `rotate (Pair x y) = Pair y x` is a perfectly cromulent operation. – Daniel Wagner Jun 10 '18 at 15:43
  • @DanielWagner interesting. Well perhaps there is something in recursion schemes that can help. `foldr` will not cut it. – erisco Jun 10 '18 at 15:45
  • @DanielWagner I expect you'd need `Traversable` – Benjamin Hodgson Jun 10 '18 at 16:33
  • 1
    @BenjaminHodgson Thanks, your comment, though it should have been obvious in retrospect, told me how to implement what was asked for. I've updated my answer with your insight. =) – Daniel Wagner Jun 10 '18 at 16:55
4

The first part of your question (combining the first value of a structure with one thing and leaving the rest the same) can be done in a straightforward way with Traversable. We'll use State, start it off with the function we want to apply, and modify it to id immediately.

onlyOnHead :: Traversable t => (a -> a) -> t a -> t a
onlyOnHead f xs = evalState (traverse go xs) f where
    go x = do
        fCurrent <- get
        put id
        return (fCurrent x)

You can rotate elements with a similar idea: we'll rotate a list, and stuff that in our State as the thing to draw elements from.

rotate :: Traversable t => t a -> t a
rotate xs = evalState (traverse go xs) (rotateList (toList xs)) where
    rotateList [] = []
    rotateList vs = tail vs ++ [head vs]

    go _ = do
        v:vs <- get
        put vs
        return v
Daniel Wagner
  • 145,880
  • 9
  • 220
  • 380
  • 2
    Another way with Traversable is `rotate xs = ys where (x0, ys) = mapAccumR (\x y -> (y, x)) x0 xs`. (Or a similar trick with `runState`, probably.) – David Fletcher Jun 10 '18 at 17:14
  • 1
    @DavidFletcher Very slick! – Daniel Wagner Jun 10 '18 at 18:08
  • Interesting. This also shows that if a `T a` type is meant to be opaque and satisfy some invariant (say, a red-black tree) then it can be made into a `Foldable` but should not be a `Traversable`. – chi Jun 10 '18 at 21:37
  • @chi Why? The invariant on red-black trees is on the spine, not the contents, and `traverse` preserves the spine exactly. So no problem there. – Daniel Wagner Jun 10 '18 at 23:04
  • @DanielWagner What I wrote is misleading, sorry. I was thinking about the BST invariant (which is only a part of the red-black invariant). That would be broken by rotation. (We can not allow `Functor` on that too, since that would already allow one to break the BST invariant) – chi Jun 11 '18 at 09:00
  • 2
    @chi Oh, sure. But also think of `Set` vs `Map` -- each has an invariant, and consequently `Set` can't support `Functor` or any of the other fun classes, but `Map` can support `Functor` and `Traversable` because the invariant is on its keys, which those instances don't touch. – Daniel Wagner Jun 11 '18 at 10:46
1

To rotate, you don't need any ugly partial functions. This weird Applicative will do the trick.

data Foo a t where
  Cons :: (a -> q -> t) -> a -> Foo a q -> Foo a t
  Nil :: t -> Foo a t

instance Functor (Foo a) where
  fmap f (Cons g x xs) = Cons (\p q -> f (g p q)) x xs
  fmap f (Nil q) = Nil (f q)

instance Applicative (Foo a) where
  pure = Nil
  liftA2 f (Nil t) ys = f t <$> ys
  liftA2 f (Cons g x xs) ys = Cons (\a (q,b) -> f (g a q) b) x (liftA2 (,) xs ys)

You can rotate a Foo:

rot :: Foo a t -> Foo a t
rot n@(Nil _) = n
rot (Cons g0 a0 as0) = go g0 a0 as0
  where
    go :: (a -> q -> t) -> a -> Foo a q -> Foo a t
    go g a n@(Nil _) = Cons g a n
    go g a (Cons h y ys) = Cons g y (go h a ys)

And run one to get a result:

runFoo :: Foo a t -> t
runFoo (Nil t) = t
runFoo (Cons g x xs) = g x (runFoo xs)

Putting it all together,

rotate :: Traversable t => t a -> t a
rotate = runFoo . rot . traverse (\a -> Cons const a (Nil ()))

Then rotate [1..10] = [2..10] ++ [1].

dfeuer
  • 48,079
  • 5
  • 63
  • 167
0

Thanks to all who responded!

Note that Conal Elliott's shaped-types library also has some useful machinery in this regard.

dfeuer
  • 48,079
  • 5
  • 63
  • 167
dbanas
  • 1,707
  • 14
  • 24