【问题标题】:How do I bind together multiple monads?如何将多个 monad 绑定在一起?
【发布时间】:2021-12-31 13:38:07
【问题描述】:

我在LanguageExt 中使用TryAsync monad,但在尝试将多个绑定在一起时遇到了困难。我仍在学习函数式编程,因此可能完全错误地这样做。请随时在此处对我的方法的任何部分发表评论。

假设我有以下调用 Google Drive API 的方法...

TryAsync<File> GetFolder(string folderId)
TryAsync<string> CreateFolder(string folderName, string parentFolderId)
TryAsync<string> UploadFile(Stream file, string fileName, string mimeType, string folderId)

...其中File 是 Google 驱动器中文件/文件夹的 Google 类型。

我可以单独调用每一个,没问题,并且可以使用Match 来处理结果。

但是,有时我想调用多个,比如获取特定文件夹的File 对象,然后创建一个新的子文件夹并将文件上传到它。我知道我可以这样做(空气代码,所以请忽略任何拼写错误)...

(await GetFolder("123"))
  .Match(async folder => {
    (await CreateFolder("New folder", folder.Id))
      .Match(async newFolder => {
        (await UploadFile(stream, "New file name.txt", "text/text", newFolder.Id))
          .Match(fileId => /* do whatever with the uploaded file's Id */, ex => /* report exception */);
      }, ex => /* report exception */);
  }, ex => /* report exception */);

如您所见,这非常痛苦。我确信你应该能够将 monad 链接在一起,我认为使用 Bind,所以你最终会得到更像这样的东西(再次,空气代码)......

(await GetFolder("123"))
  .Bind(folder => CreateFolder("New folder", folder.Id))
  .Bind(newFolder => UploadFile(stream, "New file name.txt", "text/text", newFolder.Id))
  .Match(fileId => /* do whatever with the uploaded file's Id */, ex => /* report exception */);

但是,我无法像这样编译任何代码。

一个问题是我不确定我的方法是否有正确的签名。他们应该返回Task&lt;T&gt;,并让调用代码使用TryAsync&lt;T&gt;,还是让方法本身返回TryAsync&lt;T&gt;是正确的?

谁能告诉我应该怎么做?谢谢

【问题讨论】:

    标签: c# functional-programming monads language-ext


    【解决方案1】:

    你的最后一次尝试真的接近了,如果你检查了编译器错误,你可能会发现:

    'string' does not contain a definition for 'Id' and no accessible extension method 'Id'
    accepting a first argument of type 'string' could be found (are you missing a using directive
    or an assembly reference?)
    

    问题是newFolder.Id。原因是newFolder(如错误所示)是string,而不是File。这是因为上一个操作 CreateFolder 的结果返回 TryAsync&lt;string&gt; - 而不是 TryAsync&lt;File&gt;

    只需删除.Id 属性:

    var actual = await GetFolder("123")
        .Bind(folder => CreateFolder("New folder", folder.Id))
        .Bind(newFolder => UploadFile(stream, "New file name.txt", "text/text", newFolder))
        .Match(fileId => handleFileId(fileId), ex => handleException(ex));
    

    这会编译并完成工作。

    如果您愿意,也可以用查询语法编写表达式:

    var actual = await (
        from folder in GetFolder("123")
        from newFolder in CreateFolder("New folder", folder.Id)
        from fileId in UploadFile(stream, "New file name.txt", "text/text", newFolder)
        select fileId)
        .Match(fileId => handleFileId(fileId), ex => handleException(ex));
    

    结果是一样的 - C# 编译器将使用 Bind 将查询语法转换为之前的方法绑定样式。因此,区别只是可读性之一。

    有时查询语法更易读,有时方法调用语法更易读。在这种情况下,我认为前者的可读性最高。

    【讨论】:

    • 非常感谢您的明确解释。正如你所知道的,我仍在努力让我的头脑围绕 FP!
    猜你喜欢
    • 2020-03-12
    • 2021-03-28
    • 1970-01-01
    • 2019-06-07
    • 2011-08-09
    • 2016-12-29
    • 1970-01-01
    • 2012-06-12
    • 1970-01-01
    相关资源
    最近更新 更多