在重用多个值构造函数模式匹配项时,避免重复代码



假设您有一个具有多个值构造函数的数据结构,例如LogMessage数据结构,如下所示:

data LogMessage = Unknown String 
                | LogMessage MessageType TimeStamp String

如果消息可以正确解析,则它有一些额外的数据,然后是一个String。如果无法解析,那么它只是一个包罗万象的Unknown String

或者假设您正在使用类似 Either String String 的东西,因此您可能正在处理Left StringRight 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

在那里,我们必须对每种不同的模式说同样的话。

编写一个在任一情况下获取消息的函数。 然后,您无需为不关心的用途编写单独的案例:

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

请注意,某些内容必须检查您类型的案例,并且getMsg与这些行一样小。

简单,不会让人讨厌你

尝试使用视图模式。

{-# 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)

最新更新