Monadic .NET类型

时间:2013-04-28 19:13:24

标签: c# .net haskell monads

In a great series of posts Eric Lippert概述了.NET类型的所谓“Monad模式”,它有点像monad,并且为其中一些实现返回和绑定。

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

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

我有两个问题:

  1. 我认为Nullable<T>有点像Haskell中的Maybe,并且绑定多个Maybe动作代表了一组可能在任何时候都失败的操作。我知道列表monad(IEnumerable<T>)代表非决定论。我甚至有点理解Func作为monad(Reader monad)的作用。 Lazy<T>Task<T>的monadic sematnics是什么?绑定它们意味着什么?

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

2 个答案:

答案 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中解除值。