编写此函数的干净方法是什么?

时间:2021-03-04 03:41:58

标签: haskell

我想要帮助解决我正在尝试解决的问题。假设我有一个名为 Thing 的类型:

data Thing = ....

我想写一个函数,给定一个字符串,尝试将它与我状态中的一些东西匹配并返回一个 Thing

findFirstMatch :: String -> State (Maybe Thing)
    

问题是,要匹配该字符串,它需要一个可能的字符串列表来与之匹配。该列表由为我的状态定义的函数提供:

getPossibilities :: State String

现在,我需要调用第三个函数来接收原始字符串和一种可能性,并返回一个 Maybe Thing

tryToMatch :: String -> String -> State (Maybe Thing)

如何写findFirstMatch?我想这样做,但似乎不太干净,感觉可能已经实现了一些东西:

findFirstMatch :: String -> State (Maybe Thing)
findFirstMatch str = do
    xs <- getPossibilities
    firstNotNull (map (tryToMatch str) xs)

firstNotNull :: [State (Maybe Thing)] -> State (Maybe Thing)
firstNotNull [] = return Nothing
firstNotNull (x:xs) = do
    r <- x
    case r of
        Just _ -> return r
        Nothing -> firstNotNull xs

1 个答案:

答案 0 :(得分:2)

首先,如果您不使用 firstNotNull 编写 State,您可以将其清理干净。一个非常简单的传递是:

firstNotNull :: [Maybe Thing] -> Maybe Thing
firstNotNull [] = Nothing
firstNotNull (Just x:_) = Just x
firstNotNull Nothing:xs = firstNotNull xs

此外,您还可以使用 Data.Maybe 中的一些函数进一步简化:

import Data.Maybe (catMaybes, listToMaybe)

firstNotNull :: [Maybe a] -> Maybe a
firstNotNull = listToMaybe . catMaybes

现在,让我们将注意力转向 findFirstMatch,看看我们如何使用 firstNotNull 的这个简化版本。第一个问题是:tryToMatch真的需要住在State吗?毕竟,它已经可以访问与之匹配的两个 String。如果您可以将其类型更改为 tryToMatch :: String -> String -> Maybe Thing,那么您基本上就可以开始使用了。

另一方面,如果 tryToMatch 确实需要住在 State,那么还有一点点要做:我们需要传递 firstNotNull 一个 {{1} },但我们有一个[Maybe Thing]。我们可以通过使用 [State (Maybe Thing)] 来解决这个问题,如下所示:

sequenceA

请注意,这仅在您的 findFirstMatch :: String -> State (Maybe Thing) findFirstMatch str = do xs <- getPossibilities fmap firstNotNull $ sequenceA (map (tryToMatch str) xs) monad 足够懒惰时才有效。如果它太严格,它最终会找到所有匹配项,做太多工作(并搞砸性能),然后返回第一个。

从这里,我们可以认识到 StatesequenceA 的用法可以简化为对 map 的单个调用,如下所示:

traverse

这看起来干净多了!


当然,如果我们真的愿意,我们仍然可以走得更远。尚不清楚以下更改是否确实使代码更干净(相反,有一个强有力的论据认为它们使代码更难阅读),但无论如何让我们找点乐子吧。

我们可以选择通过适当使用 monadic 绑定使其成为单行代码,而不是使用 fmap firstNotNull $ traverse (tryToMatch str) xs

do

内部 lambda 可以很好地减少为:

findFirstMatch str = getPossibilities $ \xs -> (fmap firstNotNull $ sequenceA (map (tryToMatch str) xs))

这也可以减少eta:

findFirstMatch str = getPossibilities >>= fmap firstNotNull . sequenceA . map (tryToMatch str)

虽然我们正在研究它,但当我们可以内联它时,为什么还要定义 findFirstMatch = (getPossibilities >>=) . ((fmap firstNotNull . sequenceA) .) . map . tryToMatch

firstNotNull

在那里,您的整个功能都在一个凌乱的行中!

相关问题