l-lang-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[L-lang-devel] Let without type; syntax change


From: Matthieu Lemerre
Subject: [L-lang-devel] Let without type; syntax change
Date: Mon, 12 Feb 2007 00:34:44 +0100
User-agent: Gnus/5.11 (Gnus v5.11) Emacs/22.0.50 (gnu/linux)

I finally got the "let without types" feature into CVS.  The funny
part is that I wasn't at all working on it, when it hit me that
previous work allowed to put it in in a clear design, without too much
changes.

* Let expressions

The key changes are that let forms are now treated as expressions, not
statements.  The signification of "let a" as an expression is :
"introduce a new variable, a, from where it is defined until the end
of the enclosing scope.  Except from that, "let a" behaves just like
"a", in particular can be used as an r or lvalue."

So to sum up, "let a" is a bit like "++a": ++a is equivalent to
"increment a at the previous sequence point, and use that value", and
let a is "define a at the previous sequence point, and you can use a
from here".

Quite a lot of advantages then come from that:

** Uniformity of treatment

*** In macros and operators

When using macros, for instance "for", one need not care whether the
user introduces a new variable or not:

  /* i is global here.  */
  for(i = 0; i <= 10; i++)

and 

  for(let i = 0; i <= 10; i++)

are treated in the same way.

The same applies for foreach (foreach(let element in element_list)),
map, and more generally to any operators that use a temporary element
to traverse a collection.  I don't know any operators that could use
that too.

In short, writing expander that create operators that may or may not
introduce new variables (like the C99's for) will be greatly
simplified thanks to this.

*** In "deconstructors"

I plan to have what I may call "deconstructors".  This will allow to
retrieve individual components of a global structure at once.

For instance:

  let p = struct { x:Int; y:Int;};

  let i;
  struct(x:i, y:let j) = p; //same as i=p.x; let j = p.y

I'm not going to detail deconstructors here, but I will when I have
implemented them (which should be soon); they will also be the basis
for ML-like pattern-matching.

** Easily save temporary results to reuse them after.

It is often the case that the result of a computation that you do must
be reused after. Consider the following code:

  let temp_result = function1(arguments);
  function2(1 + 2 * temp_result);
  function3(3 + temp_result);

With let expressions, you could write instead:

  function2(1 + 2 * (let temp_result = function1(arguments)));
  function3(3 + temp_result);

Note that this could be used similarly in C99, but it isn't a common C
idiom.  I think that the introduction of let expressions could make
this kind of code more idiomatic (especially for people who like
Lisp-like functional programming).

The following example might be more convincing:

  if(let value = hash['toto'])
    { 
      /* use value here.  */
    }


In general, I hope that it will bring a programming style where all
temporary variables have the littlest possible scope; this simplifies
life for the programmer and renders code generation is faster (in
particular, live variables analysis) and better.

** Interaction between let expressions and macros

Now let's notice that macros can introduce new scopes, that are hidden
from the user.

For instance:

  macro min(x, y)
  Form({ let _x = $x;
         let _y = $y;
         if (_x < _y) _x else _y })

(which, btw, is a nice example of how let expressions enable to
rewrite this in one line).

If you try to use this macro like this:

  let x = min(let u = function1(25), 0);
  ... 
  u  

Then you will end up with an error telling you that u is unknown.
This is because the min macro introduces a new scope, that is hidden
from the user.

To cope with this problem, we will separate the macros into two
categories:

 - The macros which are autoscoped, i.e. introduce a new, invisible
   new scope around them.  Example of these are for, foreach, if,
   while ...

 - Other macros.

The general rule is that macros which behaves like functions should
NOT introduce implicit new scopes; whereas new operators can or cannot
do it (depending on what makes the most sense).

A new programming construct will have to be created to support "scope
for some variables only", when you need temporary variables like in
the min macro.  For now, we can ignore the problem because temporary
variables use symbol names that are not accessible from the outside;
for the future it will be more convenient (especially when we have
destructors) that these temporary variables are really destructed at
the end of the macro call (this can happen automatically).

** Parsing 

The introduction of let without types, and let expressions, introduces a 
parsing problem:

how to distinguish (for instance):

  let a * b  //(let a) * b

from 

  let Int * b // let (Int* b)

? L's syntax need to be changed.  The new grammar rule for a grammar
declaration is now id:type.

For instance, variables are declared that way:

  let x:Int = 3;
  let s:Symbol;  

Fields in a structure:

  type Point = struct { x:Int;
                        y:Int; }*;

(this makes it coherent with 

  let point = Point(x:3, y:4))

Functions are declared that way:

  Int move_to(point:Point, x:Int, y:Int)
  { ... }

(I also envision to make functions callable with key arguments like
this:

  move_to(x:3, y:4, point:p))

When we have kinds, a new colon will be added:

  let x:Int:static;
  let y::numeric;

But kind support isn't really for now.

** Left expander

Finally, what has made the let without type possible was the creation
of the left-expander feature.  Left expander allows the transformation
of fun(...) = exp() code into anything.

In that precise case, I just changed let(a) = exp into let(a,exp.type)
= exp.  And that's it.

let without type can thus only be used before an =, but that's what I
wanted anyway.  I believe that types of every variable should be
clearly apparent, because they are part of program specifications.

The some cases where it isn't needed is :

- Using let to create a temporary copy of an existing expression.
  This is especially useful in macros, and the let without type allows
  for having generic macros with temporary copies without something
  like GCC's typeof most (if not all?) of the time.

- When the type is clearly apparent, like in

  let buf = BufferedInputStream(...); 

no need for
 
  let BufferedInputStream buf = BufferedInputStream(...); 

On the contrary, when you do:

  let x:Int;
  
  if(rand())
    x = 0;
  else
    x = 1;

Then I find it clearer to indicate the type.  Imagine you had written:
  
  let x;
 
  if(rand())
    x = 0;
  else
    x = 'toto';
  
What should the type be?  Most languages would report it as an error,
but you could have wanted to write:

  let x:Int|Symbol;
 
  if(rand())
    x = 0;
  else
    x = 'toto';

and then it would be correct.  In general, declaring the type of the
let makes code a lot clearer when dealing with type coercion.

Moreover, if there is a lot of code between your variable declaration
and first affectation, then it would be a pain to find the type of
your variable.

Finally, it encourage always initialising variables, unless it is
especially not needed; which is good (to my taste) programming style.



L really begins to be fine; before long some parts of the compiler
will be written in L.

Matthieu





reply via email to

[Prev in Thread] Current Thread [Next in Thread]