好吧,Haskell 默认具有惰性,所以这在 Haskell 中不会很有指导意义,但我仍然可以展示如何将Task
s 实现为 monad。以下是在 Haskell 中实现它们的方法:
import Control.Concurrent.Async (async, wait)
newtype Task a = Task { fork :: IO (IO a) }
newTask :: IO a -> Task a
newTask io = Task $ do
w <- async io
return (wait w)
instance Monad Task where
return a = Task $ return (return a)
m >>= f = newTask $ do
aFut <- fork m
a <- aFut
bFut <- fork (f a)
bFut
为方便起见,它建立在async
库之上,但并非必须如此。该async
函数所做的只是派生一个线程来评估一个动作,返回一个未来。我只是围绕它定义了一个小包装器,以便我可以定义一个Monad
实例。
使用此 API,您可以轻松地定义自己的Task
s,只需在运行时提供要分叉的操作Task
:
import Control.Concurrent (threadDelay)
test1 :: Task Int
test1 = newTask $ do
threadDelay 1000000 -- Wait 1 second
putStrLn "Hello,"
return 1
test2 :: Task Int
test2 = newTask $ do
threadDelay 1000000
putStrLn " world!"
return 2
然后,您可以Task
使用符号组合 s do
,这会创建一个准备运行的新延迟任务:
test3 :: Task Int
test3 = do
n1 <- test1
n2 <- test2
return (n1 + n2)
运行fork test3
将产生Task
并返回一个未来,您可以随时调用它来要求结果,如有必要,阻塞直到完成。
为了证明它有效,我将做两个简单的测试。首先,我将在test3
不要求其未来的情况下进行分叉,以确保它正确生成复合线程:
main = do
fork test3
getLine -- wait without demanding the future
这可以正常工作:
$ ./task
Hello,
world!
<Enter>
$
现在我们可以测试当我们要求结果时会发生什么:
main = do
fut <- fork test3
n <- fut -- block until 'test3' is done
print n
...这也有效:
$ ./task
Hello,
world!
3
$