gpt4 book ai didi

haskell - 如何使用Monad变形金刚来组合不同的(纯净的和不纯净的)monad?

转载 作者:行者123 更新时间:2023-12-02 20:58:42 25 4
gpt4 key购买 nike

我正在编写我的第一个Haskell应用程序,并且很难理解Monad变压器的用法。

示例代码:

-- Creates a new user in the system and encrypts their password
userSignup :: Connection -> User -> IO ()
userSignup conn user = do
-- Get the encrypted password for the user
encrypted <- encryptPassword $ password user. -- encryptPassword :: Text -> IO (Maybe Text)
-- Updates the password to the encrypted password
-- if encryption was successful
let newUser = encrypted >>= (\x -> Just user { password = x })
-- Inserts the user using helper function and gets the result
result <- insertUser (createUser conn) newUser
return result
where
insertUser :: (User -> IO ()) -> (Maybe User) -> IO ()
insertUser insertable inuser = case inuser of
Just u -> insertable u -- Insert if encryption was successful
Nothing -> putStrLn "Failed to create user" -- Printing to get IO () in failure case


问题:


insertUser没有产生任何输出的情况下,如何避免像打印到控制台这样的操作(在 IO辅助功能中完成)。更具体地说,如何为IO monad创建“零”值?
如何组合两种不同类型的monad(在本例中为Maybe和IO),以便我可以组合它们的内容并产生一个统一的结果,其中可能包含结果或可能的错误?
如何以更容易理解的功能性方式表达此类问题?

最佳答案

编辑:更新的答案以匹配您更新的问题。

需要明确的是,您的代码示例中实际上并未使用任何monad转换器。您只是将一个单子嵌套在另一个单子中。有关使用真实monad变压器MonadT的示例,请参阅我对第二个问题的回答。

对于第一个问题,正如@David Young所说,可以使用return ()

showSuccess :: Bool -> IO ()
showSuccess success =
if success then putStrLn "I am great!"
else return () -- fail silently


更一般而言,如果函数针对某些类型 IO a返回 a,则始终可以通过使用 return函数返回没有关联的IO操作的“纯”值。 (这就是 return的目的!)在函数返回 IO ()的情况下,类型 ()的唯一值是值 (),因此唯一的选择是 return ()。对于其他类型为 IO aa,您需要 return一些类型为 a的值。如果希望该选项返回一个值或不返回一个值,则需要输入 IO (Maybe a)类型或使用 MaybeT转换器,如下所示。

对于第二个问题,您基本上是在问如何在 Maybe monad中巧妙地表达嵌套计算:

let newUser = encrypted >>= (\x -> Just user { password = x })


在外部 IO monad中。

通常,嵌套monad中的大量计算很难编写,并且会导致难看,不清楚的代码。这就是发明monad变压器的原因。它们使您可以利用从多个monad借来的设施并将它们捆绑在一个monad中。然后,所有bind( >>=)和 return操作,以及所有do-syntax都可以引用同一单monad中的操作,因此当您执行以下操作时,不必在“ IO模式”和“也许模式”之间切换重新阅读和编写代码。

重写代码以使用转换器涉及从 MaybeT包导入 transformers转换器并定义您自己的monad。您可以随意命名,尽管您可能会键入很多,所以我通常使用简短的名称,例如 M

import Control.Monad
import Control.Monad.Trans
import Control.Monad.Trans.Maybe

-- M is the IO monad supplemented with Maybe functionality
type M = MaybeT IO
nothing :: M a
nothing = mzero -- nicer name for failing in M monad


然后,您可以按以下方式重写 userSignUp函数:

userSignUp :: Connection -> User -> M ()
userSignUp conn user = do
encrypted <- encryptPassword (password user) -- encrypted :: String
let newUser = user { password = encrypted } -- newUser :: User
insertUser <- createUser conn -- insertUser :: User -> M ()
insertUser newUser


我在注释中添加了一些类型注释。请注意,新的 M monad会确保确保已对 <-运算符绑定的每个变量进行检查。如果任何步骤返回 Nothing,则处理将中止。如果某个步骤返回 Nothing,则 Just x将自动展开。您通常不必处理(甚至看不到) xNothing

您的其他函数也必须位于 Just monad中,并且它们可以返回值(成功)或指示失败,如下所示:

encryptPassword :: String -> M String
encryptPassword pwd = do
epwd <- liftIO $ do putStrLn "Dear System Operator,"
putStrLn $ "Plaintext password was " ++ pwd
putStr $ "Please manually calculate encrypted version: "
getLine
if epwd == "I don't know" then nothing -- return failure
else return epwd -- return success


请注意,他们可以使用 M将操作提升到基础IO monad,因此所有IO操作均可用。否则,它们可以返回纯值(通过 liftIO)或使用 return(我的 MaybeT别名)在 nothing层中发出信号失败。

现在剩下的唯一事情就是提供一种“运行”自定义monad的功能(这涉及将其从 mzero转换为 M a,因此实际上可以从 IO a运行它)。对于此monad,定义很简单,但是最好定义一个函数,以防您的 main monad更复杂:

runM :: M a -> IO (Maybe a)
runM = runMaybeT


下面包括带有存根代码的完整的完整工作示例。

对于第三个问题,使其“更具功能性”并不一定会使它更易于理解,但其想法是利用诸如 M之类的monad运算符或诸如 =<<之类的应用运算符来模仿一元形式的函数形式。上下文。以下是 <*>我的monad转换器版本的等效“更多功能”形式。尚不清楚这比上面的命令式“ do-notation”版本更容易理解,并且肯定更难编写。

moreFunctionalUserSignUp :: Connection -> User -> M ()
moreFunctionalUserSignUp conn user
= join $ createUser conn
<*> (setPassword user <$> encryptPassword (password user))
where
setPassword u p = u { password = p }


您可以想象这大致等同于纯函数计算:

  createUser conn (setPassword user (encryptPassword (password user)))


但是运算符适当的话,它就可以进行类型检查,成为单子计算。 (为什么需要 userSignUp?什至不问。)

完整的示例

import Control.Monad
import Control.Monad.Trans
import Control.Monad.Trans.Maybe

-- M is the IO monad supplemented with Maybe functionality
type M = MaybeT IO
nothing :: M a
nothing = mzero -- nicer name for failing in M monad

runM :: M a -> IO (Maybe a)
runM = runMaybeT

data User = User { username :: String, password :: String } deriving (Show)
data Connection = Connection

userSignUp :: Connection -> User -> M ()
userSignUp conn user = do
encrypted <- encryptPassword (password user) -- encrypted :: String
let newUser = user { password = encrypted } -- newUser :: User
insertUser <- createUser conn -- insertUser :: User -> M ()
insertUser newUser

encryptPassword :: String -> M String
encryptPassword pwd = do
epwd <- liftIO $ do putStrLn "Dear System Operator,"
putStrLn $ "Plaintext password was " ++ pwd
putStr $ "Please manually calculate encrypted version: "
getLine
if epwd == "I don't know" then nothing -- return failure
else return epwd -- return success

createUser :: Connection -> M (User -> M ())
createUser conn = do
-- some fake storage
return (\user -> liftIO $ putStrLn $ "stored user record " ++ show user)

main :: IO ()
main = do username <- putStr "Username: " >> getLine
password <- putStr "Password: " >> getLine

let user = User username password
result <- runM (userSignUp Connection user)

case result of
Nothing -> putStrLn "Something failed -- with MaybeT, we can't tell what."
Just () -> putStrLn "Success!"

关于haskell - 如何使用Monad变形金刚来组合不同的(纯净的和不纯净的)monad?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46649804/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com