LX Frequently Asked Questions
Christophe de Dinechin
Version 1.10 (updated 2001/10/25 03:27:45)
>>>>>>> 1.9
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:
procedure Sort(in out array A) template<class T, int N> void Sort(T A[N]);
module BANK_ACCOUNT body is procedure Add(Client c) is ... procedure Remove(Client c) is ... #include "bank_account.h" void BANK_ACCOUNT::Add(const Client &c) { ... } void BANK_ACCOUNT::Remove(const Client &c) { ... }
-- Adding debug code for anything procedure Debug(any thing O) is Write "unknown object" procedure Debug(any integer X) is Write X
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.
The compiler is under development. You can access it through the Mozart CVS tree. You can refer to the status page for details.
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).
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)
The short answer is: yes. Functional programming is defined by three essential characteristics.
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.
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.