Looks like a bug to me. Here's a much simpler repro:

1
2
3
let rec A = C
and B = 2.0
and C = B

Based on this, I suspect that the compiler is treating some bindings as constant in a way that's not compatible with a top-to-bottom evaluation order (e.g. because B is constant, so is C, and therefore A just reads C's value rather than forcing a thunk, which doesn't work if C's value isn't set first).

By on 3/24/2010 9:40 AM ()

It seems I've found another issue. The following code contains a circular relationship but it will still be compiled (F# v1.9.9.9):

1
2
3
4
5
6
// ISSUE: a circular relationship and infinite loop!

let rec a = b

and b = a

although in the next case the compiler will correctly detect the loop:

1
2
3
4
5
6
// OK: it won't be compiled

let rec a = b + 1

and b = a

In my opinion it looks like a contradiction at least.

By on 3/26/2010 12:50 AM ()

The updated F# spec explains why

1
2
let rec a = b + 1
and b = a

is flagged as an error but

1
2
let rec a = b
and b = a

isn't. In the second case, a and b are inferred to be values of generic type, which means that they must be represented as method calls and therefore their definitions aren't immediately evaluated. Thus, the following very similar code is also flagged as an error:

1
2
let rec a = b
and b = (a:int)

I think that it would be nice for the generic version to be flagged as well, but the spec calls this out as a limitation in F# 2.0 so hopefully it will be addressed in a future version of the language.

By on 4/1/2010 9:06 AM ()

Oddly enough, I noticed that same issue earlier this week and reported it to fsbugs. Fortunately, the code is so obviously wrong that I doubt it will cause any issues in practice.

By on 3/26/2010 6:33 AM ()

Based on this, I suspect that the compiler is treating some bindings as constant in a way that's not compatible with a top-to-bottom evaluation order (e.g. because B is constant, so is C, and therefore A just reads C's value rather than forcing a thunk, which doesn't work if C's value isn't set first).

In F# let bindings are constant, unless you use the mutable keyword. I suspect this is why the compile generates a warning when you trying and initialize recursive values.

By on 3/24/2010 9:50 AM ()

Robert,
You're right that the bindings are all constant; my description of what the compiler is doing wrong is probably not accurate. However, I do believe that this behavior is a bug - the relevant portion of the spec seems to indicate that this code:

1
2
3
let rec A = C
and B = 2.0
and C = B

should be equivalent to something like this:

1
2
3
4
5
6
7
#r "FSharp.PowerPack"
let rec ATemp = lazy (Lazy.force CTemp)
and B = 2.0
and CTemp = lazy B

let A = ATemp.Force()
let C = CTemp.Force()

This second set of bindings produces the expected result.

The reason that F# warns when defining mutually recursive values is that even after inserting delays and forces, it may still be impossible to come up with a sound initialization order. For instance,

1
2
let rec A = 1 + B
and B = 1 + A

would convert to something like

1
2
3
4
5
let rec ATemp = lazy (1 + Lazy.force BTemp)
and BTemp = lazy (1 + Lazy.force ATemp)

let A = ATemp.Force()
let B = BTemp.Force()

At runtime this would fail, since forcing ATemp forces BTemp, which would then try to recursively force ATemp. The initial example in this thread does not suffer from that problem, though (and if it did, the result would be an exception at runtime rather than bindings with incorrect values).

By on 3/24/2010 10:58 AM ()

And I indeed received an exception at runtime in case of my monad. Then I decided to write about the issue.

It's interesting that your repro is so small. I rewrote it slightly with help of the async workflow.

let rec a = c

and b = async { return 2.0 }

and c = b

printfn "%A" (Async.RunSynchronously a)

Now I receive the NullReferenceException exception. Here it means that some variable is requested before its thunk is created. I suspected another behavior. We should create all thunks with help of delayed functions and only then begin their evaluation.

By on 3/24/2010 11:10 AM ()

Can't comment on whether it's a bug or an ambiguity, but didn't note that you get a compiler warning from the above code because of the recursive definitions of values. If you rewrite it without the recursive definitions, you get the correct answer:

let Area = 100.0

let Carrying_Capacity = 1000.0

let Fish = 1000.0

let Fish_Price = 20.0

let Fraction_Invested = 0.2

let Ships = 10.0

let Ship_Cost = 300.0

let Hatch_Fraction = 6.0

let Death_Fraction = (Fish/Carrying_Capacity)

let Fish_Death_Rate = (Fish*Death_Fraction)

let Fish_Hatch_Rate = (Fish*Hatch_Fraction)

let Density = Fish/Area

let Catch_per_Ship = Density

let Operating_Cost = Ships*250.0

let Total_catch_per_Year = Ships*Catch_per_Ship

let Revenue = Total_catch_per_Year*Fish_Price

let Profit = Revenue-Operating_Cost

let Ship_building_Rate = Profit*Fraction_Invested/Ship_Cost

let Annual_Profit = Profit

val Area : float = 100.0

val Carrying_Capacity : float = 1000.0

val Fish : float = 1000.0

val Density : float = 10.0

val Hatch_Fraction : float = 6.0

val catch_per_Ship : float = 10.0

val Death_Fraction : float = 1.0

val Fish_Death_Rate : float = 1000.0

val Fish_Hatch_Rate : float = 6000.0

val Fish_Price : float = 20.0

val Fraction_Invested : float = 0.2

val Ships : float = 10.0

val Operating_Cost : float = 2500.0

val Total_catch_per_Year : float = 100.0

val Revenue : float = 2000.0

val Profit : float = -500.0

val Ship_Cost : float = 300.0

val Ship_building_Rate : float = -0.3333333333

val Total_Profit : float = 0.0

val Annual_Profit : float = -500.0

By on 3/24/2010 2:06 AM ()

As far as I understand, the problem is that the F# compiler cannot resolve the dependencies properly while it truly informs that the system is resolvable. For example, if I created a circular relationship then it would truly say that the system is wrong and would show the cause. Moreover, if I applied the lazy keyword then the compiler would kindly allow me to create a loopback that I need for the integrals. All this looks fine. But I suppose that the compiler cannot initialize the variables in this particular case, although it looks strange. For example, in Scheme there is the LETREC construct that will work. Also I wrote its analog for Common Lisp and it was a simple task using macros and an implicit laziness. But unlike F# the Lisp system cannot check whether the system of recursive variables is resolvable.

David

By on 3/24/2010 8:44 AM ()

Robert, I intentionally used a recursive definition. Actually, this is a simplification of a dynamic system consisting of differential equations based on my modeling monad. I need the recursion to define the integrals with loopbacks. If it worked then I (and my users too) could very easily define complex dynamic systems in F# and then simulate them. I have to say that it indeed works in simple cases but not now.

By on 3/24/2010 4:38 AM ()
IntelliFactory Offices Copyright (c) 2011-2012 IntelliFactory. All rights reserved.
Home | Products | Consulting | Trainings | Blogs | Jobs | Contact Us | Terms of Use | Privacy Policy | Cookie Policy
Built with WebSharper