在 Polysemy 中将 monadic 值转换为 IO
Converting a monadic value to an IO in Polysemy
我正在尝试使用 Haskell 中的 webdriver
和 polysemy
构建自动化功能测试套件。我已经定义了适当的效果并将它们解释为 webdriver WD monad,但现在我被卡住了。
我有一个 Member BrowserMaster r => Sem r ()
类型的值,其中 BrowserMaster
是我的自定义功能。
这是解释器:
runBrowserMaster :: Members [Embed WD.WD, Embed IO] r => Sem (BrowserMaster ': r) a -> Sem r a
runBrowserMaster = interpret $ \case
ClickElement bmSelector ->
let action = (WD.findElem (bmSelectoToSelector bmSelector) >>= WD.click :: WD.WD ())
in embed action
{- ... -}
现在我想知道如何将 Embed WD.WD
效果转换为 Embed IO
,这样我最终只得到一个效果。
我尝试制作一个解释器:
runWebDriver :: Member (Embed IO) r => Sem (Embed WD.WD ': r) a -> Sem r a
runWebDriver = interpret $
\a -> embed $ runSession chromeConfig . finallyClose $ do
setImplicitWait 60000
setWindowSize (1024, 768)
unEmbed a
(这里runSession chromeConfig . finallyClose
是一个WD a -> IO a
)
它确实有效,但它似乎为每个命令启动一个新的浏览器会话,而不是只启动一次,在其中完成所有操作并关闭。
我有一种直觉,它必须在资源获取和释放方面做一些事情,但我无法理解这一点,无法将它们放在一起。
请记住,每次执行 BrowserMaster
效果的动作时,都会执行每个解释器。所以每次它 运行s runWebDriver
解释器,这解释了为什么它创建,运行s 并关闭会话。
我认为您想做的是 create/delete 会话一次,然后在该会话中执行整个代码。
此外,由于 WD
已经是 IO
的包装器,我认为没有必要嵌入这两种效果。
我不熟悉您的代码和 webdriver
库,但我认为这与以下内容类似:
main :: IO ()
main = runSession chromeConfig . finallyClose $ do
setImplicitWait 60000
setWindowSize (1024, 768)
runM . runBrowserMaster $ myBusinessCode
runBrowserMaster :: Member (Embed WD.WD) r => Sem (BrowserMaster ': r) a -> Sem r a
runBrowserMaster = interpret $ \case
ClickElement bmSelector ->
let action = (WD.findElem (bmSelectoToSelector bmSelector) >>= WD.click :: WD.WD ())
in embed action
{- ... -}
注意:如果您需要在解释器中 运行 某些 IO
代码,请使用 liftIO
使其成为 WD
操作,例如liftIO $ putStrLn "Hello world"
.
PS:我建议将 runBrowserMaster
解释器重命名为 browserMasterToWD
之类的名称,因为它更好地代表了它的作用:根据 WD
动作。
我正在尝试使用 Haskell 中的 webdriver
和 polysemy
构建自动化功能测试套件。我已经定义了适当的效果并将它们解释为 webdriver WD monad,但现在我被卡住了。
我有一个 Member BrowserMaster r => Sem r ()
类型的值,其中 BrowserMaster
是我的自定义功能。
这是解释器:
runBrowserMaster :: Members [Embed WD.WD, Embed IO] r => Sem (BrowserMaster ': r) a -> Sem r a
runBrowserMaster = interpret $ \case
ClickElement bmSelector ->
let action = (WD.findElem (bmSelectoToSelector bmSelector) >>= WD.click :: WD.WD ())
in embed action
{- ... -}
现在我想知道如何将 Embed WD.WD
效果转换为 Embed IO
,这样我最终只得到一个效果。
我尝试制作一个解释器:
runWebDriver :: Member (Embed IO) r => Sem (Embed WD.WD ': r) a -> Sem r a
runWebDriver = interpret $
\a -> embed $ runSession chromeConfig . finallyClose $ do
setImplicitWait 60000
setWindowSize (1024, 768)
unEmbed a
(这里runSession chromeConfig . finallyClose
是一个WD a -> IO a
)
它确实有效,但它似乎为每个命令启动一个新的浏览器会话,而不是只启动一次,在其中完成所有操作并关闭。
我有一种直觉,它必须在资源获取和释放方面做一些事情,但我无法理解这一点,无法将它们放在一起。
请记住,每次执行 BrowserMaster
效果的动作时,都会执行每个解释器。所以每次它 运行s runWebDriver
解释器,这解释了为什么它创建,运行s 并关闭会话。
我认为您想做的是 create/delete 会话一次,然后在该会话中执行整个代码。
此外,由于 WD
已经是 IO
的包装器,我认为没有必要嵌入这两种效果。
我不熟悉您的代码和 webdriver
库,但我认为这与以下内容类似:
main :: IO ()
main = runSession chromeConfig . finallyClose $ do
setImplicitWait 60000
setWindowSize (1024, 768)
runM . runBrowserMaster $ myBusinessCode
runBrowserMaster :: Member (Embed WD.WD) r => Sem (BrowserMaster ': r) a -> Sem r a
runBrowserMaster = interpret $ \case
ClickElement bmSelector ->
let action = (WD.findElem (bmSelectoToSelector bmSelector) >>= WD.click :: WD.WD ())
in embed action
{- ... -}
注意:如果您需要在解释器中 运行 某些 IO
代码,请使用 liftIO
使其成为 WD
操作,例如liftIO $ putStrLn "Hello world"
.
PS:我建议将 runBrowserMaster
解释器重命名为 browserMasterToWD
之类的名称,因为它更好地代表了它的作用:根据 WD
动作。