在编译中,Do / Return的行为有所不同 - 为什么?

时间:2011-11-18 15:08:44

标签: wolfram-mathematica

我想知道这是一个错误还是记录在案的行为?

f1 = Function[v, 
  Do[If[v[[i]] < 0, Return[v[[i]]]], {i, 1, Length[v]}]]

c1 = Compile[{{v, _Integer, 1}}, 
  Do[If[v[[i]] < 0, Return[v[[i]]]], {i, 1, Length[v]}]]

将它们应用于不包含负数的列表时,我们会得到不同的结果:

In[66]:= Through[{f1, c1}[{1, 2, 3}]]

Out[66]= {Null, 3}

当我尝试编译short function(实际上是它的修改版本)时,这会导致错误。

Do并未显示问题:

c2 = Compile[{}, Do[i, {i, 5}]]

c2[] (* returns nothing, as expected *)

4 个答案:

答案 0 :(得分:5)

我会说这是Compile工作方式的错误,但它不能正常运行并不奇怪。 Compile确实对其输入(此处,v将是整数列表)以及其输出做出了非常具体的假设。编译函数应该返回单个特定类型的值,并且该类型必须是可接受的类型之一,作为编译函数的输入:True|FalseInteger等等,和相同的数组。如果函数抱怨一条消息然后返回Null显然会更好,但为了成为一个表现良好的编译函数,你需要提供一个适当的整数返回值作为一个defalult。

编辑以澄清下面的Szabolcs'评论中的输出类型。

答案 1 :(得分:4)

我不会说这是一个错误。正如@Pillsy所指出的那样,Compile - d函数受到更多限制,因为它必须始终返回相同的类型。由于Do是一个范围构造,Return内的Do只会突破Do,而不是Function。因此,在某些情况下,它返回一个向量元素,在其他情况下,它返回Null。严格来说,正如所写,该函数根本不应该编译。但是,一个人可以更灵活,并假设函数的编写者知道更好,并将丢弃该特定情况下的答案。通过这种解释,Compile可以自由地在这种情况下产生任何答案。它在这里做的是生成列表中的最后一个元素。而且我认为,这并不是每次都产生固定数字的临时性。我还认为,当编译更灵活的符号代码时,无法避免这种极端情况。 Compile在这种情况下可能有更严格的规则,要求在所有情况下都有一些有意义的回报(相同类型),但我不清楚这是否真的有益。从某种意义上说,所有的C都是这样的 - 编译器假定你知道自己在做什么,但如果不小心,你可以创建许多未定义的行为。

答案 2 :(得分:3)

如@Pillsy和@Leonid的回答所述,问题是原始函数有时会返回Null,有时会返回整数。相反,编译的函数总是返回一个整数。在V8中,我们可以使用CompilePrint

看到这一点
Needs["CompiledFunctionTools`"]
CompilePrint @
  Compile[{{v,_Integer,1}},Do[If[v[[i]]<0,Return[v[[i]]]],{i,1,Length[v]}]]

,在V8.0.4下,产生了这个结果:

        1 argument
        1 Boolean register
        6 Integer registers
        1 Tensor register
        Underflow checking off
        Overflow checking off
        Integer overflow checking on
        RuntimeAttributes -> {}

        T(I1)0 = A1
        I3 = 0
        I0 = 1
        Result = I5

1   I2 = Length[ T(I1)0]
2   I4 = I3
3   goto 10
4   I5 = Part[ T(I1)0, I4]
5   B0 = I5 < I3
6   if[ !B0] goto 10
7   I5 = Part[ T(I1)0, I4]
8   goto 11
9   goto 10
10  if[ ++ I4 < I2] goto 4
11  goto 12
12  Return

我们可以看到编译函数的结果是整数寄存器I5中的任何结果。根据反编译指令的流程,我们看到如果没有匹配,那么I5将最终包含列表的最后一个元素。

编译器的行为可能会在Mathematica的发行版之间发生变化。我认为有理由认为编译器应该在返回结果类型不明确的情况下至少发出警告。

答案 3 :(得分:3)

您可能会发现一些有用的其他信息。考虑一下:

In[26]:= f1 = 
 Function[v, Do[If[v[[i]] < 0, Return[v[[i]]]], {i, 1, Length[v]}]; 
  last = 1;];

In[27]:= last

Out[27]= last

In[28]:= f1[{-1, 2, 3}]

In[29]:= last

Out[29]= 1

尽管该函数应该在第一个元素上返回它返回= 1,因此,正如其他人所指出的那样,返回被破坏了。这不会被修复,因为有太多的代码依赖于这种行为。

现在,您可以使用:

In[30]:= f2 = Function[v, Module[{},
   Do[If[v[[i]] < 0, Return[v[[i]], Module]], {i, 1, Length[v]}]; 
   last2 = 1;]];

In[31]:= f2[{-1, 2, 3}]

Out[31]= -1

In[32]:= last2

Out[32]= last2

其行为符合预期。不幸的是,

In[33]:= c1 = Compile[{{v, _Integer, 1}},
  Module[{},
   Do[If[v[[i]] < 0, Return[v[[i]], Module]], {i, 1, Length[v]}];
   ]
  ];

不会编译。

这是一种让它发挥作用的方法。

In[137]:= c1=Compile[{{v,_Integer,1}},
Module[{res=1},
Do[If[v[[i]]<0,res=v[[i]];Break[]],{i,1,Length[v]}];
If[res==1,Internal`CompileError[]];
res
]
,"RuntimeOptions"->{"RuntimeErrorHandler"->Function[Null]}]

In[140]:= c1[{1,2,3,1}]

In[141]:= c1[{1,2,3,-1}]

Out[141]= -1

检查输出。

In[139]:= CompilePrint[c1]

进一步说明:“RuntimeErrorHandler” - &gt;函数[Null]这是一个函数!这点考虑一下吧。你可以Thow,发消息!

这样的事情就可以了。

cfquietfail = 
  Compile[{{x, _Real, 1}}, Exp[x], 
   "RuntimeOptions" -> {"WarningMessages" -> False, 
     "RuntimeErrorHandler" -> 
      Function[Message[MyFunctionName::"I can complain here!"]; 
       Throw[$Failed]]}];
Catch[ cfquietfail[{1000.}]]

我希望这很有用。