有什么办法 "lift" 构造函数吗?

Is there any way to "lift" a constructor?

假设我有一个函数(下例中的 plusOne)只接受 returns 一个 Int。但是我没有 Int;相反,我有一个 Maybe Int;如果这个 Maybe Int 包含一个值,那么我想将它传递给 plusOne 并获得任何 plusOne returns 的 Just,或者如果它是 Nothing 然后我想让这个 Nothing 传播。

在这种情况下,我们 liftM 以优雅的方式对其进行编码:

import Control.Monad

plusOne :: Int -> Int
plusOne n =
    n+1 -- a very complicated computation that is failsafe

main =
    let n = Just 15 -- a very complicated computation that can fail
    in let res = liftM plusOne n
    in print res

到目前为止一切顺利。但是,这样的事情也可以用构造函数来完成吗?

忘记 plusOne。现在我有:data SomeData = SomeData Int 并且想从我的 Maybe Int 获得一个 Maybe (SomeData Int)。该解决方案似乎明显不够优雅:

import Control.Monad

data SomeData = SomeData Int
    deriving Show -- so that print works

main =
    let n = Just 15
    in let res = n >>= (\nn -> Just (SomeData nn))
    -- alternatively: in let res = liftM (\nn -> SomeData nn) n
    in print res

上面的两种解决方案(使用 >>= 或使用 liftM)都需要通过匿名 lambda 函数,根据我的直觉,这不是必需的,只会破坏代码。有办法避免吗?我能否以某种方式 "lift" someData 构造函数,就像我可以在第一个片段中提升 plusOne 一样?

你可以简单地使用函数组合:

main =
    let n = Just 15
        res = n >>= Just . SomeData
    in print res

尽管正如 Robin Zigmond 指出的那样,您可以简单地使用 fmap,因为 fmap f x 等同于 x >>= return . f,而 return == Just 相当于 Maybe单子。

main = let n = Just 15
           res = fmap SomeData n  -- or SomeData <$> n
       in print res

故事的寓意:不要在只需要仿函数的地方使用 monad。