1

我在 haskell 中使用dbus,我很难弄清楚如何导出执行有状态操作的 dbus 方法。下面是一个完整的例子来说明我卡在哪里。


假设您正在使用 dbus 编写计数器服务。当服务启动时,计数器最初为 0。服务定义了一个 dbus API,它公开了一个count方法,该方法返回计数器的当前值,以及一个update方法,该方法递增该计数器并返回新值。

这是我刚刚描述的行为的伪代码实现,使用消息传递样式的通信:

-- | Updates the given integer. 
update :: Int -> Int
update = (+1)

-- | main function with message-passing-style communication
mainLoop :: Int -> IO Int
mainLoop state = do
  case receiveMessage of
    "update" -> do -- increment / update counter
      sendReply $ update state
      mainLoop $ update state -- recurse
    "count" -> do -- return counter value
      sendReply state
      mainLoop state
    "stop" -> do -- stop the counting service
      exitSuccess

main :: IO ()
main = do
  mainLoop 0

但是,dbus 使用方法调用,而不是消息传递。所以,我需要能够导出一个countupdate我的消息传递示例中行为方式相同的方法。

我们将使用的存根是这样的:

-- | Updates the given integer. 
update :: Int -> Int
update = (+1)

main :: IO ()
main = do
  let initialState = 0
  dbus <- connectSession
  export dbus "/org/counter/CounterService"
    [ autoMethod "org.counter.CounterService" "update" ({-- call update? --})
    , autoMethod "org.counter.CounterService" "count" ({-- return state? --}) ]

这就是我的问题:我应该如何编码缺失{-- call update? --}{-- return state? --}功能?

我知道我可以使用 anMVar创建全局可变状态,然后从中读取函数,但我想在这里尽可能避免可变性。我我可以用 Reader/State monad 以某种方式做到这一点,也许通过将get/偷偷ask带入函数中,但我不知道如何处理与 DBus 相关的类型。

4

1 回答 1

1

最终,这个dbus包只允许你使用typeexport的方法,它有一个返回单子值的字段:MethodmethodHandler

DBusR Reply === ReaderT Client IO Reply

那里没有空间让你挤进你自己的StateT单子。您可以改为导出aProperty,但这对您没有帮助,因为该类型的字段还涉及IO获取和设置属性的操作。

所以,保持你的状态IO,很可能是一个MVar,几乎是不可避免的。

您可以尝试将纯粹的“核心”与 IO shell 分开。一种方法(根据@HTNW的评论)是将核心写入State

type Counter = Int

update :: State Counter ()
update = modify (+1)

count :: State Counter Int
count = get

并使用以下内容将其提升到IO

import Data.Tuple (swap)

runStateIO :: State s a -> MVar s -> IO a
runStateIO act s = modifyMVar s (return . swap . runState act)

main = do
    ...
    s <- newMVar 0
    let run act = runStateIO act s

    export dbus "/com/example/CounterService"
      defaultInterface
      { interfaceName = "com.example.CounterService"
      , interfaceMethods =
        [ autoMethod "update" (run update)
        , autoMethod "count" (run count) ]
      }

(我想我在这里使用的版本dbus比你更新,因为 API 有点不同——我正在测试dbus-1.2.16,仅供参考。)

一个潜在的缺点是,这将锁定MVar每个方法调用的状态,即使调用不需要状态或只需要只读访问。DBus 服务的流量通常非常低,其方法调用旨在快速完成,所以我认为这在实践中不是问题。

无论如何,这是一个完整的工作程序,我用它进行了测试:

dbus-send --print-reply --session --dest=com.example /com/example/CounterService com.example.CounterService.update
dbus-send --print-reply --session --dest=com.example /com/example/CounterService com.example.CounterService.count

该程序:

{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_GHC -Wall #-}

import System.IO
import System.Exit
import Data.Int
import DBus.Client
import Data.Tuple
import Control.Concurrent
import Control.Monad.State

type Counter = Int32

update :: State Counter ()
update = modify (+1)

count :: State Counter Int32
count = get

runStateIO :: State s a -> MVar s -> IO a
runStateIO act s = modifyMVar s (return . swap . runState act)

main :: IO ()
main = do
  dbus <- connectSession

  requestResult <- requestName dbus "com.example" []
  when (requestResult /= NamePrimaryOwner) $ do
    hPutStrLn stderr "Name \"com.example\" not available"
    exitFailure

  s <- newMVar 0
  let run act = runStateIO act s

  export dbus "/com/example/CounterService"
    defaultInterface
    { interfaceName = "com.example.CounterService"
    , interfaceMethods =
      [ autoMethod "update" (run update)
      , autoMethod "count" (run count) ]
    }

  forever $ threadDelay 60000000
于 2021-01-04T17:49:35.647 回答