LX Frequently Asked Questions

Christophe de Dinechin
Version 1.10 (updated 2001/10/25 03:27:45) >>>>>>> 1.9


Why are you doing that?
What is LX good for?
Why would I want to use it? Why does it look like Pascal?

See this page for an in-depth discussion of these questions.

LX is too verbose. Why so many keywords?

This comment is very frequent. But in reality, LX has about half the number of keywords of C++ for the same expressive power. LX is also quite often shorter than C or C++, in particular for non-trivial pieces of code. The following table shows this on a typical piece of code, and this matches experience porting more significant pieces of code.
// 12 lines, 261 characters
Item *FindItem(Item *first, Item *last, const char *name) {
	Item *ptr;
	for (ptr = first; ptr; ptr = ptr->next) {
		if (!strcmp(ptr->name, name))
			return ptr;
		if (ptr == last)
			break;
	}
	if (ptr == last && ptr->next)
		return ptr->next;
	return NULL;
}
-- 9 lines, 241 characters
function FindItem(Item first, last; string name) return Item is
	with Item ptr
	for ptr in first..last loop
		if ptr.name = name then
			return ptr
		exit if ptr = last
	if ptr = last and ptr.next <> NULL then
		return ptr.next
	return NULL

Besides the problem of dealing with an unfamiliar syntax, the real issue people express with these comments, I believe, are related to the cases where the LX syntax is significantly more verbose, in particular when this syntax happens to be often used in C and C++. This is essentially true for complex object declarations (functions, arrays, pointers), which are in general more verbose in LX than in C or C++.

The justification for introducing keywords is that the C and C++ syntax is ambiguous. This is well known and largely documented. In C and C++, which are closed languages, this is acceptable (although in C++, it quite often causes obscure programming errors). In LX, which is designed for extensions, this is not acceptable. The declaration syntax in LX allows the compiler to know what it is parsing, even if something like array A[1..10] of integer has been completely defined by a library. Also note that the notation is largely under control of the user: array A[10,integer] is also acceptable.

In practice, this verbosity is not a problem, for reasons that depend on the types being discussed:

Is LX strongly typed or not?

Yes, LX is strongly typed. However, it also has a root type. All objects derive from record [Note: considering to name the root type thing]. All dynamic objects derive from object (which itself derives from record). Secondly, the any keyword allows the creation of entities with dynamic type. An entity declared as any object will receive any dynamic object. An entity declared as any record [any thing] will receive any object, dynamic or stack-based.
-- Adding debug code for anything
procedure Debug(any thing O) is
	Write "unknown object"
procedure Debug(any integer X) is
	Write X

Won't expression reduction be confusing?
What happens if you have both A+B, A*B and A+B*C?
How do you change operator priorities?

My experience so far did not allow me to find a case where expression reduction was confusing. It did allow me to find multiple cases where it improved significantly over C++ operator overloading.

Expression reduction selects the largest possible expression. If two candidates are ambiguous, the compiler warns you and picks one. You select the one you want using parentheses.

You cannot change operator priority. A+B*C is always parsed as A+(B*C). Of course, you can redefine a function that computes (A+B)*C for the above expression, but that wouldn't be smart. Named operators all share the lowest priority.

Where is the compiler? How advanced is it?

The compiler is under development. You can access it through the Mozart CVS tree. You can refer to the status page for details.

Why doesn't LX have 'classes'?

The only types defined by keywords in LX are the type type, and the procedure / function types. In addition to this, there is a root built-in type that the compiler knows about (object) and a few types that the compiler uses for built-in constants (integer, real, string, character.) All these types are otherwise not different than normal types to the compiler. In particular, integer is not a keyword, it is a type defined in the implicitly imported module LX.BUILT_IN. This module also defines a few generic types, such as array or pointer (don't know yet about list, knowing that string is actually a dynamic vector of objects, with a default type of character.)

Regarding class, I have not made a decision yet.

So the reason class doesn't appear is because I did not make my mind yet, but it's likely to be a "library implementation detail" ultimately, although an important one. What matter is that the current syntax supports using class without requiring a modification of the compiler or language. The convenience I see in having a class type defined by the library is to automatically enable short-cut notations for invokation of "class member functions" (like a.f(), which implicitly passes a as an argument to function f).

Why no private keyword? Information hiding is important!

Information hiding is important. But hiding the information is the best way to practice it :-) In LX, private implementation details are really hidden, they are not in the source code. In C++ or Ada, by contrast, they are not hidden but forbidden (using a private keyword). The LX approach allows you to use all the scoping rules to perform information hiding.

For instance, the following is a C++ class. Many developers would walk away from a class that looks like this, assuming incorrectly that it can store only a few books, and saying "that's stupid". This shows that our knowledge (or what we think we know) of the internal details of the implementation influences if and how we use the class.

class Library
{
public:
  void AddBook(Book *bk);
private:
  Book *books[32];
  int numbooks;
};

In contrast, the same interface in LX would look like the following. No private information leaks out. The implementation is free to intelligently hash book entries alphabetically without having users making incorrect assumptions on the behavior of the class.

type Library
procedure AddBook(in out Library L; in Book b)

Does LX support functional programming?

The short answer is: yes. Functional programming is defined by three essential characteristics.

  1. The value of a function depends only on its arguments
  2. Objects are immutable. Rather than modifying objects, one creates new objects.
  3. Functions are first class objects: they can be used in expressions, passed as arguments, part of a data structure, and function literal can be created ("lambda functions")

LX does not always respect the first rule, and is therefore not a pure functional language. Most functional languages are not different in that respect: as long as you allow a user to enter data, the output of the function that reads user input doesn't depend solely on its inputs. However, in LX, the {functional} pragma can be used to indicate that a given function has no side effects and that its result only depends on the input arguments. In this case, the compiler is allowed for instance to optimize f(1)+f(1) into 2 * f(1).

The second aspect is not enforced by LX: LX does have pointers. This is necessary to create data structures which are difficult to create with languages that respect the rule, such as circular lists or cyclic graphs. On the other hand, it is possible to write LX code that completely respects the rule (this would be very difficult in C or C++)

LX does qualify for the last aspect. Functions as objects are a necessity for convenient use of reflection. See for instance how the tasking example uses functional bodies for the task objects. LX also has support for other properties often associated with functional programming, such as lists, garbage collection and weak typing.

On the other hand, the internal representation of a function as an object (for reflection) is distinct from the representation of a compiled function (a code pointer), essentially for performance reasons. The upside is that the representation can contain additional information (such as the structure of the generated code or optimization annotations.) The downside is that the structure of A+B is not A-then-plus-then-B (matching the source code as it would in Lisp), but rather a tree with + at the root and name leafs. Thus, Lisp hackers will often find the LX way a bit clumsy.

However, it is important to understand that the functional approach is often not the most efficient one in LX. For instance, closures and lambda functions are often conveniently replaced with generic code and functors in LX.

It's the fragile base class problem, not the weak base class!

The two problem are subtly different, if related. It appears that Daveed Vandevoorde and I may have created the terminology and I was not aware of it. This is why I used it as if it was standard terminology.

Object-oriented languages with weak typing such as Objective-C or Smalltalk don't generally have the weak base class problem. LX solves the problem while preserving (not mandating) strong typing. LX also helps solving the fragile base class problem by allowing better encapsulation, but it doesn't totally eliminate it.