4

我正在尝试学习 Haskell,所以我在 Haskell 中尝试了 Project Euler 的问题 26: http ://projecteuler.net/problem=26

我对这个问题的解决方案是这样的:

answer26 = answer26' 1000
answer26' n = snd $ maximum $ map (\x -> cycleLength x [1]) [2..n - 1]
    where
    cycleLength n (r:rs)
        | i /= Nothing      = (1 + fromJust i, n)
        | r < n             = cycleLength n $ (10*r):r:rs
        | otherwise         = cycleLength n $ (r `mod` n):r:rs
        where i = elemIndex r rs

我意识到这不是最有效的算法,但认为它是天真的 O(n^3) (其中 n = 1000)不是这样的问题。不过,我担心的是,根据我对 monad 的理解,它们的主要属性之一是它们在某种意义上“标记”了任何使用过 monad 的东西。函数“fromJust”似乎直接与它背道而驰。它为什么存在?另外,假设它的存在是合理的,我在上面的代码中使用它是好的做法吗?

4

3 回答 3

11

通常不鼓励使用部分函数(可能不返回值的函数)。函数喜欢headfromJust存在是因为它们偶尔很方便;您有时可以编写更短的代码,这对学习者来说更容易理解。许多函数式算法用 和 表示,在head概念上tail与.fromJusthead

通常最好使用模式匹配,并避免使用偏函数,因为它允许编译器为您捕获错误。在您的代码片段中,您已仔细检查该值是否为 never Nothing,但在大型现实生活代码库中,代码可能有很多年历史,有 1000 行长并由许多开发人员维护。开发人员很容易重新订购一些代码并错过这样的检查。使用模式匹配,它就在代码结构中,而不仅仅是在一些任意的 Bool 表达式中。

fromJust用模式匹配替换你的使用并不难:

answer26 = answer26' 1000
answer26' n = snd $ maximum $ map (\x -> cycleLength x [1]) [2..n - 1]
    where
    cycleLength n (r:rs) = case elemIndex r rs of
            Just i  -> (1 + i, n)
            Nothing -> if r < n
                        then cycleLength n $ (10*r):r:rs
                        else cycleLength n $ (r `mod` n):r:rs

而且(我认为)结果也更清晰一些。

编辑:TypeclassopediafromJust中提到了一个显然“理论上可以”使用的地方,尽管你需要我以外的人来解释 wtf 这一切都是关于.. ;)

于 2013-08-17T13:10:37.923 回答
10

monad 接口不包含用于从 monad 中“提取”值的任何特定函数,仅用于将它们放入 ( return) 中。

但是,它也不禁止这些功能。当它们存在时,它们将特定于每个 monad(因此有大量的 run* 函数:runIdentity, runReader, runWriter, runState... 每个都有不同的参数。)

按照设计,IO它没有任何这样的“退出”功能,因此它用于在 monad 中“捕获”不纯的值。但一般来说,“不能出去”并不是单子的要求。重要的是他们尊重单子定律。

使用comonads时,情况正好相反。extract每个comonad都必须实现一个从它们中提取值的通用函数( )。但是“将值放入”的功能,当它们存在时,会因每个特定的comonad(envstore ...)而有所不同

至于fromJust,最好尽可能避免它,因为它是一个部分函数,​​可能在运行时无法匹配。

于 2013-08-17T13:14:35.957 回答
3

这种模式很常见,甚至有一个函数:maybe :: b -> (a -> b) -> Maybe a -> b

在您的情况下,如果您执行 \x -> (cycleLength x [1], x),即在 cycleLength 之外构造对:

    cycleLength n (r:rs) = maybe (cycleLength n rs') (1+) $ elemIndex r rs where
      rs'
        | r < n = (10*r):r:rs
        | otherwise = (r `mod` n):r:rs

此外,因为您只是在寻找最大值,而不是实际值,所以即使使用 id 而不是 (1+),它也可以工作。

于 2013-08-18T11:09:55.987 回答