4

I am writing a Haskell function that operates on a list of ByteString values. I need to do a different operation on the first and last items (which may be the same if the list has only one item).

Specifically, I want to write out the following:

  "item-1\
  \item-2\
  \item-3\
  ...
  \item-n"

where item-1 starts with a double quote and ends with a backslash and item-n starts with a backslash and ends with a double quote. All items between item-1 and item-n start and end with backslashes. I am emitting a base64 encoded value as a Haskell String for some code generation. I have already broken the original (long) base64-encoded ByteString into chunks that are 64 characters long each.

Ralph
  • 31,584
  • 38
  • 145
  • 282

4 Answers4

11

I just realized that my question is a dumb one.

I can just use intercalate to inject the "\\\n\\" between the items and then prepend and append a double quote:

import qualified Data.ByteString.Lazy.Char8 as L
(L.pack "\"") `L.append` (L.intercalate "\\\n\\" items) `L.append` (L.pack "\"")

Sample output:

"H4sIAAAAAA\
\AAA2NgAAMm\
\CMXA7JRYxI\
\Am5JafD2Uy\
\AgDvdHs6Lg\
\AAAA==\
\"
Ralph
  • 31,584
  • 38
  • 145
  • 282
  • 1
    You perhaps know this, but if these files get long and complicated you might take a look at `Data.ByteString.Lazy.Builder`. Your case is sort of akin to the `encodeUtf8CSV` used as an illustration toward the top of the documentation page http://www.haskell.org/ghc/docs/7.6.1/html/libraries/bytestring-0.10.0.0/Data-ByteString-Lazy-Builder.html – applicative Jan 02 '13 at 19:35
2

You can also consider splitting your list using:

  • "head" to get the first element of the list
  • "tail" to get all but the first element of the list
  • "init" to get all but the last element of the list
  • "last" to get the last element of the list

So [head a] ++ init (tail a) ++ [last a] == a.

That way, you can change the first and last elements of the list individually and map a function over the "init" part.

Alfonso Villén
  • 373
  • 1
  • 9
  • 2
    That's inefficient, though. Both `init` and `last` need to traverse the entire list, quite unnecessarily. – leftaroundabout Jan 02 '13 at 02:10
  • I know there are more efficient ways to do that thing, but this is a very simple way. I start making things like that and then finding out how I can improve them. – Alfonso Villén Jan 02 '13 at 09:11
0

I've been in this situation several times and I've never found a good idiomatic solution. Sometimes intercalate isn't enough. Here's one simple solution.

-- | Annotate elements of a list with Bools, the first of which is True if
-- the element is the head of the list, the second of which is True if the
-- element is the last of the list. Both are True for singleton.
markbounds :: [a] -> [(a, Bool, Bool)]
markbounds [] = []
markbounds [x] = [(x, True, True)]
markbounds (x:xs) = (x, True, False) : tailbound xs
  where
    tailbound [y] = [(y, False, True)]
    tailbound (y:ys) = (y, False, False): tailbound ys

For example:

λ> markbounds [1,2,3]
[(1,True,False),(2,False,False),(3,False,True)]

λ> forM_ (markbounds [1,2,3]) $ \(x, isFirst, isLast) -> when isLast $ print x
3
James Brock
  • 3,236
  • 1
  • 28
  • 33
0

I have build (my first!) library that has a reasonably-generic solution to the "different first and last functions" question. It's on GitHub (https://github.com/davjam/MapWith) and Hackage (http://hackage.haskell.org/package/MapWith).

Initially inspired by James's markbounds function, but can:

  • work on more general structures than just lists (all Traversable types)
  • add parameters directly to a function (not just create tuples)
  • add different types of parameters (first / last / next element / previous element / index from start or end, etc)
  • allows creation of additional types of parameter.
  • add any number of parameters

For example:

> andFirstLast [1,2,3]
[(1,True,False),(2,False,False),(3,False,True)]

> mapWithM_ ((\x isL -> when isL $ print x) & isLast) [1,2,3]
3

> mapWith ((,,,) & isLast & prevElt <-^ eltIx) [1,7,4]
[(1,False,Nothing,2),(7,False,Just 1,1),(4,True,Just 7,0)]

More examples at https://github.com/davjam/MapWith/blob/master/doc/examples.hs.

Any feedback would be gratefully received. Thanks!

David
  • 309
  • 1
  • 9