Avoid code duplication when reusing multiple value constructor templates

Suppose you have a data structure with several value constructors, for example, a LogMessage data LogMessage , for example:

 data LogMessage = Unknown String | LogMessage MessageType TimeStamp String 

If the message can be parsed correctly, it contains additional data, then String . If it cannot be parsed, then this is just a catch-all Unknown String .

Or suppose you are working with something like an Either String String , so you can deal with a Left String or a Right String .

Now let's say that you want to apply the same processing steps to the underlying data, regardless of which value constructor it is in.

For example, I could find a specific word in the LogMessage lines, so I could have a function like this:

 detectWord :: String -> LogMessage -> Bool detectWord s (Unknown m) = isInfixOf s (map toLower m) detectWord s (LogMessage _ _ m) = isInfixOf s (map toLower m) 

or just that easy to write to handle an Either String String as input instead of LogMessage .

In both cases, I have to repeat the same code (part of isInfixOf ... ), because I need to extract the basic data, which it will work differently due to the matching patterns for different value constructors.

It’s bad that you need to repeat / copy-paste the code for each constructor of different values.

How to write such Haskell functions without copy / paste code? How can I write basic logic only once, but then explain how it should be used in many different constructors of value constructors?

Just moving it to a helper helper function will reduce the number of characters, but will not really solve the problem. For example, the following idea does not make sense with respect to “do not repeat yourself” than in the first case:

 helper :: String -> String -> Bool helper sm = isInfixOf s (map toLower m) detectWord :: String -> LogMessage -> Bool detectWord s (Unknown m) = helper sm detectWord s (LogMessage _ _ m) = helper sm 

There again we must say the same for every other template.

+5
source share
2 answers

Write a function that receives the message anyway. Then you will not need to write separate use cases that do not care:

 getMsg (Unknown m) = m getMsg (LogMessage _ _ m) = m detectWord s log = infixOf s (map toLower (getMsg log)) 

Note that you will need to check for cases of your type, and getMsg will be about as minimal as those lines.

+6
source

Simple won't make people hate you

Try viewing templates .

 {-# LANGUAGE ViewPatterns #-} data LogMessage = Unknown String | LogMessage MessageType TimeStamp String stringOfLogMessage :: LogMessage -> String stringOfLogMessage (Unknown s) = s stringOfLogMessage (LogMessage _ _ s) = s detectWord :: String -> LogMessage -> Bool detectWord needle (stringOfLogMessage -> hay) = needle `isInfixOf` map toLower hay 

Sophisticated can make people hate you

Use Generics and Generics.Deriving.Lens .

 {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE NoImplicitPrelude #-} module Lib where import BasePrelude import Control.Lens import Generics.Deriving.Lens data LogMessage = Unknown String | LogMessage () () String deriving (Generic) detectWord :: String -> LogMessage -> Bool detectWord needle = allOf tinplate (isInfixOf needle . map toLower) 
+4
source

Source: https://habr.com/ru/post/1242491/


All Articles