On 29/01/2014, at 11:52 PM, srean wrote: > Conflicted on eager vs lazy. I like default to be eager if laziness can be > forced.
I guess you have to unlearn you C prejudices. Almost all code *especially* in procedural languages uses lazy evaluation. As o...@pobox.com said to me, lazy evaluation IS control flow. And procedural languages are all about control flow. Just consider any branch, and you have lazy evaluation. The argument bodies of the branches are only executed if the condition for that branch is met: if (cond) { branch1; } else { branch2; } In a functional language this doesn't matter much. In fact if the functions branch1 and branch2 are pure and strict it doesn't matter at all. And if the functions aren't strict, then you're much better off with lazy evaluation. For example if one of the branches contains a division by zero, eager evaluation crashes the code even if the branch isn't selected. In procedural language the "functions" have side effects, so you definitely DONT want to eagerly evaluate these branches. > What I didnt realize is that in this setup the laziness would be runtime > laziness (i.e. via thunks). There are no thunks in Felix. Thunks (or trampolines) are a hack which puts executable code on the stack. This is done in crap languages like C to trick the system into binding a client data pointer to a function. The function on the stack loads a register with the client data pointer then calls the actual function. Felix uses real closures: pointers to C++ class objects which contain a pointer to the function (technically this is a lie: Felix is using virtual methods, so the objects actually contain a pointer to a vtable). These objects are first class values (that is, the pointers to them are first class). However they're mutable. So to avoid problems (eg with recursion) when you call a function stored in a variable, the object is cloned onto the heap. Generators, however, are meant to preserve state between calls so their clone functions just return the C++ this pointer instead. When you do a direct call to a function (not via a variable) you know the actual function (whereas with a variable you only know the type). So in that case we just construct a fresh object on the heap every time. This has nothing to do with laziness. This is how all functions and procedures work. Ok, so that's the white lie. The truth is that Felix "optimises away" as much unnecessary work as possible. In particular, for direct calls it can often just inlines the code. If you're calling through a variable, you can't inline anything. Then the model has to be followed strictly. That's a grey lie... Felix is sometimes smart enough to know what got put in the variable and do a direct call instead, but it cannot do this very well or very often. NONE of this has anything to do with evaluation strategy. Felix never makes closures for argument expressions like Haskell does. If you want to force lazy evaluation you have to do this manually. When calling a closure Felix (currently) always uses eager evaluation. It has to .. because it has no idea what function is stored in a variable. All it knows is the type, i.e. the call signature. When Felix inlines code then it can not only replace the call with the function body, it can also replace the parameters with their actual arguments. That is lazy evaluation. If the parameters are vars, it should do eager evaluation even when inlining. The parameters remain, and are assigned by the argument before the inlined body of the function is entered. But NONE of this is a worry most of the time, and if anything is a worry semantically it is eager evaluation. Lazy evaluation is always safer and usually faster. The worry with lazy evaluation would be if the argument expressions had side effects. Well DONT DO IT. If you use a generator as an argument, you're asking for trouble. If it is a direct call Felix WILL lift the call out so it is done once, before the function is called, and it will get the generated value. However Felix CANNOT do this if the argument expression contains the application of a generator closure. Why? Because it has no idea the variable containing the closure is a generator. Generators have the same type as functions. So now, let me explain that NONE of this is a problem. Its all fine, just do it. It won't bite, I promise :) No, there is indeed a problem, but it is not what you think. It has nothing to do with laziness. Rather it has to do with asynchronous behaviour and the interpretation of variables. This problem exists in C as well. It is not unique to Felix. When you make a closure, it can contain references to its parent scope. In particular it can refer to variables of the parent scope. These are accessed via a pointer to the parent frame. So naturally the value of those variables is the value when the closure runs .. NOT the value at the time the closure is created. A closure includes the pointer to the parent. It does NOT contain the arguments of the function. So if you do this: var x = 1; var f = { println$ x; }; ++x; f (); what do you expect to happen? It prints 2 of course. So what about this: var x = 1; proc f (y:int) () => println$ y; var g = f x; ++x; g(); It prints 1 of course. > Whereas, if the default was lazy, one could have compile time laziness by > inlining the body. I still have not shaken my fear of laziness and mutation, > without some compiler help to keep things sane. It would be good if the compiler provided stronger assurances. Part of the "experimental" concept is that it is not clear what these should actually be. Compromises are made because we want to retain C/C++ compatibility: embed C++, embed Felix in C++ (harder), and generate C++ ABI compliant libraries. Because we're stuck with crap but we want good stuff, we have to give up some guarantees. For example, you can wrap a non-local goto in a closure, pass it out of the function and down to a parent, and then execute the closure, causing the goto to execute when the target procedure frame is no longer live. The frame still exists! However it may have already returned. Worse, you can try to jump through a function. Since the jump code unwinds the spaghetti stack manually, all hell breaks lose. Felix tries to handle this by switching over to throwing an exception to unwind the machine stack .. but don't hold your breath, because the handler may be dead too :) And its pretty hard to alternate between unwinding spaghetti and throwing up all over the machine stack. So exactly how would you handle that? The one thing you can NOT do is ban non-local gotos, because often Felix creates procedures magically that the user doesn't know about. Not only that, but Felix itself generates non-local gotos. How else, for example, would you expect to "break" out of a loop, if the body is a procedure? -- john skaller skal...@users.sourceforge.net http://felix-lang.org ------------------------------------------------------------------------------ WatchGuard Dimension instantly turns raw network data into actionable security intelligence. It gives you real-time visual feedback on key security issues and trends. Skip the complicated setup - simply import a virtual appliance and go from zero to informed in seconds. http://pubads.g.doubleclick.net/gampad/clk?id=123612991&iu=/4140/ostg.clktrk _______________________________________________ Felix-language mailing list Felix-language@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/felix-language