比使用“任务/生产/消费”更好的方法将惰性集合表示为协程

2024-03-12

使用起来非常方便Tasks表达一个惰性集合/生成器。

Eg:

function fib()
    Task() do
        prev_prev = 0
        prev = 1
        produce(prev)
        while true
            cur = prev_prev + prev
            produce(cur)
            prev_prev = prev
            prev = cur
        end
    end
end

collect(take(fib(), 10))

Output:

10-element Array{Int64,1}:
  1
  1
  2
  3
  5
  8
 13
 21
 34

然而,它们根本不遵循良好的迭代器约定。 他们的行为极其恶劣

他们不使用返回的状态state

start(fib()) == nothing #It has no state

因此,他们正在改变迭代器对象本身。 正确的迭代器使用其状态,而不是改变自身,因此多个调用者可以立即迭代它。 创建该状态start,并在期间推进它next.

有争议的是,该状态应该是immutable with next返回一个新的状态,这样就可以很简单tee编辑。 (另一方面,分配新内存——尽管在堆栈上)

此外,隐藏状态在next。 以下不起作用:

@show ff = fib()
@show state = start(ff)
@show next(ff, state)

Output:

ff = fib() = Task (runnable) @0x00007fa544c12230
state = start(ff) = nothing
next(ff,state) = (nothing,nothing)

相反,隐藏状态在done: 以下作品:

@show ff = fib()
@show state = start(ff)
@show done(ff,state)     
@show next(ff, state)

Output:

ff = fib() = Task (runnable) @0x00007fa544c12230
state = start(ff) = nothing
done(ff,state) = false
next(ff,state) = (1,nothing)

期间状态不断进步done并不是世界上最糟糕的事情。 毕竟,通常情况下,如果不尝试寻找下一个状态,就很难知道何时完成。有人会希望done总是会被调用之前next。 但这仍然不太好,因为发生了以下情况:

ff = fib()
state = start(ff)
done(ff,state)
done(ff,state)
done(ff,state)
done(ff,state)
done(ff,state)
done(ff,state)
@show next(ff, state)

Output:

next(ff,state) = (8,nothing)

现在这正是您所期望的。可以合理地假设done多次拨打是安全的。


基本上Tasks 的迭代器很差。在许多情况下,它们与需要迭代器的其他代码不兼容。 (在很多情况下都是如此,但很难区分哪个)。 这是因为Task在这些“生成器”函数中,s 并不是真正用作迭代器。它们用于低级控制流。 并进行了这样的优化。

那么更好的方法是什么? 为以下内容编写迭代器fib还不错:

immutable Fib end
immutable FibState
    prev::Int
    prevprev::Int
end

Base.start(::Fib) = FibState(0,1)
Base.done(::Fib, ::FibState) = false
function Base.next(::Fib, s::FibState)
    cur = s.prev + s.prevprev
    ns = FibState(cur, s.prev)
    cur, ns
end

Base.iteratoreltype(::Type{Fib}) = Base.HasEltype()
Base.eltype(::Type{Fib}) = Int
Base.iteratorsize(::Type{Fib}) = Base.IsInfinite()

但有点不太直观。 对于更复杂的功能,它就不那么好了。

所以我的问题是:有什么更好的方法可以像 Task 一样工作,作为从单个函数构建迭代器的方法,但表现良好?

如果有人已经编写了一个带有宏的包来解决这个问题,我不会感到惊讶。


当前任务的迭代器接口相当简单:

# in share/julia/base/task.jl
275 start(t::Task) = nothing
276 function done(t::Task, val)
277     t.result = consume(t)
278     istaskdone(t)
279 end
280 next(t::Task, val) = (t.result, nothing)

不知道为什么开发人员选择将消耗步骤放在done函数而不是next功能。这就是产生奇怪副作用的原因。对我来说,实现这样的接口听起来要简单得多:

