4
\$\begingroup\$

I need to create an instance of Show of a somewhat complicated type that represents a C++ function. The "serialization" gives me a Haskell interpretation of the said function.

It works as I wish but it looks utterly horrible, with the string concatenations, point-free conversions and nested let bindings. How could I improve readability of the code?

instance Show Function where
 show (Function templateArgs returnT fname fArgs) =
 let toLowerIfPolymorphic argName =
 let lcArg = strToLower argName in
 if lcArg `elem` (map snd templateArgs)
 then lcArg
 else id argName in
 concat [
 fname, " :: (",
 intercalate ", " (
 filter (/="") . map (
 \(concept, tIdent) ->
 if not $ elem concept ["Any", "typename", "class"]
 then concept ++ " " ++ strToLower tIdent
 else ""
 ) $ templateArgs 
 ), ") => ",
 intercalate " -> " (
 map toLowerIfPolymorphic . map (show . fst) $ fArgs
 ),
 (if not $ null fArgs then " -> " else ""),
 toLowerIfPolymorphic . show $ returnT
 ]

The Function is:

data Function = Function {
 funcTemplateArgs :: [(Concept, Identifier)],
 funcReturnT :: CppType,
 funcName :: String,
 funcArgs :: [(CppType, Identifier)]
 }

CppType is basically a sum-type representing Scalar and Parameterzied types. Just synonyms for tuples. Concept / Identifier is a String synonym. Everything is an instance of Show.

Also I find the if condition then f else id very strange. "Do nothing if false" is somewhat silly.

asked Apr 23, 2015 at 20:41
\$\endgroup\$
0

1 Answer 1

1
\$\begingroup\$

I would clean this up by bringing what you can to the top level (hiding it using a module export list if need be) as new functions. There are also some sectioning/alias changes that I think would make it more readable.

toLowerIfPolymorphic :: [(Concept, Identifier)] -> Identifier -> Identifier
toLowerIfPolymorphic templateArgs argName
 | lcArg `elem` (snd $ unzip templateArgs) = lcArg
 | otherwise = argName
 where
 lcArg = strToLower argName

I often prefer guards to if _ then _ else _, it helps cut down on word soup (and I still don't understand why we don't have boolCase :: Bool -> a -> a -> a in the Prelude). Consider how snd . unzip more specifically captures your intent than using a more powerful but generic higher-order function like map.

The bit with ["Any", "typename", "class"] should work out better with some reordering. Instead of applying a partial transformation with a sentinel value (i.e. filter (not . null) with occasional "" values), drop the junk and then transform the good stuff.

showConstraints :: [(Concept, Identifier)] -> [String]
showConstraints templateArgs = map display . filter valid $ templateArgs
 where
 display (concept, ident) = concept ++ " " ++ strToLower ident
 valid (concept, _) = concept `notElem` ["Any", "typename", "class"]

Then your Show instance ends up looking something like this.

instance Show Function where
 show (Function templateArgs returnT fName fArgs)
 = fName
 ++ " :: ("
 ++ intercalate ", " (showConstraints templateArgs)
 ++ ") => "
 ++ intercalate " -> " (map (toLowerIfPolymorphic templateArgs . show . fst) $ fArgs)
 ++ (if null fArgs then "" else " -> ")
 ++ toLowerIfPolymorphic templateArgs (show returnT)

I'd probably take this one step further though to handle not having any constraints (in which case you don't want "() =>" to show up, presumably) and write one more helper function.

wrap :: String -> String -> String -> String
wrap _ "" _ = ""
wrap start content end = start ++ content ++ end

Giving—

instance Show Function where
 show (Function templateArgs returnT fName fArgs)
 = fName
 ++ " :: "
 ++ wrap "(" (intercalate ", " $ showConstraints templateArgs) ") => "
 ++ wrap "" (intercalate " -> " $ map (lowerPoly . show . fst) fArgs) " -> "
 ++ lowerPoly (show returnT)
 where
 lowerPoly = toLowerIfPolymorphic templateArgs
answered Apr 23, 2015 at 22:27
\$\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.