一元.net类型

本文关键字:类型 net 一元 | 更新日期: 2023-09-27 18:11:10

Eric Lippert在一系列的文章中概述了所谓的。net类型的"Monad模式",它有点像Monad,并为其中一些类型实现了返回和绑定。

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

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

我有两个问题:

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

  2. 谁有更多的类型的例子,在。net有点像单子?

一元.net类型

嗯,Haskell默认有惰性,所以这在Haskell中不是很有指导意义,但我仍然可以展示如何将Task s实现为单子。下面是在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函数所做的就是派生一个线程来计算一个动作,返回一个future。我只是在它周围定义了一个小包装器,这样我就可以定义一个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

然后你可以使用do符号组合Task s,创建一个新的延迟任务准备运行:

test3 :: Task Int
test3 = do
    n1 <- test1
    n2 <- test2
    return (n1 + n2)

运行fork test3将生成Task并返回一个future,你可以在任何时候调用它来要求结果,如果需要的话阻塞直到完成。

为了证明它是有效的,我将做两个简单的测试。首先,我将分叉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
$

一元绑定函数的类型为:

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中解包值