import Base.start; function Base.start(t::Task) return t end
import Base.next;  function Base.next(t::Task, s::Task) return consume(s), s end
import Base.done;  function Base.done(t::Task, s::Task) istaskdone(s) end

因此,这就是我对你的问题的回答。

I think this simpler implementation is a lot more meaningful, fulfils your criteria above, and even has the desired outcome of outputting a meaningful state: the Task itself! (which you're allowed to "inspect" if you really want to, as long as that doesn't involve consumption :p ).


但是,有一些注意事项:

  • Caveat 1: 任务是REQUIRED具有返回值,表示迭代中的最终元素,否则可能会发生“意外”行为。

    我假设开发人员选择第一种方法来避免这种“意外”输出;然而I相信这实际上应该是预期的行为!预期用作迭代器的任务应该通过设计定义适当的迭代端点(通过明确的返回值)!

    示例1:错误的做法

    julia> t = Task() do; for i in 1:10; produce(i); end; end;
    julia> collect(t) |> show
    Any[1,2,3,4,5,6,7,8,9,10,nothing] # last item is a return value of nothing
                                      # correponding to the "return value" of the
                                      # for loop statement, which is 'nothing'.
                                      # Presumably not the intended output!
    

    示例2:另一种错误的做法

    julia> t = Task() do; produce(1); produce(2); produce(3); produce(4); end;
    julia> collect(t) |> show
    Any[1,2,3,4,()] # last item is the return value of the produce statement,
                    # which returns any items passed to it by the last
                    # 'consume' call; in this case an empty tuple.
                    # Presumably not the intended output!
    

    示例3:The (在我的愚见) 正确的做法!.

    julia> t = Task() do; produce(1); produce(2); produce(3); return 4; end;
    julia> collect(t) |> show
    [1,2,3,4] # An appropriate return value ending the Task function ensures an
              # appropriate final value for the iteration, as intended.
    
  • 注意事项 2:不应在迭代内部进一步修改/消耗任务(通常是迭代器的常见要求),除非理解这故意导致迭代中的“跳过”(这充其量是一种黑客行为,并且可能是不可取的) )。

    Example:

    julia> t = Task() do; produce(1); produce(2); produce(3); return 4; end;
    julia> for i in t; show(consume(t)); end
    24
    

    更微妙的例子:

    julia> t = Task() do; produce(1); produce(2); produce(3); return 4; end;
    julia> for i in t   # collecting i is a consumption event
            for j in t  # collecting j is *also* a consumption event
              show(j)
            end
           end # at the end of this loop, i = 1, and j = 4
    234
    
  • 注意事项 3:有了这个方案就是expected您可以“从上次中断的地方继续”的行为。例如

    julia> t = Task() do; produce(1); produce(2); produce(3); return 4; end;
    julia> take(t, 2) |> collect |> show
    [1,2]
    julia> take(t, 2) |> collect |> show
    [3,4]
    

    然而,如果人们更喜欢迭代器always从任务的预消费状态开始,可以修改启动函数来实现这一点:

    import Base.start; function Base.start(t::Task) return Task(t.code) end;
    import Base.next;  function Base.next(t::Task, s::Task) consume(s), s end;
    import Base.done;  function Base.done(t::Task, s::Task) istaskdone(s) end;
    
    julia> for i in t
             for j in t
               show(j)
             end
           end # at the end of this loop, i = 4, and j = 4 independently
    1234123412341234
    

    有趣的是,请注意此变体将如何影响“警告 2”中的“内部消耗”场景:

    julia> t = Task() do; produce(1); produce(2); produce(3); return 4; end;
    julia> for i in t; show(consume(t)); end
    1234
    julia> for i in t; show(consume(t)); end
    4444       
    

    看看你是否能明白为什么这是有道理的! :)


说了这么多,还有一个哲学观点,即任务与任务的行为方式是否重要。start, next, and done命令很重要,因为这些函数被认为是“非正式的界面 http://docs.julialang.org/en/release-0.5/manual/interfaces/“:即它们应该是“幕后”功能,而不是手动调用。

