如何从 Perl 中的 coderef 获取语法树?

发布于 2024-09-25 22:50:21 字数 1143 浏览 3 评论 0原文

我想在 Perl 中检查和操作任意 Perl 过程的代码(通过 coderefs 获得)。有相应的工具/模块/库吗?类似于 B::Concise,除了 B::Concise 在输出上打印代码,但我想以编程方式检查它。

我想像这样使用它。给定一个 coderef F,它被称为例如。有 10 个参数:

$ret = &$F(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10);

我想创建一个函数 F1,st。

&$F(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10) == 
  &$F1(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10)*
  &$C(x2, x3, x4, x5, x6, x7, x8, x9, x10)

也就是说,将其“分解”为两部分,其中第二部分不依赖于 x1,而第一部分尽可能简单(我假设 F 被构造为巨大的产品)。

我想要的应用程序是优化 Metropolis 采样算法 - 假设我正在对分布 p(x1 | x2 = X1, x3 = X3, ...) = f(x1, x2, x3, ...) 进行采样。该算法本身是不变的。乘法常数因子,其他变量不会通过算法改变,因此不依赖于x1的部分(即上面的$c)根本不需要计算) 。

联合概率可能有例如。以下形式:

  p(x1, x2, x3, x4, x5) = g1(x1, x2)*g2(x2, x3)*g3(x3, x4)*g4(x4, x5)*g5(x4, x1)*g6(x5, x1)

我还考虑将 p 构造为一个由因素组成的对象,并带有特定因素所依赖的变量的注释。即使这样也会受益于代码自省(自动确定变量)。

I'd like to inspect and manipulate code of arbitrary Perl procedures (got by coderefs) in Perl. Is there a tool/module/library for that? Something similar to B::Concise, except that B::Concise prints the code on output, but I'd like to inspect it programmatically.

I'd like to use it like this. Given a coderef F, which is called eg. with 10 arguments:

$ret = &$F(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10);

I'd like to create a function F1, st.

&$F(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10) == 
  &$F1(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10)*
  &$C(x2, x3, x4, x5, x6, x7, x8, x9, x10)

that is to "factor" it into two parts, where the second doesn't depend on x1 and the first is as simple as possible (I assume F is constructed as a huge product).

The application I want this for is optimization of Metropolis sampling algorithm - suppose I'm sampling the distribution p(x1 | x2 = X1, x3 = X3, ...) = f(x1, x2, x3, ...). The algorithm itself is invariant wrt. multiplicative constant factors, and other variables do not change through the algorithms, so the part not depending on x1 (ie. $c from above) need not be evaluated at all).

The joint probability might have eg. the following form:

  p(x1, x2, x3, x4, x5) = g1(x1, x2)*g2(x2, x3)*g3(x3, x4)*g4(x4, x5)*g5(x4, x1)*g6(x5, x1)

I also consider constructing p as an object consisting of the factors with annotations of which variables does a particular factor depend on. Even this would benefit from code introspection (determining the variables automatically).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

小…红帽 2024-10-02 22:50:22

对于 opttree 的自省,通常使用 B 系列模块。

给定代码引用 $cv,首先为此创建一个 B 对象:

my $b_cv = B::svref_2object($cv);

现在您可以调用 B 用于从 optree 检索各种内容。

仅使用 Optree 内省,您就已经可以实现令人惊奇的事情。请参阅 DBIx::Perlish 了解这方面的高级示例。

还有一个 B::Generate 模块,允许构建新的 opttree 可以做任何你想做的事情,或者操纵现有的 opttree。然而,B::Generate 并不像人们希望的那样成熟,并且存在许多缺失的功能和相当多的错误。

实际的 opttree 创建和操作通常最好使用 perl 的 C api 来完成,如 perlapiperlgutsperlhack 等等。您可能还需要学习一些XS,以公开您写回perl的optree操作函数空间,但这确实是最简单的部分。

构建 opttree(不一定基于正在内省的其他现有 opttree)最近似乎变得有些流行,特别是自从 Syntax Plugins 已添加到 perl 5.12.0 的核心中之后。您可以找到各种示例,例如 Scope::Escape::Sugar 在 cpan 上。

然而,处理 Perl 的 opttree 仍然有些繁琐,而且不太适合初学者。对于任何最神秘的事情来说都没有必要。像使用 B::Deparse->new->coderef2text($cv) 这样的东西,然后可能对评估的源代码进行轻微的修改,这确实是我想要进行 optree 内省的事情来自纯 Perl 空间。

您可能需要退后一步并解释您要解决的实际问题。也许有一个更简单的解决方案,根本不涉及扰乱 opttree。

For introspection of optrees the B family of modules is usually used.

