6

I need a function that does this:

>>> func (+1) [1,2,3]
[[2,2,3],[2,3,3],[2,3,4]]

My real case is more complex, but this example shows the gist of the problem. The main difference is that in reality using indexes would be infeasible. The List should be a Traversable or Foldable.

EDIT: This should be the signature of the function:

func :: Traversable t => (a -> a) -> t a -> [t a]

And closer to what I really want is the same signature to traverse but can't figure out the function I have to use, to get the desired result.

func :: (Traversable t, Applicative f) :: (a -> f a) -> t a -> f (t a)
Danny Navarro
  • 2,733
  • 1
  • 18
  • 22
  • 1
    That's impossible in a general case, since the output of the applied function doesn't have to be same type as the input. So intermediate result might be something like `["Foo", 2, 3]`. You should reformulate your requirement. – Eugene Sh. May 10 '17 at 18:58
  • 1
    @EugeneSh. I think it's implicit that the `(+1)` function must be an endo, otherwise it's impossible, as you explained. – chi May 10 '17 at 18:59
  • 4
    This is a job for "Clowns and Jokers". One might choose to illustrate intermediate states more flexibly and precisely. E.g., [([],(1,2),[2,3]),([2],(2,3),[3]), ([2,3],(3,4),[])], where each element of the list of states has the corresponding element of the input "in focus", we see the outputs we already have on the left, the inputs we have yet to visit on the right, and the before-and-after pair for the element in focus. For such structures, the mapped function need not be an endo. – pigworker May 10 '17 at 19:33
  • @pigworker That's precisely what I'm looking for. I also have to perform some actions with **side-effects** on the element under focus before moving on. Is there a an nice implementation of "Clowns and Jokers" in some Haskell package? – Danny Navarro May 10 '17 at 19:44
  • 3
    I haven't seen one. The literate source code for the paper is eminently googleable. Some helpful person took a copy, here https://github.com/mak/course-haskell/blob/master/zipppers/CJ.lhs which is just as well as I can't find mine. – pigworker May 10 '17 at 21:31
  • An implementation of `Clown` and `Joker` can be found in [bifunctors](http://hackage.haskell.org/package/bifunctors) by Edward A. Kmett. – erisco May 11 '17 at 00:12

4 Answers4

3

It looks like @Benjamin Hodgson misread your question and thought you wanted f applied to a single element in each partial result. Because of this, you've ended up thinking his approach doesn't apply to your problem, but I think it does. Consider the following variation:

import Control.Monad.State

indexed :: (Traversable t) => t a -> (t (Int, a), Int)
indexed t = runState (traverse addIndex t) 0
  where addIndex x = state (\k -> ((k, x), k+1))

scanMap :: (Traversable t) => (a -> a) -> t a -> [t a]
scanMap f t =
  let (ti, n) = indexed (fmap (\x -> (x, f x)) t)
      partial i = fmap (\(k, (x, y)) -> if k < i then y else x) ti
  in  map partial [1..n]

Here, indexed operates in the state monad to add an incrementing index to elements of a traversable object (and gets the length "for free", whatever that means):

> indexed ['a','b','c']
([(0,'a'),(1,'b'),(2,'c')],3)

and, again, as Ben pointed out, it could also be written using mapAccumL:

indexed = swap . mapAccumL (\k x -> (k+1, (k, x))) 0

Then, scanMap takes the traversable object, fmaps it to a similar structure of before/after pairs, uses indexed to index it, and applies a sequence of partial functions, where partial i selects "afters" for the first i elements and "befores" for the rest.

> scanMap (*2) [1,2,3]
[[2,2,3],[2,4,3],[2,4,6]]

As for generalizing this from lists to something else, I can't figure out exactly what you're trying to do with your second signature:

func :: (Traversable t, Applicative f) => (a -> f a) -> t a -> f (t a)

because if you specialize this to a list you get:

func' :: (Traversable t) => (a -> [a]) -> t a -> [t a]

and it's not at all clear what you'd want this to do here.

K. A. Buhr
  • 45,621
  • 3
  • 45
  • 71
  • Ah, you're absolutely right, I did misread the question. This is the right answer. I think the code is a bit more complex than it needs to be, though. You don't need the `indexed` infrastructure wherein you label every element and then go back over them to compute the result: you can keep the counting-down-index idea of my answer and just change the `== 0` to `>= 0`, applying `f` to every element before the target index, rather than just the single element itself. – Benjamin Hodgson May 11 '17 at 12:10
  • I'd specialize the second signature to something like: `(a -> IO a) -> [a] -> IO [a]` but it's alright, I can take it from here. – Danny Navarro May 11 '17 at 12:46
  • Thanks for the name `scanMap`, that's a perfect fit for the function in question. – Danny Navarro May 11 '17 at 12:55
2

On lists, I'd use the following. Feel free to discard the first element, if not wanted.

> let mymap f [] = [[]] ; mymap f ys@(x:xs) = ys : map (f x:) (mymap f xs)
> mymap (+1) [1,2,3]
[[1,2,3],[2,2,3],[2,3,3],[2,3,4]]

This can also work on Foldable, of course, after one uses toList to convert the foldable to a list. One might still want a better implementation that would avoid that step, though, especially if we want to preserve the original foldable type, and not just obtain a list.

chi
  • 111,837
  • 3
  • 133
  • 218
  • @DannyNavarro Yes, I agree -- I saw your last edit which clarified that. My solution is too specific as it is. – chi May 10 '17 at 20:06
2

I just called it func, per your question, because I couldn't think of a better name.

import Control.Monad.State

func f t = [evalState (traverse update t) n | n <- [0..length t - 1]]
    where update x = do
            n <- get
            let y = if n == 0 then f x else x
            put (n-1)
            return y

The idea is that update counts down from n, and when it reaches 0 we apply f. We keep n in the state monad so that traverse can plumb n through as you walk across the traversable.

ghci> func (+1) [1,1,1]
[[2,1,1],[1,2,1],[1,1,2]]

You could probably save a few keystrokes using mapAccumL, a HOF which captures the pattern of traversing in the state monad.

Benjamin Hodgson
  • 42,952
  • 15
  • 108
  • 157
  • Unfortunately I can't rely on length in my real world problem. I can't use anything that is `List` specific. – Danny Navarro May 10 '17 at 19:36
  • @DannyNavarro `length :: Foldable t => t a -> Int` – Benjamin Hodgson May 10 '17 at 19:48
  • I didn't realize `length` is now implemented in terms of `Foldable`. That's very nice. I still think I can't apply it to my problem but it's worth exploring this approach. – Danny Navarro May 10 '17 at 20:00
  • @DannyNavarro From what I can glean from your comments, it seems like you want to perform a side effect each time you map one of the elements? It's very simple to generalise my function for `f :: a -> m a`: use the state monad _transformer_ over `m` and just `lift` your action when you map it. I'll edit my answer when I get off the train – Benjamin Hodgson May 10 '17 at 20:20
  • PS it pays to specify your actual problem in the question. If you ask a question about a somewhat-similar-but-different problem you'll get answers (like this one!) that answer your question correctly but still fail to address your real-world needs. https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem – Benjamin Hodgson May 10 '17 at 20:23
  • PPS. An alternative approach: Observe that at the end of this process you've applied `f` to each element of the traversable exactly once. You could traverse the whole traversable with `f` once, to do the side effects, and then put the list of results back in the input traversable by adapting the code above and/or something like [this](http://stackoverflow.com/q/41522422/1523776) – Benjamin Hodgson May 10 '17 at 20:38
  • @BenjaminHodgson Saying something is not list-specific because it uses `Foldable` is a bit silly. Every function using `Foldable` can be factored into a list-specific function and uses of `toList`. The `Foldable` class simply corresponds to some arbitrary, distinguished conversion to lists. – Derek Elkins left SE May 11 '17 at 08:08
  • @DerekElkins [I know what `Foldable` does](http://stackoverflow.com/questions/42521811/why-is-haskell-list-not-a-type-class/42522630#42522630). The questioneer was asking for a function which worked on any traversable, as opposed to lists specifically. He didn't realise that `length` now works on any foldable, he thought it was a list function. – Benjamin Hodgson May 11 '17 at 12:04
1

This sounds a little like a zipper without a focus; maybe something like this:

data Zippy a b = Zippy { accum :: [b] -> [b], rest :: [a] }

mapZippy :: (a -> b) -> [a] -> [Zippy a b]
mapZippy f = go id where
  go a [] = []
  go a (x:xs) = Zippy b xs : go b xs where
    b = a . (f x :)

instance (Show a, Show b) => Show (Zippy a b) where
  show (Zippy xs ys) = show (xs [], ys)


mapZippy succ [1,2,3]
-- [([2],[2,3]),([2,3],[3]),([2,3,4],[])]

(using difference lists here for efficiency's sake)

To convert to a fold looks a little like a paramorphism:

para :: (a -> [a] -> b -> b) -> b -> [a] -> b
para f b [] = b
para f b (x:xs) = f x xs (para f b xs)

mapZippy :: (a -> b) -> [a] -> [Zippy a b]
mapZippy f xs = para g (const []) xs id where
  g e zs r d = Zippy nd zs : r nd where
    nd = d . (f e:)

For arbitrary traversals, there's a cool time-travelling state transformer called Tardis that lets you pass state forwards and backwards:

mapZippy :: Traversable t => (a -> b) -> t a -> t (Zippy a b)
mapZippy f = flip evalTardis ([],id) . traverse g where
  g x = do
    modifyBackwards (x:)
    modifyForwards (. (f x:))
    Zippy <$> getPast <*> getFuture
Community
  • 1
  • 1
oisdk
  • 9,763
  • 4
  • 18
  • 36
  • In the end I'm going to use something similar to this because it matches more closely what I really needed. However I'm marking @K. A. Buhr answer as accepted because, for the example I provided, it works very well. – Danny Navarro May 11 '17 at 12:55