因此,只要他们完成自己的工作并返回预期的迭代值,您就不应该太关心他们在幕后是如何做的,即使技术上他们在这样做时并不完全遵循“规范”,因为你一开始就不应该手动调用它们。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

比使用“任务/生产/消费”更好的方法将惰性集合表示为协程 的相关文章

  • 在 VS Code 中找不到 Julia 包

    我对 vscode 很陌生 之前一直使用 jupyter 笔记本 问题是 当我尝试在 VS Code 中运行 julia 时 它找不到任何已随 jupyter 安装的软件包 我是否必须重新安装所有内容 或者有什么方法可以解决这个问题 Jul
  • R / Python / Julia 中 Matlab 的类型转换函数相当于什么

    相当于Matlab的什么typecastR 中的函数 在Python中 在朱莉娅 Matlab的typecast函数的描述如下 typecast http www mathworks com help matlab ref typecast
  • 有什么方法可以获取模块中定义的函数列表?

    是否有任何内省的魔法可以给我一个模块中定义的函数列表 module Foo function foo foo end function bar bar end end 一些神话般的功能 例如 functions in Foo 这将返回 fo
  • `yield from foo()` 和 `for x in foo(): Yield x` 之间的区别

    在Python中 大多数yield from的例子都是这样解释的 yield from foo 类似于 for x in foo yield x 另一方面 它似乎并不完全相同 并且有一些魔法 我对使用一个执行我不理解的魔法的函数感到有点不安
  • 朱莉娅中的矢量化“in”函数?

    我经常想要循环遍历数据帧的长数组或列 并且对于每个项目 查看它是否是另一个数组的成员 而不是做 giant list a c j good letters a b isin falses size giant list 1 for i 1
  • 在 julia 中打开 csv 文件时转义序列无效

    当打开 CSV 文件时julia它给 无效的转义序列 error 无效的转义序列 https i stack imgur com U9hn0 png julia gt using CSV julia gt wikiEVDraw CSV re
  • 生成 DTMF 音

    我想知道是否有人遇到过在 iPhone SDK 中生成铃声的方法 我正在尝试生成 DTMF 音调 但似乎找不到任何实质性内容 我还希望能够指定播放音调的时间 即模拟按住按钮而不是简单地按下按钮 我发现了一个名为 iPhreak 的开源应用程
  • 我可以使用具有固定输入的“map”吗?

    假设我这样做f x y 2x ythen map f 2 4 6 1 1 1 我会得到 3 element Array Int64 1 5 9 13 If I do map f 2 4 6 1 即我想假设第二个输入始终相同 我会得到 1 e
  • Julia 泛型函数类型参数

    我定义了一个函数如下 function approx pi n tot Float64 0 0 for i in 1 n x rand y rand if x 2 y 2 lt 1 tot 1 end end tot n 4 end pri
  • Julia DataFrames 中的高效自定义排序?

    有没有一种快速的方法来指定自定义订单sort sort 在 Julia 数据框架上 julia gt using DataFrames julia gt srand 1 julia gt df DataFrame x rand 10 y r
  • 从 Node JS 中的生成器获取返回值

    我似乎无法弄清楚如何获取生成器的返回值 有人知道我做错了什么吗 function getGeneratorReturn var generator runGenerator var generatorReturn null var done
  • Julia:数组是否包含特定的子数组

    在 julia 中 我们可以检查数组是否包含值 如下所示 gt 6 in 4 6 5 true 然而 当尝试按特定顺序检查子数组时 这会返回 false gt 4 6 in 4 6 5 false 验证数组中是否存在特定子数组的正确语法是什
  • Julia 似乎没有使用字符串来执行插值

    官方文档指出 连接和字符串插值调用string 将对象转换为字符串形式 然而 以下最小工作示例似乎证明了其他情况 type MyType x Int end import Base string Base string m MyType w
  • 使用 Gevent 和 WSGI 阻止调用

    我刚刚开始使用协程并阅读了 gevent 和 greenlet 为了进行测试 我通过 gevents pywsgi 模块提供了此代码 from gevent pywsgi import WSGIServer import gevent de
  • 有没有办法在 writetable() 中使用字符串作为分隔符 - Julia

    当使用 writetable 将数据帧写入文件时 我希望能够将分隔符设为空格然后逗号 即 作为分隔符 我知道 writetable 只能选择将单个字符作为分隔符参数 是否有可能的解决方法能够将字符串作为分隔符 或者 是否可以简单地在数据框中
  • 如何在 Swift 中返回序列?

    我正在尝试编写一个扩展Matrix书中的例子 稍微调整为通用的 https stackoverflow com q 24136604 458193 我正在尝试编写一个名为的方法getRow返回给定行的值序列 在 C 中 我会这样写 IEnu
  • 在 Julia 中,有没有办法让“现在”(至少)达到毫秒精度?

    通常 要了解代码中发生的情况 您需要高精度时间来分析您的应用程序或出于其他原因 显然 现在 https stackoverflow com questions 32407509 how to get the milliseconds fro
  • Python 中基于收益的协程与带有 @asyncio.coroutine 和 @types.coroutine 装饰器的协程有何不同?

    我一直在尝试理解异步编程 尤其是 Python 中的异步编程 我知道 asyncio 是基于事件循环构建的 该事件循环安排协程的执行 但我已经阅读了几种定义协程的不同方法 并且我很困惑它们如何相互关联 I read 本文 http masn
  • 在 Julia 中提取参数类型

    假设我在 Julia 中编写了一个函数 它接受Dict K V 作为参数 然后创建类型的数组Array K 1 and Array V 1 我怎样才能提取类型K and V来自 Dict 对象 以便我可以使用它们来创建数组 斯文和约翰的答案
  • Julia 中 @with_kw 和 Base.@kwdef 之间的区别?

    受到这个问题评论的启发 with kw 在 Julia 中做什么 https stackoverflow com questions 69586136 what does with kw do in julia 有什么区别 with kw

