2
\$\begingroup\$

I have a data structure called ClientSession which I convert to a String separated by | for each element and back to the data structure.

 18 data ClientSession = ClientSession                                                                                                                 
 19     { sec      :: Int                                                                                                                              
 20     , nsec     :: Int                                                                                                                              
 21     , username :: String                                                                                                                           
 22     , dbid     :: Integer                                                                                                                          
 23     , uuid     :: Int                                                                                                                              
 24     , prand    :: Int                                                                                                                              
 25     } deriving (Ord, Eq)                                                                                                                 
 26                                                                                                                                                    
 27 instance Show ClientSession where                                                                                                                  
 28     show (ClientSession                                                                                                                            
 29          { sec = s                                                                                                                                 
 30          , nsec = ns                                                                                                                               
 31          , username = un                                                                                                                           
 32          , dbid = db                                                                                                                               
 33          , uuid = ud                                                                                                                               
 34          , prand = pr}) = intercalate "|" ls                                                                                                       
 35         where ls = [show s, show ns, un, show db, show ud, show pr]

Then I have a set of functions to read that string and create a ClientSession from the result. The problem is that this set of functions are so ugly that I lay sleepless at night.

I'm looking for feedback on how to make this code more Haskell. It doesn't feel Haskell to me, it feels like I have solved it in another language and then just ported the code straight off. Like when you translate a natural language in Google Translate.

I have thought of different solutions. Maybe I could use template Haskell, or Typeable/Generics type classes in some way, or chain the reading using >>=, or something similar.

I'm still new to Haskell, I've used it on my spare time very little over a couple of years.

 37 fromString :: String -> Maybe ClientSession                                                                                                        
 38 fromString ss = fromParts $ endBy "|" ss         
 ...
 74 fromParts :: [String] -> Maybe ClientSession                                                                                                       
 75 fromParts (s:ns:un:db:ud:pr:[])                                                                                                                    
 76     = newSessionM (readMaybe s) (readMaybe ns) (Just un) (readMaybe db) (readMaybe ud) (readMaybe pr)                                              
 77 fromParts _ = Nothing                                                                                                                              
 78                                                                                                                                                    
 79 newSessionM :: Maybe Int                                                                                                                           
 80             -> Maybe Int                                                                                                                           
 81             -> Maybe String                                                                                                                        
 82             -> Maybe Integer                                                                                                                       
 83             -> Maybe Int                                                                                                                           
 84             -> Maybe Int                                                                                                                           
 85             -> Maybe ClientSession                                                                                                                 
 86 newSessionM (Just s)                                                                                                                               
 87             (Just ns)                                                                                                                              
 88             (Just un)                                                                                                                              
 89             (Just db)                                                                                                                              
 90             (Just ud)                                                                                                                              
 91             (Just pr) = return $ newSession s ns un db ud pr                                                                                       
 92 newSessionM _ _ _ _ _ _ = Nothing                                                                                                                  
 93                                                                                                                                                    
 94 newSession :: Int -> Int -> String -> Integer -> Int -> Int -> ClientSession                                                                       
 95 newSession s ns un db ud pr = ClientSession                                                                                                        
 96     { sec = s                                                                                                                                      
 97     , nsec = ns                                                                                                                                    
 98     , username = un                                                                                                                                
 99     , dbid = db                                                                                                                                    
100     , uuid = ud                                                                                                                                    
101     , prand = pr}
\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

First note that if your username contains a bar '|', you won't be able to parse the output back. So be sure to check for this.

Since you're already using a parser, it's much easier to read the whole ClientSession using the parser instead of splitting the string and merging the values manually.

First let's define two helper functions:

import Control.Applicative
import Data.Char (isDigit)
import Data.Functor
import Data.List
import Text.ParserCombinators.ReadP as P

parseInt :: (Read a, Integral a) => ReadP a
parseInt = read <$> munch1 isDigit <* P.optional (char '|')

This one reads one or more digits, consumes '|' if there is one, and converts the digits into a number. (Combinator <* runs two actions sequentially, but keeps only the result of the first one.)

parseNotBar :: ReadP String
parseNotBar = munch (/= '|') <* P.optional (char '|')

Similarly tihs second function reads a string until it hits '|', consumes the '|' optionally and returns the string.

Then it's easy to construct a Read instance. The parser library already has a handy function readP_to_S that converts a parser into a ReadS function:

instance Read ClientSession where
    readsPrec _ = readP_to_S parseClientSession
      where
        parseClientSession :: ReadP ClientSession
        parseClientSession =
            ClientSession <$> parseInt <*> parseInt <*> parseNotBar
                          <*> parseInt <*> parseInt <*> parseInt

or more shortly

instance Read ClientSession where
    readsPrec _ = readP_to_S $
            ClientSession <$> parseInt <*> parseInt <*> parseNotBar
                          <*> parseInt <*> parseInt <*> parseInt

Note: The last part is similar to what you have done in your newSessionM. Realizing that Maybe is an Applicative instance you could have written

newSessionM s ns un db ud pr
    = ClientSession <$> s <*> ns <*> un <*> db <*> ud <*> pr

instead. This generalizes it to any Applicative, not just Maybe, so it can be used on Maybe as well as ReadP or any other applicative parser, for example as

parseClientSession =
    newSessionM parseInt parseInt parseNotBar
                parseInt parseInt parseInt

However, using <$> and <*> makes the notation usually short enough so that we use the combinators directly without the need to define such helper functions.

\$\endgroup\$
1
  • \$\begingroup\$ Now that's the Haskell way to do it! :) Thanks for taking time to explain! I clearly have much to learn. \$\endgroup\$ Aug 18, 2013 at 19:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.