当您提供相同代码的异步变体时,约定是什么?
What's the convention for when you offer an async variant of the same code?
设foo
为子或方法。我编写了一个阻塞变体和一个异步变体,所以从外部看,本质区别在于 return 值。我首先想到的是在签名中指定它,但不幸的是调度程序只查看传入端而不是两者:
> multi sub foo (--> Promise) {}; multi sub foo (--> Cool) {};
> my Promise $p = foo
Ambiguous call to 'foo(...)'; these signatures all match:
:( --> Promise)
:( --> Cool)
in block <unit> at <unknown file> line 1
我应该在签名中添加一个Bool :$async
吗?我应该像 JS 一样添加名称后缀(即 foo
和 foo-async
)吗?两者对我来说都没有太大的意义。这个问题目前有哪些解决方案?
return 类型的多重分派无法工作,因为 return 值本身可以用作多重分派调用的参数(并且因为几乎 Perl 6 中的每个运算符都是多重分派打电话,这将是很常见的事情)。
至于手头的问题:考虑到核心代码、模块和我自己的一堆代码,给定的 class 或模块似乎通常会提供同步接口 或 一个异步接口,以最适合手头问题的方式为准。在两者都有意义的情况下,它们通常在类型或模块级别进行区分。例如:
- 核心:有
Proc
和Proc::Async
,还有IO::Socket::INET
和IO::Socket::Async
。虽然有时可以通过为每个同步例程提供 Promise
-returning 替代方案来获得合理的异步 API,但在其他情况下,整体工作流程会有点不同。例如,对于同步套接字 API 来说,坐在循环中请求数据是很合理的,而异步 API 在 Perl 6 中通过提供 Supply
通过网络到达的数据包。
- 库:
Cro::HTTP::Client
提供一致的异步接口来执行 HTTP 请求。没有同步API.
- 应用程序:考虑到我的很多应用程序代码,就 API 而言,事情似乎要么始终同步,要么始终异步。我发现的唯一例外是几乎完全同步的 classes,除了它们有一些
Supply
-returning 方法以提供事件通知。然而,这并不是真正被问及的情况,因为通知自然是异步的。
有趣的是,我们在这里结束了,这与通过命名约定提供异步变体的各种其他语言形成鲜明对比。我认为大部分原因是可以在 Perl 6 中的任何地方使用 await
。在具有 async
/await
对的语言中情况并非如此,为了使用 await
必须首先将调用例程重构为 async
,然后将其调用者重构为 async
,等等
因此,如果我们正在编写一段完全同步的代码,并希望使用 return 是 Promise
的模块中的某些内容,我们的全部成本是 "just write await
"。而已。在对 await
的调用中写入与 -sync
或 -async
后缀或 :sync
或 :async
命名参数的长度相同。
另一方面,人们可能会选择为某些东西提供同步 API,即使它在内部正在做 await
,因为感觉大多数消费者只想同步使用它。如果有人希望异步调用它,还有另一个 5 个字母的单词 start
,它将在线程池上触发它,而在代码内部执行的任何 await
都不会(假设 Perl 6.d) 阻塞了一个真正的线程,而是安排它在 await
ed 工作完成后继续。同样,这与编写 async
后缀、命名参数等的长度相同或更短
这意味着我们似乎要结束的模式(考虑到对年轻语言的通常警告,以及需要时间发展的惯例)是:
- 对于简单的情况:选择最常见的用例并提供,让调用者使用
start
(同步 -> 异步)或 await
/react
( async -> sync) 如果他们想要其他东西
- 对于更复杂的情况,使用该功能的同步和异步工作流可能看起来完全不同,但都很有价值:分别提供它们。当然,一个可能是另一个的外观(例如,核心中的
Proc
实际上只是 Proc::Async
之上的同步适配层)。
我最后要说的是,模块的个人消费者几乎肯定会同步或异步使用它,而不是两者的混合。如果希望同时提供两者,那么我可能会改用 export tags,所以我可以这样做:
use Some::Thing :async;
say await something();
或者:
use Some::Thing :sync;
say something();
并且不必在每次调用时声明我想要的。
设foo
为子或方法。我编写了一个阻塞变体和一个异步变体,所以从外部看,本质区别在于 return 值。我首先想到的是在签名中指定它,但不幸的是调度程序只查看传入端而不是两者:
> multi sub foo (--> Promise) {}; multi sub foo (--> Cool) {};
> my Promise $p = foo
Ambiguous call to 'foo(...)'; these signatures all match:
:( --> Promise)
:( --> Cool)
in block <unit> at <unknown file> line 1
我应该在签名中添加一个Bool :$async
吗?我应该像 JS 一样添加名称后缀(即 foo
和 foo-async
)吗?两者对我来说都没有太大的意义。这个问题目前有哪些解决方案?
return 类型的多重分派无法工作,因为 return 值本身可以用作多重分派调用的参数(并且因为几乎 Perl 6 中的每个运算符都是多重分派打电话,这将是很常见的事情)。
至于手头的问题:考虑到核心代码、模块和我自己的一堆代码,给定的 class 或模块似乎通常会提供同步接口 或 一个异步接口,以最适合手头问题的方式为准。在两者都有意义的情况下,它们通常在类型或模块级别进行区分。例如:
- 核心:有
Proc
和Proc::Async
,还有IO::Socket::INET
和IO::Socket::Async
。虽然有时可以通过为每个同步例程提供Promise
-returning 替代方案来获得合理的异步 API,但在其他情况下,整体工作流程会有点不同。例如,对于同步套接字 API 来说,坐在循环中请求数据是很合理的,而异步 API 在 Perl 6 中通过提供Supply
通过网络到达的数据包。 - 库:
Cro::HTTP::Client
提供一致的异步接口来执行 HTTP 请求。没有同步API. - 应用程序:考虑到我的很多应用程序代码,就 API 而言,事情似乎要么始终同步,要么始终异步。我发现的唯一例外是几乎完全同步的 classes,除了它们有一些
Supply
-returning 方法以提供事件通知。然而,这并不是真正被问及的情况,因为通知自然是异步的。
有趣的是,我们在这里结束了,这与通过命名约定提供异步变体的各种其他语言形成鲜明对比。我认为大部分原因是可以在 Perl 6 中的任何地方使用 await
。在具有 async
/await
对的语言中情况并非如此,为了使用 await
必须首先将调用例程重构为 async
,然后将其调用者重构为 async
,等等
因此,如果我们正在编写一段完全同步的代码,并希望使用 return 是 Promise
的模块中的某些内容,我们的全部成本是 "just write await
"。而已。在对 await
的调用中写入与 -sync
或 -async
后缀或 :sync
或 :async
命名参数的长度相同。
另一方面,人们可能会选择为某些东西提供同步 API,即使它在内部正在做 await
,因为感觉大多数消费者只想同步使用它。如果有人希望异步调用它,还有另一个 5 个字母的单词 start
,它将在线程池上触发它,而在代码内部执行的任何 await
都不会(假设 Perl 6.d) 阻塞了一个真正的线程,而是安排它在 await
ed 工作完成后继续。同样,这与编写 async
后缀、命名参数等的长度相同或更短
这意味着我们似乎要结束的模式(考虑到对年轻语言的通常警告,以及需要时间发展的惯例)是:
- 对于简单的情况:选择最常见的用例并提供,让调用者使用
start
(同步 -> 异步)或await
/react
( async -> sync) 如果他们想要其他东西 - 对于更复杂的情况,使用该功能的同步和异步工作流可能看起来完全不同,但都很有价值:分别提供它们。当然,一个可能是另一个的外观(例如,核心中的
Proc
实际上只是Proc::Async
之上的同步适配层)。
我最后要说的是,模块的个人消费者几乎肯定会同步或异步使用它,而不是两者的混合。如果希望同时提供两者,那么我可能会改用 export tags,所以我可以这样做:
use Some::Thing :async;
say await something();
或者:
use Some::Thing :sync;
say something();
并且不必在每次调用时声明我想要的。