随机推荐

  • 数据仓库模型:集线器有什么用?

    我刚刚读到数据仓库建模 https en wikipedia org wiki Data vault modeling据我了解 集线器仅包含密钥 和记录源 所以我想知道为什么我应该创建这些中心表 只是为了存储记录源 仅拥有卫星和链接还不够吗
  • NSValueTranformer 用于加密数据

    我想在 NSValueTransformer 的帮助下加密一些核心数据列 我的想法是 我使用数据类型可转换并为我的数据类型定义一些转换器 在转换方法中 我想加密 解密该值 我定义了一个实际上什么都不做的变压器 加密的东西目前还没有实现 im
  • ReportViewer 到 div/image/object 内的 PDF

    这几天一直在寻找 但没能找到我要找的东西 希望我没有错过 我有一个 ASP NET 4 0 站点 我正在将其放在一起以检索工资信息 目前我正在使用reportviewer 但由于跨浏览器支持 它不能100 工作 我已经将其设置为自动将 RV
  • Symfony2 形成没有实体的验证器组

    我正在使用 Symfony2 表单组件来构建和验证表单 现在我需要根据单个字段值设置验证器组 不幸的是 似乎每个示例都基于实体 由于多种原因 我没有使用实体 例子 如果任务为空 则应删除所有约束验证器 但如果不是 则应使用默认的验证器集 或
  • 使用 spring RestTemplate 来自 Android 的 PUT 请求

    我必须通过 Restful 服务 Net 将客户对象从 Android 客户端放入数据库 服务合同 WebInvoke Method PUT UriTemplate customers customerId RequestFormat We
  • 我可以将同一个 Firebase 用于两个不同的应用吗?

    我想为两个应用程序使用相同的 Firebase 在这两个应用程序中 用户应该能够通过 Google Facebook 或电子邮件登录 但在我的 Firebase 仪表板的 登录和身份验证 部分中 我只能添加一个 Facebook 应用程序
  • 用不同的方式从列表中删除重复项

    我有一个名为employee的类 它是pojo 并且我创建了该pojo类型的employee类型的列表 现在我想从列表中删除重复项 请告知实现这一目标的各种方法是什么 class Emp implements Comparable Stri
  • 无法在经典 asp 中写入文件

    好吧 自从我使用经典的 asp 以来已经有一段时间了 所以我有点生疏了 这是我的问题 我正在尝试使用 FSO 将文件写入文件系统 下面的代码非常简单 但是 该文件没有出现 也没有出现错误 我知道它正在运行代码 因为我可以在此片段之前和之后添
  • 无法打开源文件:“WIN32”:没有这样的文件或目录

    在 VS2008 中构建 C 项目时出现此错误 很困惑这是什么意思 为什么它认为WIN32是一个文件 Go to Project gt 特性 gt C C gt 命令行 你很可能有一个 I 在你之前 D WIN32 由于某种原因 空的 in
  • 在 Typescript 上获取插件函数

    我正在尝试在打字稿文件上使用一些科尔多瓦插件功能 但我无法构建该文件 想象一下 我想要访问设备平台和型号 有人可以帮我弄这个吗 我应该为插件的 js 文件上的每个函数创建接口吗 提前致谢 对此已有定义 https github com bo
  • 在 Dart 中克​​隆列表、映射或集

    来自 Java 背景 克隆 Dart 的推荐方法是什么List Map and Set Use of clone in Java is tricky and questionable1 2 Effectively clone is a co
  • 检测多列中的关键词并在新列中标记它们

    我有这种类型的数据 set seed 123 df lt data frame v1 sample LETTERS 1 10 5 v2 sample LETTERS 1 10 5 v3 sample LETTERS 1 10 5 v4 sa
  • Vite 构建命令挂在“模块已转换”处。在Vue 3.3.4、Vite 4.3.8环境下

    您好 我目前正在使用 Vue 3 3 4 Vuetify 3 2 5 和 Vite 4 3 8 开发一个项目 当我运行 vite build 命令时 该过程似乎挂起并且未完成 最后的输出我看到的是 679 modules transform
  • 应该如何将 F# SqlDataConnection TypeProvider 与 App.Config 文件一起使用?

    我正在使用类型表达式 type dbSchema SqlDataConnection
  • 我可以定位 .net 的(表单)消息框或通用对话框吗?

    我试图获得父窗体的中心 而不是屏幕行为的中心 传入父窗体似乎只能控制窗口的所有权 这些类是密封的 因此我不知道如何执行任何 WinProc 技巧 重写类并不是一个有吸引力的选择 还有其他想法吗 正如 HTH 上面所解释的 有一些丑陋的方法可
  • spring data elasticsearch如何使用offset和limit进行查询

    spring data elasticsearch如何使用offset和limit进行查询 我想使用偏移量和限制参数来查询页面 但我找不到方法支持 例如 queryBuild withPageable PageRequest of page
  • 为4000万条记录的表添加多列主键

    我正在维护一个数据库 该数据库存储不同网络之间的数据传输信息 本质上 每次数据传输都会被记录下来 并在每个月末运行一个 perl 脚本 将日志文件加载到数据库的表中 我没有设计 Perl 脚本或数据库模式 这是在我开始从事这个项目之前完成的
  • 苹果风格的滚动条WPF

    我正在尝试减小滚动条控件的大小并使其更像 Apple 因为它非常适合我的界面 我想要实现的目标是这样的 到目前为止我所拥有的就是这个
  • 如何用 R 重现这个移动分布图?

    G Elliot Moris showed political polarization through time using a moving distribution plot https twitter com gelliottmor
  • 比使用“任务/生产/消费”更好的方法将惰性集合表示为协程

    使用起来非常方便Tasks表达一个惰性集合 生成器 Eg function fib Task do prev prev 0 prev 1 produce prev while true cur prev prev prev produce