Basically because you can embed a side effect in an F# expression.

So you're forced pretty much to have eager evaluation since you want all sorts of horrible side effects (IO, mutation) to happen. :-)

By on 6/8/2010 8:30 AM ()

There are good blogs/videos on why F# isn't lazy by Don Syme - basically, it doesn't mesh with .Net interop well, and can give surprisingly good perf for certain corner cases, but more "interesting" results for most programs.

There is another question: Why doesn't F# have controllable lazyness?

Now, F# does have Lazy<>, etc., but these are just thunks that are explicilty created and de-referenced, so saying that F# supports lazyness is the same as saying that C# or Java or any other language that supports delgates/fn pointers/lambads, etc., can support lazyness.

It seems to me that F# could simply do some compiler magic that would allow a "lazy" keyword:

let lazy x = Console.WriteLn("hi"); 42 // or just "lazy x = ..."
let y = x+1
let z = y x

this would get compiled to:

let x = lazy ( Console.WriteLn("hi"); 42 )
let y = x + 1
let y_lazy = x.Force() + 1 // a lazy "overload"
let z = y x // pick the right y here - in this case y_lazy

This would allow referential transparency when using lazy - now I can change from eager to lazy at the point of creation, and not modify my code, unless I want to control evaluation, in which case I can use the old form. Automatic inlinining could be done to force specialization of fn's to allow 'T or Lazy<'T> - and because we can't overload let-bound fn's at the moment, we'd need either a dynamic dispatch, or (better) speacial overload logic calling the _lazy version.)

It does mean, of course, that you have to be compiling the code that consumes that lazy value if you want to make sure the .Force() happens at the last possible moment - if you're passing into a library that expects 'T rather than either 'T or Lazy<'T>, then the compiler would be forced to call .Force() at that boundary.

I'm sure I'm missing something here, but this seems like the best of both worlds to me.

By on 6/8/2010 9:49 AM ()

I don't think that would work well with separate compilation. Consider if in a.dll you have

1
let f x = x + 1

and in b.dll you have

1
2
3
4
let x = 1
let lazy y = 2
f x
f y

then does y get forced when it's passed to f, or within f's body? If the former, then it's really not much of a lazy value (e.g. it would be forced even if f didn't use it), but if the latter, then two compiled copies of f's body would need to exist (for a lazy parameter versus a non-lazy one). This is even more problematic if you try to do something like "let g = List.map f in g [x; y]", where now g's internals need to know how to handle x and y differently.

By on 6/8/2010 3:48 PM ()

> I don't think that would work well with separate compilation

Hence my comment:

<i>...if you're passing into a library that expects 'T rather than either 'T or Lazy<'T>, then the compiler would be forced to call .Force() at that boundary...<i> <i>> <i> then two compiled copies of f's body would need to exist (for a lazy parameter versus a non-lazy one Right - that's what I meant by fn specialization. The map case just looks more complex to the eye due to the embedding/currying, but I think it's the same as far as the compiler is concerned. As noted, you're right that y will be forced across dll boundaries in your case, unless you had an [<OptionallyLazy>] in a.f() (or just a compiler switch) to force specialization when a.dll was compiled. (Unless you want fancy footwork such as tagged IL and custom JITing of specalized methods in a.dll at call-time.) I didn't say it was a quick weekend project :)

By on 6/8/2010 8:27 PM ()

Oops - I read your first post too quickly and didn't see that you had already addressed the separate compilation issue. However, I think that it would be pretty confusing for the evaluation order to change so dramatically based on whether a function is defined in the same assembly or not.

By on 6/8/2010 10:29 PM ()

>I think that it would be pretty confusing for the evaluation order to change so dramatically

I don't think evaluation order is effected at all, only when the computation is forced - the important thing is that the same result should be acheived either way - just lazyness will be effected. (Assuming the lazy val. doesn't depend on side-effects, which probably would have been trouble in the first place.)

However, given that good lazy algols tend to be different than good eager ones, I think it's not so bad to say that an entire lib has to be written this way - but I suppose one could just use Haskell and do some interop...

By on 6/9/2010 8:07 AM ()

You're probably right, but I think it depends on what you mean by side effects. Consider the following:

1
2
3
4
5
6
let f x = printfn "Hello world!"

let lazy y = 1 / 0

do
  f y

If I understand your proposal, this will print "Hello world", but if the function f is moved into another assembly, it would throw an exception due to division by zero. This is an artificial example, but you could imagine that the 0 denominator is being input by the user, for example. I think that this behavior is potentially very confusing

By on 6/9/2010 3:29 PM ()

You are probably right :) - but the same issue of using lazy values in this way will hold true for Haskell as well -- you don't know when a side-effect producing lazy vaule (like an exception) is going to be forced due to de-referencing when you call some external code - who knows if they just pass the value along or try and get the value. (I think the type inference system could give consumers this "pass-only vs. de-ref" info, but I don't think they do.) (Caveat: Im not an I/O using modads guru, so perhaps the safety lies there.) That's why I think it's a reasonable restriction to say that if you want auto-lazy values to work transparently and "deep", that you need to be in charge of all the code. Anyway, probably is not worth the effort, which is really the answer to my original question :) - I'm sure Don Syme and the rest of the gang thought about these things first...

By on 6/9/2010 5:10 PM ()

basically, it doesn't mesh with .Net interop well, and can give surprisingly good perf for certain corner cases, but more "interesting" results for most programs.

That sound very reasonable.

1
     let lazy x = Console.WriteLn("hi"); 42 // or just "lazy x = ..."

How about a lazy computation expression...

By on 6/8/2010 11:57 AM ()

>How about a lazy computation expression...

The idea I was going after is to not have any syntax at all at the reference site, but only at the decl/creation site, so that the same code (from the user's point of view) can consume lazy or eager values. (I.e., you could take existing source for factorial or a merge sort of whatever, and w/o any changes it would work for a Lazy<'T> or a 'T - just with different runtime tradeoffs.)

By on 6/8/2010 1:30 PM ()
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