代码错误或我的误解? [英] Code gen error or my misunderstanding?

查看:81
本文介绍了代码错误或我的误解?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我看到了我无法从F#编译器(Visual F#3.1.1.0)解释的行为-表面上看起来仅仅是具有命名局部变量和传递临时变量之间的差异实际上会产生行为差异

I'm seeing behavior I can't explain from the F# compiler (Visual F# 3.1.1.0)--what appears on the surface to be only the difference between having a named local and passing a temporary actually produces a behavior difference.

我是否不了解F#行为,或者这是代码生成错误? (我知道,后者更有可能.)

Am I not understanding something about F# behavior, or is this a code gen error? (I know, the latter is more likely.)

Repro -我发现不使用Reactive Extensions很难进行复制,所以这和我得到的一样简单.请注意,try1try2几乎相同.

Repro - I found it difficult to repro without using Reactive Extensions, so this is about as simple as I got it. Note that try1 and try2 are nearly identical.

open System
open System.Reactive.Linq
open System.Threading

let interval = TimeSpan.FromSeconds(0.5)
let testDuration = TimeSpan.FromSeconds(2.0)

let mkHandler () = // creates a function that closes over state
    let count  = ref 0
    fun _ -> count := !count + 1
             printfn "State is now %d" !count

let try1 () =
    printfn "try1"
    let handler = mkHandler ()
    use subscription = Observable.Interval(interval).Subscribe(handler)
    Thread.Sleep(testDuration)

let try2 () =
    printfn "try2"
    // creates handler inline:
    use subscription = Observable.Interval(interval).Subscribe(mkHandler ())
    Thread.Sleep(testDuration)

[<EntryPoint>]
let main argv = 
    try1 ()
    try2 ()
    0

输出-try1try2函数分别说明了期望的行为和不期望的行为.该程序的输出为:

Output - The try1 and try2 functions illustrate the desired and undesirable behaviors, respectively. Output from the program is:

try1
State is now 1
State is now 2
State is now 3
try2
State is now 1
State is now 1
State is now 1

根据我的理解,try2的行为应与try1相同.如果没有,请说明这种微小差异应如何发挥不同的作用.

According to my understanding try2 should behave the same as try1. If not, please explain how this minor difference should function differently.

通过检查反编译器的输出,我确定了以下内容:

From examining the output of a decompiler I have determined the following:

mkHandler正常运行;它创建了一个关闭唯一状态的函数.多次调用时,它将改变该状态.

mkHandler is functioning correctly; it creates a function that closes over unique state. When called multiple times it mutates that state.

try1try2都调用Subscribe的相同重载:public static IDisposable Subscribe<T>(this IObservable<T> source, Action<T> onNext)

The same overload of Subscribe is called by both try1 and try2: public static IDisposable Subscribe<T>(this IObservable<T> source, Action<T> onNext)

try1生成的幕后助手代码将关闭处理程序函数并正确调用它:

The behind-the-scenes helper code generated for try1 closes over the handler function and invokes it correctly:

[CompilationMapping(SourceConstructFlags.Closure)]  
[Serializable]  
// subscription@16  
internal sealed class subscriptionu004016
{
    public FSharpFunc<long, Unit> handler;

    public subscriptionu004016(FSharpFunc<long, Unit> handler)
    {
    }

    internal void Invoke(long obj)
    {
        this.handler.Invoke(obj);
    }
}

try2的幕后助手代码不会关闭处理程序函数,而是每次调用mkHandler工厂函数;这说明了输出,但不是所需的行为:

The behind-the-scenes helper code for try2 does not close over the handler function but calls the mkHandler factory function every time it's invoked; this explains the output, but is not the desired behavior:

[CompilationMapping(SourceConstructFlags.Closure)]
[Serializable]
// subscription@22-1
internal sealed class subscriptionu004022u002d1
{
    public subscriptionu004022u002d1()
    {
    }

    internal void Invoke(long obj)
    {
        Program.mkHandler<long>().Invoke(obj);
    }
}


重申我的问题:为什么这两个函数的行为不同?这是代码生成错误吗?以上都不是吗?


To reiterate my question: Why do these two function behave differently? Is this a code gen error? None of the above?

推荐答案

据我所知,您的代码没有任何问题-您正在做的事情是有道理的.在F#编译器中,这似乎是一个微妙的错误.

As far as I can see, there is nothing wrong with your code - what you're doing makes sense. This seems to be a subtle bug in the F# compiler.

我怀疑编译器如何解析Subscribe方法有问题.您的代码正在创建F#函数值,但是编译器会自动将其包装到Action<int64>委托中并使用Subscribe的Rx版本.但是,它通常不会自动将部分应用的函数转换为委托-似乎仅在这种情况下发生.

I suspect that there is something wrong with how the compiler resolves the Subscribe method. Your code is creating an F# function value, but the compiler wraps it into Action<int64> delegate automatically and uses Rx version of Subscribe. However, it does not normally automatically turn partially applied functions to delegates - it seems to be happening only in this case.

最简单的解决方法似乎是更改您的mkHandler函数以显式创建委托,然后一切都按预期工作:

The easiest workaround seems to be to change your mkHandler function to explicitly create the delegate and then everything works as expected:

let mkHandler () = // creates a function that closes over state
    let count  = ref 0
    Action<int64>(fun _ -> 
      count := !count + 1
      printfn "State is now %d" !count)

编辑:经过更多调查,我想说这是一个错误,具体发生在IObservable<T>Subscribe方法上.由于F#自动将事件视为IObservable<T>值,因此对事件进行了一些特殊处理,并添加了Subscribe方法.如果在其他地方声明了Subscribe扩展名,则会发生冲突,从而导致事情中断.

After some more investigation, I'd say that this is a bug which happens specifically with the Subscribe method of IObservable<T>. Since F# treats events automatically as IObservable<T> values, it has some special handling for them and it adds Subscribe method. If there is a Subscribe extension declared elsewhere, it clashes and things break.

我能找到的最简单的复制方法是使用以下命令创建一个C#项目:

The easiest repro I could find is to create a C# project with:

public static class Extensions {
  public static void Subscribe(this IObservable<int> c1, Action<int> f) { 
    f(1);
    f(2);
  }
}

然后完全按照您的意愿进行操作:

And then do exactly what you did:

let partial() = 
  printfn "called"
  fun n -> ()

let a = new Event<int>()
let o = a.Publish.Subscribe(partial())

这将两次打印被调用",而实际上应该仅被调用一次.我为此问题在F#错误跟踪器上创建了错误.

This prints "called" twice, while it should really be called just once. I created a bug for this issue on the F# bug tracker.

这篇关于代码错误或我的误解?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