|
Site highlights
LUKS - Linux Unified Key Setup
New Methods In Hard Disk Encryption - Close encounter with storage encryption
Ego - introducing myself
Recent Blog Lines
|
New Liskell SiteLiskell has a new home. Please go to http://liskell.org. Liskell - the languageLiskell is a new syntax frontend for Haskell. Next to its syntax in the form of symbolic expressions — which is also known as Lisp — Liskell also features an extended meta-programming facility. Its aim is to get the best of both worlds: being pure and functional with type inference in the tradition of Haskell, while providing the simplicity and uniformity in its syntax that is necessary for meta-programming. A language definition for Liskell has been submitted to ILC07 (April 07, Cambridge). The preliminary draft for this paper can be obtained from ILC07-Liskell-draft.pdf Liskell - the implementationLiskell is available from this darcs repository: Liskell is a regular GHC branch and builds identical to a regular GHC branch:darcs get --partial http://clemens.endorphin.org/ghc-liskell cd ghc-liskell chmod +x darcs-all ./darcs-all get autoreconf ./configure echo "GhcLibHcOpts = -O -fgenerics -fvia-c" >> mk/build.mk makeTo get the Liskell testsuite go into your ghc-liskell directory: darcs get --partial http://clemens.endorphin.org/testsuite-liskell cd testsuite-liskell make cd tests/liskell make stage=2 fastThe GHC Developer Wiki is a very valuable resource for GHC Hacking. Useful direct links are:
A 15 minute tour of LiskellThe following section gives a short tour of the Liskell syntax forms. You will learn how to define functions, define algebraic data types (ADTs), and use pattern matching do destructure ADTs. Canonical hello world
The Parse TreeThe Liskell parser can produce six kinds of parse tree elements. A parse tree element might have different meanings in different contexts. For instance, the uppercase symbol Shape might be a data constructor, the name of a type or the name of a module. Its meaning is determined by the surrounding context. The most important contexts are expressions, patterns, types and top-level declarations. But before we talk about them, we have a look of what parse tree elements their syntax forms might be built of:
ExpressionsEvery parse tree element from the list above can be part of an expression. Everything except symbols and lists are self-evaluating. ''self-evaluating'' means an integer parse tree element evaluates to its integer value, a string parse tree element evaluates to a list of characters representing that string and so on. Verify that we are using three integers in the example, all returning themselves when evaluated. This is different — for example — for n, the argument to the function fact. n is a symbol, and all symbols evaluate to the value bound by the variable in the lexical scope. Within the functions body of fact, n is bound to the functions first argument. The symbol fact -- interpreted as variable -- returns the function object for fact which we readily use in a function application to the expression (- n 1). Lists in expressions represent function application. The following is a list of symbols (- n 1) and when evaluated as expression it is read as a function application of the subtraction function bound to the - symbol to the expressions n and 1. In a few special cases, lists do not represent function application. All these special list forms are differentiated from function applications by having a special symbol as its first element. The conditional statement (if ..) is an example for that. It is a list and therefore it would be a function application to a function called if. But it is not because if is in the list of specials for expressions. In this particular case, it clearly is a language construct for conditionals. Notice that if is not colored blue in the parse tree, as the symbol if is not evaluated as an expression. Other special are lambda, case, let and a few more and they can be seen as language primitives for expressions. The Liskell paper specifies them and their function. Top-level formsEvery well formed Liskell file starts with
a defmodule top level declaration. It declares the
modules name, its exported symbols and its imported
symbol. Our example
(defmodule FactHello (main) ())
declares a module named FactHello that exports
its main symbol and imports nothing. Instead of the
export list (main), we could use the wildcard
symbol _. In this particular position, the wildcard
is interpreted as ''export-everything''.
The other two declarations
At the top-level, only lists with a special list head are allowed, such as define, defmodule, defdata, defclass, .... The Liskell paper lists all of them. Algebraic Data TypesAs example for another top-level declaration, we define a new algebraic data type. Theoretically speaking, algebraic data types are sum types of product types. If you are coming from Lisp, the data type ''list'' is a good example. To delay the discussion of type constructors, we are focusing on a specific type of list, namely the list of integers, named IList. INil should stand for an empty integer list, and ICons for a cons cell in an integer list.
(defdata IList
This top-level declaration defines the IList type
and its two data constructors. Data constructors can be used
within expressions to construct objects of the associated
type. To actually construct an IList, we can either
use INil without any arguments, or we can
call ICons with two arguments of the
types Integer and IList, as for instance
in the expression:
(INil) (ICons Integer IList))
(ICons 1 (ICons 2 INil))
To simplify things a bit, we define another type but this time, with only one constructor. It represents a point in three dimensional space.
(defdata Point3DType
Once an ADT is created, it is an opaque object. It is not
like a C struct, where one is free to inspect and modify its
values from the outside. To access the components used when
constructing the data object, we must first disassemble it
via pattern matching. Pattern matching can be seen as the
reverse process to data constructors. But before we explain
pattern matching, we have to explain patterns.
(Point3D Rational Rational Rational)) PatternsBefore we talk about patterns, let us talk about matching or unification in general. A unification algorithm answer the questions, whether two expressions can be made equal by substituting specific parts of these expressions. The parts that can be substituted are meta-variables and in the following examples they are typeset in italic:
Pattern matching in Liskell is the process of matching a pattern containing meta-variables against a given object. Patterns matching is even more restricted than matching, as you are only allowed to use what constitutes the pattern syntax. Before we describe the valid forms of pattern syntax, we again give an example to get a brief idea. Assume you have an object that is constructed by the Liskell expression (Point3D 1 2 3) and you match it against the pattern (Point3D x y z), then it is natural to get x=1, y=2, z=3 as a result of the matching process. In Liskell patterns, all lowercase symbols are automatically interpreted as meta-variables -- parts available for substitution. The result of the a matching process is a substitution set, as in the third column in the example table. But Liskell takes a more convenient approach instead, and turns the meta-variables into real variables, with their content being their respective matching part. So after the matching of the given example, the lexical environment is extended by the variables x, y, z bound to 1, 2, 3 respectively. As before matching can also fail. Function arguments are patterns. You see that by looking at the coloring of the arguments of functions so far. The function (fact n) contained the pattern n, which is pretty unexciting as it neither does any destructuring, nor can fail to match, because it is a primitive variable that can be substituted for every arbitrary object. To provide a more complicated example assume we use the (Point3D a b c) pattern in the argument list. The object in the function application supplied in the position of that argument, is then matched against this pattern, and when matched successfully, the variables a, b, c are bound to the matched parts. Let us write a useful function that takes
a Point3D object representing a point in
three dimension space and returns the distance from
the origin.
(define (distance3D (Point3D x y z))
An example invocation of this function could look like:
(sqrt (+ (^ x 2) (+ (^ y 2) (^ z 2)))))
(distance3D (Point3D 10 20 10))
The symbol Point3D refers to a constructor
in this case and is part of a function application.
After all of these rash examples, we step back and examine the parse tree elements with respect to their meaning in the context of a pattern:
Function and Variable BindingsBinders are a minor syntax form that is used in top-level binders and in the lexically scoped binder let. Liskell knows two syntax forms for binders: E-binders, and ET/TE-funbinders. The latter is a subclass of E-binders, namely when the binding is a function binder. For function binders, type annotation is possible. But except for this remark we won't treat ET/TE-funbinders here. An E-binder consists of two elements: the binding, describing the object(s) to be bound and an expression, evaluated to obtain the values for the object(s) to be bound. A simple example of that is the top-level binding of a constant.
(define main (print (fact 12)))
The binding part of this E-binder is main, a symbol, the expression main is bound to is (print (fact 12)). This is the most simple case, binding a primitive variable. To bind functions, we could use another special we have not used so far, namely lambda. It is also a special in the syntax of expressions. The left side below demonstrates the way of introducing a binding for a function with the help of lambda while the right one is a function binder.
Before we talk about pattern binders, we introduce a special form for expressions, namely let, that allows us to create lexically scoped bindings for the context of a single expression. As maintained, the E-binder contains two elements: a binding and an expression. For let these parts are explicitly wrapped in a two-element list: (binding expr). To be able to introduce more bindings at once, let takes a list of E-binders: ((binding1 expr1) (binding2 expr2) ... (bindingn exprn)). Here is a concrete example for illustration:
(let ((one 1)
We are also able to introduce lexically scoped
functions. We just use the binding form for
functions. Let us define the helper
function (twice n) as (+ n n).
(two 2)) (* (+ two two) (+ one one)))
(let ((one 1)
(two 2) ((twice n) (+ n n))) (* (twice two) (twice one))) Pattern bindingsWe can extend the E-Binder syntax to be able to do pattern matching. The reason is that a single symbol has the same meaning as a pattern as well as a function binder, namely they bind a primitive variable. For lists, we can syntactically tell apart function binders from pattern binders. Function binders are not allowed to contain an uppercase symbol as the first element of a list, because functions must have lower case names. Pattern binders are not allowed to contain lowercase names, because the must start with an uppercase constructor. So, we can easily separate these two cases, and for the case where the overlap, their implications are identical. Let's define a small helper function that
implements scalar multiplication of that point with a
given factor s
(define (scalar-multiply s point)
In this example let contains two bindings, both written as list, but
the first is a pattern binder and the second is a function of one
argument. The difference comes from the case of its first symbol.
(let (((Point3D x y z) point) ((p x) (* s x))) (Point3D (p x) (p y) (p z)))) Recommended ReadingsThe Liskell paper contains more information about
Liskell. You might also want to inspect the Haskell 98 Language
report, providing the basis for many of Liskell's
semantics.
Meta-programmingTo be written. It will most likely feature the implementation of backquoting, defmacro and Prolog. Please check in a few weeks. In the meantime, please enjoy this little preview: Flash Video, 99mb, mplayer likes it: direct, torrent (preferred) Feedback about Liskell?Of course you can always hit the feedback button on the top right of this page. For more causal chatting, I am usually available on FreeNode in #haskell. My nickname is therp. Please check my idle time. |
Credit DisclaimerLiskell is based on Haskell. Its semantics are derived from Haskell and the Haskell 98 Language Report. It does not try to be a language in its own right. Liskell is implemented in GHC on top of the Haskell facilities. GHC is an excellent piece of software and consists of over 100.000 lines of code (LOC). In contrast, Liskell is about 3.000 LOC. I argue that these 3.000 lines enables the programmer to use Haskell in new flexible and unanticipated ways. Still, I need to emphasize that Liskell rests on a foundation much larger than itself. Please bear that in mind when you have not seen Haskell, and read the introduction below — Haskell programmers will immediately recognize the Haskell facilities anyway. Having that said, I will use ''Liskell'' solely in the tutorial below for consistency, despite the fact that sometimes the feature under discussion is actually a Haskell feature. |