In a great series of posts Eric Lippert概述了.NET类型的所谓“Monad模式”,它有点像monad,并且为其中一些实现返回和绑定。
作为monadic类型的例子,他给出了:
Nullable<T>
Func<T>
Lazy<T>
Task<T>
IEnumerable<T>
我有两个问题:
我认为Nullable<T>
有点像Haskell中的Maybe
,并且绑定多个Maybe
动作代表了一组可能在任何时候都失败的操作。我知道列表monad(IEnumerable<T>
)代表非决定论。我甚至有点理解Func
作为monad(Reader
monad)的作用。
Lazy<T>
和Task<T>
的monadic sematnics是什么?绑定它们意味着什么?
有没有人在.NET中有更多类似于monad的类型的例子?
答案 0 :(得分:4)
嗯,默认情况下Haskell有懒惰,所以在Haskell中不会很有启发性,但我仍然可以展示如何将Task
实现为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
,只需在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
符号组合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
$
答案 1 :(得分:1)
monadic绑定函数的类型为:
Moand m => m a -> (a -> m b) -> m b
因此对于C#中的Task<T>
,您需要一个函数,它将Task<A>
提取值并将其传递给绑定函数。如果任务错误或被取消,复合任务应传播错误或取消。
使用async非常简单:
public static async Task<B> SelectMany<A, B>(this Task<A> task, Func<A, Task<B>> bindFunc)
{
var res = await task;
return await bindFunc(res);
}
对于Lazy<T>
,您应该从一个函数创建一个惰性值,该函数接受另一个惰性计算的结果:
public static Lazy<B> SelectMany<A, B>(this Lazy<A> lazy, Func<A, Lazy<B>> bindFunc)
{
return new Lazy<B>(() => bindFunc(lazy.Value).Value);
}
我认为
return bindFunc(lazy.Value);
是无效的,因为它急切地评估lazy
的值,所以你需要构造一个新的lazy,它从已创建的lazy中解除值。