3
\$\begingroup\$

I wrote a short Haskell script to compress and decompress via the use of run length encoding.

The concept is simple enough, n equal items x in a row will be replaced by (n, x), decompressing is just the reverse.

For example:

> runLengthCompress "foooo barrr"
[(1,'f'),(4,'o'),(1,' '),(1,'b'),(1,'a'),(3,'r')]

As you can see the four consecutive o have been substituted by (4,'o')

About the code itself I fear I have written too little points (arguments) to my functions, hindering readability but I am not sure, maybe the lack of points makes it more readable, not less.

Also I feel like my areInverses function is already built-in into Haskell and that I am rebuilding the wheel with it.

Without further ado, here is the code:

import Data.List
import Control.Monad
import Control.Arrow
runLengthExpand :: [(Int, a)] -> [a]
runLengthExpand = concat . map (uncurry replicate)
runLengthCompress :: Eq a => [a] -> [(Int, a)]
runLengthCompress = (map (length &&& head)) . group
areInverses :: Eq b => (a -> b) -> (b -> a) -> [b] -> Bool
areInverses f g = all ((==) =<< f . g)
examples :: [[Int]]
examples = [ [3, 3, 3, 5, 5, 7, 1], [1,1,1,0,0,0,5] ]
main :: IO()
main = do
 print $ runLengthCompress "foooo barrr"
 print $ areInverses runLengthExpand runLengthCompress examples
asked Nov 3, 2015 at 18:53
\$\endgroup\$
1
  • \$\begingroup\$ lpaste.net/5825040676416389120 - hlint is your friend. Try quickcheck to get things like your areInverses. \$\endgroup\$ Commented Nov 4, 2015 at 11:24

1 Answer 1

3
\$\begingroup\$

You want to import only the functions you're actually going to need:

import Data.List (group)
import Control.Arrow ((&&&))

As Gurkenglas already said, use concatMap instead of concat . map. This leads to

runLengthExpand :: [(Int, a)] -> [a]
runLengthExpand = concatMap (uncurry replicate)

Your runLengthCompress is fine, although (&&&) might not be known to newcomers.

Now, areInverses is too obfuscated:

About the code itself I fear I have written too little points (arguments) to my functions, hindering readability but I am not sure, maybe the lack of points makes it more readable, not less.

Pointfree style isn't necessarily easier to read:

5 Problems with pointfree

Point-free style can (clearly) lead to Obfuscation when used unwisely.

We can split its functionality in multiple functions:

isInverseOn :: Eq a => (a -> b) -> (b -> a) -> a -> Bool
isInverseOn f g x = x == g (f x)
areInverses :: Eq a => (a -> b) -> (b -> a) -> [a] -> Bool
areInverses f g = all (isInverseOn f g)

This removes the need for =<< completely and is easier to read than your previous (==) =<< f . g.

However, that only checks whether g is an inverse of f, not whether f is the inverse of g. For example, g's codomain might not be the completely covered by f's domain. So a proper areInverses should also check isInverseOn g f for some appropriate values.

You can use QuickCheck to generate appropriate strings and lists of pairs, although you need to generate the latter by hand.

answered Nov 27, 2015 at 11:03
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.