【问题标题】:What's wrong with my recursive list construction?我的递归列表构造有什么问题?
【发布时间】:2011-11-18 23:12:45
【问题描述】:

我已经简化了相关功能。我在单子中构建列表时遇到问题。我怀疑有优先级问题。

newtype Boundary = MkBoundary Integer

testFunc :: [Boundary] -> [Maybe Integer]
testFunc (MkBoundary x:xs)
   | (even x) = Just x : testFunc xs
   | otherwise = Nothing : testFunc xs
testFunc _ = []

这按预期工作。但我需要在单子中工作。我将在此示例中使用 IO

testFunc :: [Boundary] -> IO [Maybe Integer]
testFunc (MkBoundary x:xs)
   | (even x) = return $ Just x : testFunc xs
   | otherwise = return $ Nothing : testFunc xs
testFunc _ = []

无论我如何尝试操纵优先级,这都会中断。

test.hs:6:35:
    Couldn't match expected type `[Maybe Integer]'
                with actual type `IO [Maybe Integer]'
    In the return type of a call of `testFunc'
    In the second argument of `(:)', namely `testFunc xs'
    In the second argument of `($)', namely `Just x : testFunc xs'
Failed, modules loaded: none.

我想要完成的是构建一个列表,然后将其返回给 IO。我做错了什么?

【问题讨论】:

    标签: list haskell monads


    【解决方案1】:

    luqui 回答了你的问题,我会记下一个有用的组合子。

    如果您想对列表的所有元素执行一元操作,请使用“mapM”。定义为:

     mapM f [] = return []
     mapM f (x:xs) = do y <- f x
                        ys <- mapM f xs
                        return (y:ys)
    

    或类似的东西。 [如果你知道其他一些组合器,你可以写mapMliftM2foldr。]

     testFunc = mapM f
         where f (MkBoundary x) | even x = do print x
                                              return $ Just x
                                | otherwise = return Nothing
    

    在 GHCi 中测试:

    *Main> testFunc [MkBoundary 2, MkBoundary 3, MkBoundary 4]
    2
    4
    [Just 2,Nothing,Just 4]
    

    【讨论】:

      【解决方案2】:

      问题是testFunc xs 返回一个IO [Maybe Integer],而您将它用作列表的尾部,就好像它是一个[Maybe Integer]。你需要提取:

      | (even x) = do
          xs' <- testFunc xs
          -- now xs' is of type [Maybe Integer]
          return $ Just x : xs'
      

      或者,更简洁的表达方式:

      | (even x) = (Just x :) <$> testFunc xs
      

      ((&lt;$&gt;) 来自Control.Applicative 并且有类型

      (<$>) :: (a -> b) -> IO a -> IO b
      

      专门针对 IO 。它将函数应用于“内部”一元计算的值。)

      哦,还有missingno所说的:-)

      【讨论】:

        【解决方案3】:

        你忘了改变第二种情况

        test_func _ = return []
                   -- ^^^^^^
        

        另外,我认为你的示例函数可以更清楚地写成

        test_func :: [Boundary] -> [Maybe Integer]
        test_func = ...
        
        monadic_test_func = [Boundary] -> IO [Maybe Integer]
        monadic_test_func = return . test_func
        

        这使纯代码与讨厌的 monad 东西分开。它还使您不必输入三次“return”! :)


        最后,你为什么要首先创建这样一个函数? monad 部分(至少在您的示例中)似乎与主要功能逻辑有些无关(因为您只是在执行return)。

        也许您使用了一些不错的库函数来保持您的函数纯净且不受影响?

        --instead of 
        monadic_value >>= monadic_test_func
        
        --use
        fmap test_func monadic_value
        -- or equivalently
        test_func <$> monadic_value
        liftM test_func monadic_value
        

        【讨论】:

        • 他说他已经简化了有问题的功能。他可能需要在这个函数中的某个地方进行 I/O。无论如何,学习如何将纯代码转换为一元代码都是一项宝贵的技能。
        • 哦,但我也可以看到他可能有一个误解,即如果他需要在 monad 中使用它,他需要重写他非常好的纯函数,在这种情况下,这个答案是正确的跟踪。
        • @luqui - 我确实有这种误解。我不知道它是从哪里来的。我应该知道的更好。在我的项目的早期,我通过将返回类型放在某个 monad 中来解决了一堆问题,而我应该一直使用 fmap 或 liftM。我想在做正确的事情上会有一些问题,我很快就会把它带到这个论坛上。
        猜你喜欢
        • 1970-01-01
        • 2021-09-07
        • 2014-04-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多