【问题标题】:Convert IO callback to infinite list将 IO 回调转换为无限列表
【发布时间】:2019-03-25 19:23:37
【问题描述】:

我正在使用一个库,我可以提供一个函数a -> IO (),它会偶尔调用它。

因为我的函数的输出不仅取决于它作为输入接收的a,还取决于以前的a,所以我编写一个函数[a] -> IO ()会容易得多,其中@ 987654325@ 是无限的。

我可以写一个函数吗:

magical :: ([a] -> IO ()) -> (a -> IO ())

这会收集它从回调中接收到的a,并将它们作为惰性无限列表传递给我的函数?

【问题讨论】:

  • “因为我的函数的输出不仅取决于它作为输入接收的 a” - 不幸的是你不能在 Haskell 中这样做,因为它是引用透明的。也就是说,一个函数的输出值只取决于传递给它的参数。
  • 这对于纯函数来说是正确的,但是因为这是在 IO monad 中,所以我可以在调用之间存储状态。这是我目前正在手动执行的操作,并试图避免

标签: haskell


【解决方案1】:

IORef 解决方案确实是最简单的解决方案。如果您想探索一个纯粹的(但更复杂的)变体,请查看conduit。相同概念还有其他实现,请参阅 Iteratee I/O,但我发现自己的 conduit 非常易于使用。

管道(AKA 管道)是可以接受输入和/或产生输出的程序的抽象。因此,如果需要,它可以保持内部状态。在您的情况下,magical 将是一个 sink,即接受某种类型的输入但不产生输出的管道。通过将它连接到一个source,一个产生输出的程序,你完成了管道,然后每当 sink 请求输入时,source 就会运行直到它产生输出。

在你的情况下,你大概会有类似

magical :: Sink a IO () -- consumes a stream of `a`s, no result
magical = go (some initial state)
  where
    go state = do
      m'input <- await
      case m'input of
        Nothing -> return ()  -- finish
        Just input -> do
          -- do something with the input
          go (some updated state)

【讨论】:

    【解决方案2】:

    这并不完全符合您的要求,但我认为它可能足以满足您的目的。

    magical :: ([a] -> IO ()) -> IO (a -> IO ())
    magical f = do
       list <- newIORef []
       let g x = do
              modifyIORef list (x:)
              xs <- readIORef list
              f xs   -- or (reverse xs), if you need FIFO ordering
       return g
    

    所以如果你有一个函数fooHistory :: [a] -&gt; IO (),你可以使用

    main = do
       ...
       foo <- magical fooHistory
       setHandler foo -- here we have foo :: a -> IO ()
       ...
    

    正如@danidaz 上面所写,您可能不需要magical,但可以直接在您的fooHistory 中使用相同的技巧,修改列表引用(IORef [a])。

    main = do
       ...
       list <- newIORef []
       let fooHistory x = do
              modifyIORef list (x:)
              xs <- readIORef list
              use xs   -- or (reverse xs), if you need FIFO ordering
       setHandler fooHistory -- here we have fooHistory :: a -> IO ()
       ...
    

    【讨论】:

      【解决方案3】:

      Control.Concurrent.Chan 几乎完全符合我的要求!

      import Control.Monad           (forever)
      import Control.Concurrent      (forkIO)
      import Control.Concurrent.Chan
      
      
      setHandler :: (Char -> IO ()) -> IO ()
      setHandler f = void . forkIO . forever $ getChar >>= f
      
      process :: String -> IO ()
      process ('h':'i':xs) = putStrLn "hi" >> process xs
      process ('a':xs)     = putStrLn "a" >> process xs
      process (x:xs)       = process xs
      process _            = error "Guaranteed to be infinite"
      
      
      main :: IO ()
      main = do
        c <- newChan
        setHandler $ writeChan c
        list <- getChanContents c
        process list
      

      【讨论】:

        【解决方案4】:

        对我来说,这似乎是库设计中的一个缺陷。您可能会考虑使用上游补丁,以便您可以提供更通用的内容作为输入。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2016-01-15
          • 2015-09-25
          • 2020-06-11
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多