On 18/08/2012, at 4:28 AM, Dobes Vandermeer wrote:

> 
> It's even wuss dan dat! Consider:
> 
>         val x = if a then b else c endif
> 
> That reduces (internally) to
> 
>         val x;
>         if a then x = b;
>         else x = c;
> 
> so the optimiser actually sees two initialisations of the one variable.
> Only one gets executed (on a single control sweep).
> 
> Is that desirable, or accidental?

Neither: its appears to be necessary: how else can you
implement it?

Felix reduces stuff to "three-address" assembler code
or the moral equivalent of it. So actually what you get is
even worse that what I wrote:

        if (!a) goto label;
        x = c;
        goto endoff;
label:>
        x = b;
endoff:>

Felix has goto, conditional goto as primitives. (It also has halt, call,
return, jump, yield, and a few other such things).

There are no high level control structures like for loops,
while loops, blocks, or anything else: these have to be
implemented in the library (typically directly in the
grammar, which, remember, is in user space so its
part of the library).


> 
> Because the point of initialisation is not well defined, deliberately.
> If you write:
> 
>         var x = 1;
>         val y = x;
>         ++x;
>         println$ y;
> 
> you might get 1 or 2. You will get 1 with eager evaluation and 2 with lazy 
> evaluation.
> 
> Well, who exactly benefits from this unpredictable behavior?

You do. Most of the time it makes no difference to the semantics,
and it allows generation of blindingly fast Hyper Light Speed code.

Its one of the key innovations of Felix over other languages:
it uses neither eager evaluation nor lazy evaluation but (for vals)
allows either.

Eager evaluation seems like it is fastest: evaluate argument
and assign to parameter. You only calculate it once,
what could be faster?

But actually lazy evaluation -- a sexy name for "substitution"
is almost always MUICH faster because after you substitute
further optimisation can be applied: until you "convolute"
the patterns you do not get the opportunity to perform
reductions that are "broken" by the separation of the
code containing the variable and its initialiser.
Trivial example:

        val x = 0;
        val y = 2 * x;

Duh. Obviously y = 0, if we substitute 0 for x everywhere we can get rid
of x. If our routine is recursive eliminating local variables is a HUGE
performance boost, because it reduces stack use "per recursion"
and of course also optimises cache use.

Of course "inlining" is of course also another name for lazy evaluation.


> 
> IMO if a val depends on anything that can change, that part of the 
> calculation should be "snapshotted" at the point of initialisation, or the 
> val should be eagerly evaluated.

Felix tries a bit to do this in straight line code. But it is hard to do when 
the
initialiser is a function argument and the val is a parameter.

>  When in doubt, eagerly evaluate.  

Why? Haskell takes the opposite approach because eager evaluation
is fraught with difficulties such as non-termination, division by zero:

        if a == 0 then b  else c / a endif

This code will always fail when a == 0 if it is eagerly evaluated.


> Predictable behavior trumps performance optimization ... this is my own value 
> system, where compiler optimisations should never impact runtime behavior in 
> any externally visible manner.  That includes the decision to lazily evaluate 
> something.

You need to understand how specifications work though.
Predictable does not mean deterministic.

It means: IF you do X THEN you will get Y.
And thus: OTHERWISE all hell breaks loose.

What Felix does is predictable. I mean, the compiler
is a completely deterministic machine.

The question is: how much slack do we allow the compiler,
and the val semantics are based on the notion that performance
comes first.

If you don't like it, use vars! They're always eagerly evaluated.


> What's point in spending all this effort with a strict type system that 
> prevents type errors, and yet add some lazy evaluation system that introduces 
> unexpected semantic errors?

The point is to get performance first at all costs.
As it turns out, the existing semantics rarely cause trouble.

However this is really the point of wanting to add "const".
There's nothing wrong with val semantics, it just isn't enough.
Indeterminacy is good, its precisely where performance optimisations
come from.

>  
> > Do not allow a val to be initialized more than once or be used before it is 
> > initialized.
> 
> How do i stop it? Don't allow vals at all after a label?
> 
> A lexical approach seems fine - if the val is used in a statement that comes 
> earlier than it's declaration, report an error "val x used before it was 
> defined". Same for vars.

That's hard to determine! Did you forget nested functions? 
What about closures?

For vars: pointers.

> 
> This information came as a suprise to me, so I did a test:
> 
> proc b(i:int)() = {
>   // i += 1;
>   println$ "b, i = "+i;
> }
> proc a() {
>   var i = 0;
>   bb := b(i);
>   i = 10;
>   bb;
> }
> a();
> // prints b, i = 10
> 
> You complain about the dangers of downcasting in other languages, and yet 
> this doesn't flabbergast you?
> 
> It's a programming language boobytrap!  It's actually breaking the contract 
> of how parameters and closures are understood to work.  That is, parameters 
> are your own private immutable copy, and creating a closure of them captures 
> the value at the time of the original call.

No it doesn't. You just misunderstood the contract. You have not captured
a value, you captured a variable. A variable is logically a pointer.
You captured the variable address correctly.

the point is that parameter i of b can be lazily evaluated which means
i is replaced by the i in a.

I agree this is surprising. The question is how to fix it.
Changing the semantics of val is not an option. I have looked at this
and it would disable a huge number of significant optimisations,
and 99% of the time there is no semantic impact.

In fact "lazy" or "normal order evaluation" is the standard in 
lambda calculus. The problem is that we have thrown in variables
to a functional system.

If you ran the above program in Ocaml with a ref instead of the var
(an Ocaml ref that is) you'd get the same answer as Felix.
To get the answer you wanted in Ocaml you have to deref it
(that is, pass the value of the ref at the point of closure creation).

Eager evaluation (as noted elsewhere) has other problems:
bad performance and unexpected failure (nontermination,
division by zero, or worse).

This is why i suggest "const": it's referentially transparent
so it necessarily cant produce different values.

However we're not finished, because, unfortunately,
even transparent expressions don't need to be total!
That is: division is pure, but it isn't total. So it STILL 
matters when you evaluate it.


--
john skaller
skal...@users.sourceforge.net
http://felix-lang.org




------------------------------------------------------------------------------
Live Security Virtual Conference
Exclusive live event will cover all the ways today's security and 
threat landscape has changed and how IT managers can respond. Discussions 
will include endpoint security, mobile security and the latest in malware 
threats. http://www.accelacomm.com/jaw/sfrnl04242012/114/50122263/
_______________________________________________
Felix-language mailing list
Felix-language@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/felix-language

Reply via email to