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

Reply via email to