是否有一个宏用于在julia中从类似生成器的函数创建快速迭代器?

时间:2017-04-23 19:42:53

标签: iterator julia iterable

从python3到Julia,我希望能够将快速迭代器编写为带有yield / yield语法的函数或类似的函数。

Julia的宏似乎暗示可以构建一个将这种“生成器”函数转换为julia迭代器的宏。 [甚至看起来你很容易内联以函数样式编写的迭代器,这是Iterators.jl包也试图为其特定的迭代器提供的一个特性https://github.com/JuliaCollections/Iterators.jl#the-itr-macro-for-automatic-inlining-in-for-loops]

举一个例子来说明我的想法:

AppCompatTextView

其中@asiterator function myiterator(as::Array) b = 1 for (a1, a2) in zip(as, as[2:end]) try @produce a1[1] + a2[2] + b catch exc end end end for i in myiterator([(1,2), (3,1), 3, 4, (1,1)]) @show i end 理想情况下应该以尽可能低的开销创建一个快速迭代器。当然,这只是一个具体的例子。理想情况下,我想拥有适用于所有或几乎所有发电机功能的东西。

目前推荐的将生成器函数转换为迭代器的方法是通过Julia的任务,至少据我所知。然而,它们似乎也比纯粹的迭代器慢。例如,如果您可以使用myiteratorimap等简单的迭代器(由Iterators.jl包提供)来表达您的函数,那么这似乎是非常可取的。

在julia理论上是否有可能将生成器类型的宏转换为灵活的快速迭代器?

Extra-Point-Question:如果可能的话,是否会有一个通用宏来内联这样的迭代器?

3 个答案:

答案 0 :(得分:3)

此表单的一些迭代器可以这样写:

myiterator(as) = (a1[1] + a2[2] + 1 for (a1, a2) in zip(as, as[2:end]))

此代码可以(可能)内联。

为了完全概括这一点,理论上可以编写一个宏,将其参数转换为延续传递样式(CPS),从而可以暂停和重新启动执行,给出类似迭代器的东西。定界延续特别适用于此(https://en.wikipedia.org/wiki/Delimited_continuation)。结果是一个大的匿名函数嵌套,它可能比任务切换更快,但不一定,因为在一天结束时它需要堆分配相似数量的状态。

我碰巧在这里有一个这样的转变的例子(虽然在femtolisp,而不是朱莉娅):https://github.com/JeffBezanson/femtolisp/blob/master/examples/cps.lsp 这以一个define-generator宏结束,完成您描述的内容。但我不确定为Julia做这件事是否值得。

答案 1 :(得分:2)

Python风格的生成器 - 在Julia中最接近于从任务中屈服 - 涉及相当大的固有开销。您必须切换任务,这是非常重要的,并且不能由编译器直接消除。这就是为什么Julia的迭代器基于转换一个通常不可变的简单状态值的函数,以及另一个。长话短说:不,我不相信这种转变可以自动完成。

答案 2 :(得分:1)

在思考了很多如何将python生成器转换为Julia而不失去太多性能之后,我实现并测试了一个更高级函数的库,它们以连续样式实现类似Python的/类似任务的生成器。 https://github.com/schlichtanders/Continuables.jl

基本上,我们的想法是将Python的yield / Julia的produce视为我们从外部获取的函数作为额外参数。我把它称为cont继续。查看范围重新实现的实例

crange(n::Integer) = cont -> begin
  for i in 1:n
    cont(i)
  end
end

您可以通过以下代码简单地总结所有整数

function sum_continuable(continuable)
  a = Ref(0)
  continuable() do i
    a.x += i
  end
  a.x
end

# which simplifies with the macro Continuables.@Ref to
@Ref function sum_continuable(continuable)
  a = Ref(0)
  continuable() do i
    a += i
  end
  a
end

sum_continuable(crange(4))  # 10

正如您所希望的那样,您可以使用可持续性,就像您在python中使用生成器或在julia中使用任务一样。使用do符号代替for循环是您必须习惯的一件事。

这个想法让你真的很开心。唯一不能使用这种想法实现的标准方法是zip。所有其他标准的高级工具都可以像您希望的那样工作。

在某些情况下,性能比任务快得多,甚至比迭代器更快(特别是Continuables.cmap的天真实现比Iterators.imap快几个数量级)。查看github存储库https://github.com/schlichtanders/Continuables.jl的Readme.md以获取更多详细信息。

编辑:要更直接地回答我自己的问题,不需要宏@asiterator,只需直接使用延续样式。

mycontinuable(as::Array) = cont -> begin
  b = 1
  for (a1, a2) in zip(as, as[2:end])
    try
      cont(a1[1] + a2[2] + b)
    catch exc
    end
  end
end

mycontinuable([(1,2), (3,1), 3, 4, (1,1)]) do i
   @show i
end