在 Polysemy 中将 monadic 值转换为 IO

Converting a monadic value to an IO in Polysemy

我正在尝试使用 Haskell 中的 webdriverpolysemy 构建自动化功能测试套件。我已经定义了适当的效果并将它们解释为 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动作。