Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Missing Haskell primitive to apply a function to each element of a list successively?

In Haskell, it is well known that the map primitive can be used to apply a given function to all elements of a list:

 λ> map toUpper "abcd"
"ABCD"
 λ> 

While trying to generate all partitions of a finite set (list), the following, similar primitive would be handy:

 λ> sap toUpper "abcd"
["Abcd","aBcd","abCd","abcD"]
 λ> 

with sap standing for successive applications. The type signature would be:

sap :: (a -> a) -> [a] -> [[a]]

For example, part of the partitions of set "abcd" can be obtained from the partitions of "bcd" by sap'ing them with ('a':).

 λ> pbcd = [["b","c","d"],["b","cd"],["bc","d"],["c","bd"],["bcd"]]
 λ> 
 λ> concatMap (sap ('a':)) pbcd
[["ab","c","d"],["b","ac","d"],["b","c","ad"],["ab","cd"],["b","acd"],["abc","d"],["bc","ad"],["ac","bd"],["c","abd"],["abcd"]]
 λ> 

and the 5 missing partitions can then be obtained by adding 'a' as its own separate singleton.

My problem is that I have been unable to locate such a primitive in the language libraries, and that Hoogle, given the type signature, returns nothing of interest.

Does such a primitive as sap exist somewhere in the Haskell language libraries ??? Or is there a way to write it that is so short and simple that it does not even deserve to be a separate function, putting it below the so-called Fairbairn threshold ?

Footnote: It is possible to write sap like this:

sap :: (a -> a) -> [a] -> [[a]]
sap fn ls = fst  $  foldr  op  ([], [])  ls
   where  op x (ll,tl) = ( ((fn x):tl) : map (x:) ll , x:tl )

Essentially you start with [[fn (last ls)]] as a seed and then progress leftwards. But this seems pedestrian not simple.

like image 409
jpmarinier Avatar asked Jan 08 '20 22:01

jpmarinier


3 Answers

It seems like the simplest version of this is direct recursion:

sap :: (a -> a) -> [a] -> [[a]]
sap _ [] = []
sap f (x:xs) = (f x : xs) : map (x:) (sap f xs)

One possible exploration of this is as a paramorphism, which gives access to the recursive result and the unprocessed remainder together.

sap f = para step where
    step Nil = []
    step (Cons x (xs, rest)) = (f x : xs) : map (x:) rest

(Not checked, might have silly errors)

I don't see that as a huge improvement though. I don't see any deep insights in that decomposition of recursion from the problem itself.

For that, well... I've used holesOf for a generalized version of this in the past.

sap :: Traversable t => (a -> a) -> t a -> [t a]
sap f = map (peeks f) . holesOf traverse

Now that definitely says something. It has generalized the type to work on all instances of Traversable. On the other hand, the theoretical chunks involved were so overpowered for the end result that I'm not sure what it actually is that it says. On the third(?) hand, it looks pretty.

like image 65
Carl Avatar answered Nov 15 '22 05:11

Carl


Or is there a way to write it that is so short and simple that it does not even deserve to be a separate function, putting it below the so-called Fairbairn threshold?

This. The functionality is rarely needed, and the (a -> a) argument doesn't make for a very generic application.

A short and simple implementation can be achieved with list recursion:

sap :: (a -> a) -> [a] -> [[a]]
sap _ []     = []
sap f (x:xs) = (f x:xs):((x:) <$> sap f xs)
like image 35
Bergi Avatar answered Nov 15 '22 06:11

Bergi


I don't think it exists anywhere, although proving it negatively is of course impossible.. Another way to write sap, which I would probably prefer over using foldr,

sap f ls = zipWith (alterWith f) [0..] (iterate ls)
  where alterWith f i ls = take i ls ++ f (ls !! i) : drop (i+1) ls

alterWith is available as adjust in https://hackage.haskell.org/package/fft-0.1.8.6/docs/Math-FFT-Base.html#v:adjust, but I would very much not bring something so heavyweight in for that function. I often have something like alterWith defined in a project already, though, and if so that allows sap to be elided in favor of the call to zipWith above.

like image 45
Izaak Weiss Avatar answered Nov 15 '22 05:11

Izaak Weiss