假设您有一个包含多个值构造函数的数据结构,例如LogMessage
数据结构,如下所示:
data LogMessage = Unknown String
| LogMessage MessageType TimeStamp String
如果可以正确解析消息,则会有一些额外数据,然后是String
。如果它无法解析,那么它只是一个全能的Unknown String
。
或者假设您使用Either String String
之类的内容,以便处理Left String
或Right String
。
现在假设您要对底层数据应用相同的处理步骤,而不管它驻留在哪个值构造函数中。
例如,我可能想要检测LogMessage
字符串中的某个单词,所以我可以使用这样的函数:
detectWord :: String -> LogMessage -> Bool
detectWord s (Unknown m) = isInfixOf s (map toLower m)
detectWord s (LogMessage _ _ m) = isInfixOf s (map toLower m)
或者可以轻松编写以处理Either String String
作为输入而不是LogMessage
。
在这两种情况下,我都必须重复完全相同的代码(isInfixOf ...
部分),因为由于不同值构造函数上的模式匹配,我必须提取它将以不同方式操作的基础数据。
对于每个不同的值构造函数匹配,必须重复/“复制 - 粘贴”代码。
如何在没有复制/粘贴代码的情况下编写这些类型的Haskell函数?我怎样才能编写底层逻辑一次,然后解释如何在许多不同的值构造函数模式中使用它?
简单地将其移动到辅助辅助函数会减少字符数,但并不能真正解决问题。例如,下面的想法在实质上没有比第一种情况更好的“不要重复自己”:
helper :: String -> String -> Bool
helper s m = isInfixOf s (map toLower m)
detectWord :: String -> LogMessage -> Bool
detectWord s (Unknown m) = helper s m
detectWord s (LogMessage _ _ m) = helper s m
我们不得不为每种不同的模式说同样的事情。
答案 0 :(得分:6)
编写一个在任何一种情况下获取消息的函数。然后,您不需要为不关心的用途编写单独的案例:
getMsg (Unknown m) = m
getMsg (LogMessage _ _ m) = m
detectWord s log = infixOf s (map toLower (getMsg log))
请注意,某些内容必须检查您的类型的情况,而getMsg
与这些行相比最小。
答案 1 :(得分:4)
尝试使用view patterns。
{-# 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
使用Generics和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)