Given an code reference $cv, first create a B object for that:

my $b_cv = B::svref_2object($cv);

Now you can call the various methods documented in B on that to retrieve various things from the optree.

Using only optree introspection you can already achieve amazing things. See DBIx::Perlish for a pretty advanced example of this.

There also happens to be a B::Generate module, that allows building new optrees that do whatever you want, or to manipulate existing optrees. However, B::Generate isn't as mature as one would hope, and there's a lot of missing features and quite a few bugs.

Actual optree creation and manipulation is usually best done using perl's C api, as documented in perlapi, perlguts, and perlhack, among others. You'll probably have to learn some XS as well, to expose the optree manipulation functions you wrote back to perl space, but that's the easy part really.

Building optrees (not necessarily based on other existing optrees that are being introspected) seems to have become somewhat popular recently, especially since Syntax Plugins have been added to the core in perl 5.12.0. You can find various examples like Scope::Escape::Sugar on cpan.

However, dealing with perl's optrees is still somewhat fiddly and not exactly beginner-friendly. It shouldn't be necessary for any of the most arcane things. Something like using B::Deparse->new->coderef2text($cv) and then maybe mangling very slightly with the evaluated source code is really as far as I would want to go with optree introspection from pure-perl space.

You might want to step back a bit and explain the actual problem you're trying to solve. Maybe there's a much simpler solution that doesn't involve messing with optrees at all.

人疚 2024-10-02 22:50:22

鉴于您重申的问题 - 我认为您应该在这里做的,而不是尝试修改 coderefs,而是尽可能长时间地延迟使用 coderef。

  1. 创建一个代表计算实例的对象。
  2. 在此对象上编写评估计算值所需的方法。没有代码生成器,只需以漫长而缓慢的方式进行即可。这只是为您提供后续步骤的代码基线,该基线易于测试并希望易于理解。
  3. 编写测试以确保您在步骤 2 中所做的事情的正确性。(如果您是那种人,请在步骤 2 之前交换它。)
  4. 通过编写将计算对象转换为的方法来实现您在这个问题中提出的问题一种新的,代表相同计算的更优化形式。使用您的测试来确保计算在优化后仍然给出正确的结果。
  5. 编写接受计算对象的代码,并生成执行该计算的子对象(无论是通过字符串 eval 还是使用 B)。使用测试来确保计算在编译后仍然给出正确的结果。

插入 2 到 5 之间任意位置的可选步骤:

  • 编写一些语法糖(可能使用重载,但也可以使用其他工具),让您使用类似于计算的漂亮表达式构造“计算对象”它本身,而不是大量的对象构造函数。

Given your restated question -- I think what you should do here, instead of trying to munge coderefs, is to delay having a coderef as long as possible.

  1. Create an object representing an instance of your computation.
  2. Write the methods on this object needed to evaluate the value of the computation. No codegen, just do it the long slow way. This is just to give you a baseline of code for the next steps that's easily tested and hopefully easily understood.
  3. Write tests to ensure the correctness of what you did in Step 2. (Swap this before Step 2 if you're that kind of person.)
  4. Implement what you're asking about in this question, by writing methods to transform a computation object into a new one that represents a more-optimized form of the same computation. Use your tests to ensure that computations still give the right result after optimization.
  5. Write code that takes a computation object, and generates a sub (whether by string eval or using B) that carries out that computation. Use your tests to ensure that computations still give the right result after they've been compiled.

Optional step to insert anywhere between 2 and 5:

  • Write some syntactic sugar (probably using overload, but other tools are possible too) to let you construct "computation objects" using nice-looking expressions that resemble the computation itself, instead of lots and lots of object constructors.
疯了 2024-10-02 22:50:22

Perl 5 不允许您像这样动态操作字节码,但您可以创建匿名函数。如果我正确理解你的示例,并且我怀疑我理解正确,那么你已经有两个被 $f1$c 引用的函数,并且你想创建一个新的引用 $f 保存前两个相乘的结果。这很简单:

my $f = sub { $f1->(@_) * $c->(@_[1 .. 9]) };

$f->(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

请注意使用箭头运算符而不是 & 来取消引用 coderef。这种风格更常见(并且在我看来更具可读性)。

Perl 5 does not let you manipulate the bytecode on the fly like that, but you can create anonymous functions. If I understand your example correctly, and I doubt I do, you already have two functions that are being referenced by $f1 and $c, and you want to create a new reference $f that holds the results of the first two multiplied by each other. This is simple:

my $f = sub { $f1->(@_) * $c->(@_[1 .. 9]) };

$f->(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

Note the use of the arrow operator rather than the & to dereference the coderefs. This style is much more common (and in my opinion more readable).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文