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.
Am I not understanding something about F# behavior, or is this a code gen error? (I know, the latter is more likely.)
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
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
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
is functioning correctly; it creates a function that closes over unique state. When called multiple times it mutates that state.
The same overload of Subscribe
is called by both try1
and try2
: public static IDisposable Subscribe<T>(this IObservable<T> source, Action<T> onNext)
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);
}
}
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?
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.
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.
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)
EDIT: 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.
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())
This prints "called" twice, while it should really be called just once. I created a bug for this issue on the F# bug tracker.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With