11

在一系列精彩的文章中, Eric Lippert 概述了 .NET 类型的所谓“Monad 模式”,它有点像 monad,并为其中一些实现了 return 和 bind。

作为一元类型的例子,他给出了:

  • Nullable<T>
  • Func<T>
  • Lazy<T>
  • Task<T>
  • IEnumerable<T>

我有两个问题:

  1. 我明白这Nullable<T>有点像Maybe在 Haskell 中,绑定几个Maybe动作代表一组可能在任何时候失败的操作。我知道列表 monad ( IEnumerable<T>) 代表非确定性。我什至有点理解Func单子(Readermonad)的作用。Lazy<T>和的一元语义是Task<T>什么?绑定它们是什么意思?

  2. 有没有人在 .NET 中有更多类似 monad 的类型示例?

4

2 回答 2

4

好吧,Haskell 默认具有惰性,所以这在 Haskell 中不会很有指导意义,但我仍然可以展示如何将Tasks 实现为 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,您可以轻松地定义自己的Tasks,只需在运行时提供要分叉的操作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
$
于 2013-04-28T20:34:10.897 回答
2

一元绑定函数具有以下类型:

Moand m => m a -> (a -> m b) -> m b

因此,Task<T>在 C# 中,您需要一个函数来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因此您需要构造一个新的懒惰,它从创建的懒惰中解开值。

于 2013-04-28T23:55:35.127 回答