2

While doing user interface programming I often encounter the need to render a list of values and add some related information between the rendered values. In the following code example I'm rendering numerical values into strings where they appear in parenthesis and render the distance of two values into a string that gets placed between the renders of the values. The code works but I'm wondering if an implementation of the mystery function is available as part of the Haskell standard library. I'm also interested in names used for this function in other libraries, as using human readable words makes googling easier.

mystery :: (a -> b) -> (a -> a -> b) -> [a] -> [b]
mystery n d [] = []
mystery n d [x] = [n x]
mystery n d (x:xs) = (n x) : (d x (head xs)) : mystery n d xs

node x = "(" ++ show x ++ ")"
distance x y = "-" ++ (show $ abs $ x - y) ++ "-"
render xs = concat $ mystery node distance xs
-- render [25, 68, 54, 15] == "(25)-43-(68)-14-(54)-39-(15)"
cyberixae
  • 843
  • 5
  • 15
  • 3
    I can't see an easier way than your approach. In principle we could use `concat $ zipWith next ys (tail ys) where next x y = [n x, d x y]` but that will miss the last number. We would need a more general `zipWith` that allows to handle the case where the first list is nonempty but the second one is. I can't find that in the libraries. We could use `++ [n $ last ys]` but that's ugly in my eye. – chi Dec 20 '20 at 22:15

1 Answers1

2

Your mystery function is actually doing a couple things at once, and if you separate out the behaviors, it may be a little easier to see what's going on.

First, you're mapping n on all the elements. We can write that as fmap n xs. Next, you're constructing new elements d x y for all pairs of adjacent elements x and y. We can write that as zipWith d xs (tail xs).

The last step is taking these two constructions and making a new list with elements that alternate back and forth between them. Interestingly, this was a question asked 9 years ago, but still doesn't have a super satisfying answer. Probably the simplest answer is to define your own function:

alternate [] ys = ys
alternate (x:xs) ys = x : alternate ys xs

Then, we can define mystery as a one-liner:

mystery n d x = alternate (fmap n x) (zipWith d x (tail x))

If you really want it to be a one-liner, the best I could come up with was using concat and transpose in a bit of a funky way:

mystery n d x = concat $ transpose $ (pure $ fmap n x) <> (pure $ zipWith d x (tail x))

Basically, we're making singleton lists out of the two components using pure, fusing them together with <>, then transposeing this "list of lists" to get the elements properly interspersed, and concating the result.

DDub
  • 3,884
  • 1
  • 5
  • 12
  • Thank you for your answer! The `alternate` function seems to sometimes be called `flatZip`. The name `alternate` is not bad either. It sounds like a name you might use while working with Python generators. It might work with Haskell because of the lazy nature but might not work so well across different languages and environments. It's weird how the `alternate` / `flatZip` function seems to be missing from most standard libraries. I added an answer to the original question detailing the flatZip approach. Perhaps one day it will be available in standard libraries, who knows. – cyberixae Dec 22 '20 at 12:33