如何将多个 monad 绑定在一起?
How do I bind together multiple monads?
我在 LanguageExt 中使用 TryAsync
monad,但在尝试将多个 monad 绑定在一起时遇到困难。我仍在学习函数式编程,所以这样做可能完全错误。请随时对我的方法的任何部分发表评论。
假设我有以下调用 Google 驱动器 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 驱动器中 files/folders 的 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 */);
如您所见,这非常痛苦。我相信你应该能够将 monads 链接在一起,我认为使用 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 */);
但是,我无法像这样编译任何代码。
一个问题是我不确定我的方法是否具有正确的签名。他们应该 return Task<T>
,并让调用代码使用 TryAsync<T>
,还是我自己使用方法 return TryAsync<T>
?
谁能告诉我应该怎么做?谢谢
您的最后一次尝试 确实 接近了,如果您检查了编译器错误,您可能会发现它:
'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
returns 一个 TryAsync<string>
- 而不是 TryAsync<File>
.
只需删除 .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
的方法绑定样式。因此,区别只是可读性之一。
有时查询语法更具可读性,有时方法调用语法更具可读性。在这种情况下,我认为前者是最可读的。
我在 LanguageExt 中使用 TryAsync
monad,但在尝试将多个 monad 绑定在一起时遇到困难。我仍在学习函数式编程,所以这样做可能完全错误。请随时对我的方法的任何部分发表评论。
假设我有以下调用 Google 驱动器 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 驱动器中 files/folders 的 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 */);
如您所见,这非常痛苦。我相信你应该能够将 monads 链接在一起,我认为使用 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 */);
但是,我无法像这样编译任何代码。
一个问题是我不确定我的方法是否具有正确的签名。他们应该 return Task<T>
,并让调用代码使用 TryAsync<T>
,还是我自己使用方法 return TryAsync<T>
?
谁能告诉我应该怎么做?谢谢
您的最后一次尝试 确实 接近了,如果您检查了编译器错误,您可能会发现它:
'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
returns 一个 TryAsync<string>
- 而不是 TryAsync<File>
.
只需删除 .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
的方法绑定样式。因此,区别只是可读性之一。
有时查询语法更具可读性,有时方法调用语法更具可读性。在这种情况下,我认为前者是最可读的。