# Converting from ClientSession to String and back

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
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
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
99     , dbid = db
100     , uuid = ud
101     , prand = pr}


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

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.

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