Kompottkins Weisheiten

Link: Concur, ein Client-Webframework für Haskell, das nicht FRP, aber FRP-nah ist und vom Paradigma her etwas Neuartiges ausprobiert. 

Linkliste: Spieleprogrammierung in Haskell

  • Ecstasy, ein ECS
  • Apecs, ein weiteres ECS. Performance-orientiert und imperativ. Sieht sehr überzeugend aus.
  • Sequoia, ein Spiele-FRP, das auf FRPNow! basiert
  • Bones, Skelettanimation für Spriter-Sprites

Link: POSIX.1-2017/SUSv4-2018 zu Befehlszeilenargumentkonventionen.

Link: Emacs Anywhere, eine Sammlung von Shortcuts, um von überall her ein Emacsfenster zu holen und das Ergebnis von dort wieder zurückzukopieren. 

Rust async/await Example

As I wasn't able to find a minimal, working code sample demonstrating the use of the brand-new async/await feature in Rust, I've written one myself. Here it is.

cargo.toml:

[package]
name = "asynchello"
version = "0.1.0"
authors = [ "code@mulk.eu" ]

[dependencies.futures-core]
git = "https://github.com/rust-lang-nursery/futures-rs"
branch = "0.3"

[dependencies.futures-executor]
git = "https://github.com/rust-lang-nursery/futures-rs"
branch = "0.3"

asynchello.rs:

#![feature(await_macro, async_await, futures_api, pin)]

extern crate futures_core;
extern crate futures_executor;

use std::boxed::PinBox;
use std::task::Executor;
use std::future::Future;
use std::future::FutureObj;

async fn compute_name() -> String {
    "world".into()
}

async fn say_hello() -> () {
    let name = await!(compute_name());
    println!("Hello {}.", name);
}

fn make_task<F>(f: F) -> FutureObj<'static, ()>
where
    F: 'static + Future<Output = ()> + Send
{
    let future = PinBox::new(f);
    FutureObj::new(future)
}

fn main() {
    let task = make_task(say_hello());

    println!("Running.");

    let mut pool = futures_executor::LocalPool::new();
    let mut executor = pool.executor();
    executor.spawn_obj(task).expect("oops, failed the task");
    pool.run(&mut executor);
}

Update 2018-07-16: Updated for latest nightly.

Rancher OS mit Cloud-Init konfigurieren

Derzeit läuft mein Kubernetescluster in einem Satz von VMs, auf denen Rancher OS läuft. Rancher OS hat die angenehme Eigenschaft, daß es sich mit Cloud-Init einstellen läßt, so daß die gesamte Konfiguration außerhalb der VM in einer Datei aufbewahrt werden kann.

Cloud-Init zu verwenden, um Rancher OS zu konfigurieren, ist nicht schwer. Zuerst machen wir uns einen Ordner mit dem richtigen Namen (nämlich cloud-init/openstack/latest) und erstellen darin eine Datei namens meta_data.json:

/vm/kubeia-master-2$ cat cloud-init/openstack/latest/meta_data.json
{
         "dsmode": "local",
         "network_config": {},
         "hostname": "kubeia-master-2"
}

Dann können wir die Datei mit der eigentlichen Konfiguration anlegen.  Sie ist im YAML-Format und heißt user_data:

/vm/kubeia-master-2$ cat cloud-init/openstack/latest/user_data
#cloud-config
rancher:
   resize_device: /dev/vda
   system_docker:
     storage_driver: overlay2
   docker:
     storage_driver: overlay2
   network:
     dns:
       nameservers:
         - 10.1.1.1
         - 2a01:4f8:222:244::1:1:1
       search:
         - local
     interfaces:
       eth0:
         addresses:
           - 10.1.1.20/22
           - 2a01:4f8:222:244::1:1:20/110
         gateway: 10.1.1.1
         gateway_ipv6: 2a01:4f8:222:244::1:1:1
         dhcp: false
   services:
     user-volumes:
       volumes:
         - /home:/home
         - /opt:/opt
         - /var/lib/kubelet:/var/lib/kubelet
         - /var/lib/etcd:/var/lib/etcd
         - /etc/kubernetes:/etc/kubernetes
   sysctl:
     vm.max_map_count: 262144
 ssh_authorized_keys:
   - ssh-ed25519 ... mulk@ilmenblatt
   - ssh-ed25519 ... rancher@kubeia-master-2

Die Verzeichnisstruktur mit den beiden Dateien packen wir ein ISO-Abbild mit dem Namen „config-2“. genisoimage eignet sich dafür gut:

/vm/kubeia-master-2$ genisoimage -o cloud-init.iso -V config-2 -r -J cloud-init

In unserer VM binden wir die ISO-Datei als CD-Laufwerk ein (hier als Beispiel in Form einer Konfigurationsdatei für vm-bhyve):

/vm/kubeia-master-2$ cat kubeia-master-2.conf

# ... some detail omitted ...

disk0_type="virtio-blk"
disk0_name="disk0"
disk0_dev="sparse-zvol"
disk1_type="ahci-cd"
disk1_name="/vm/kubeia-master-2/cloud-init.iso"
disk1_dev="custom"

# ... some detail omitted ...

loader="grub"
grub_install0="linux /boot/vmlinuz-4.9.80-rancher rancheros-v1.3.0 console=ttyS0 rancher.autologin=ttyS0 printk.devkmsg=on panic=10"
grub_install1="initrd /boot/initrd-v1.3.0"
grub_run0="linux /boot/vmlinuz-4.9.80-rancher rancheros-v1.3.0 printk.devkmsg=on console=ttyS0 rancher.autologin=ttyS0 rancher.state.dev=LABEL=RANCHER_STATE rancher.state.wait panic=10 "
grub_run1="initrd /boot/initrd-v1.3.0"

Und das sollte es tun.

Continuation-Based Fibers for Scala

Since monadic composition is kind of ugly in Scala, I have created a library implementing coroutines based on delimited continuations as provided by the CPS transformation plugin for the Scala compiler. The library is available on git.benkard.de and GitHub.

Example use:

import monix.eval.Task
import monix.execution.Scheduler.Implicits.global
import monix.reactive.Observable

import eu.mulk.fibers.Fiber._

import scala.util.Success

val slowBackgroundTask = Task.delay(100)

def produceNumbers: Unit @fiber[Int] = {
  val Success(x) = await(slowBackgroundTask)
  emit(x)
  emit(x*2)
  emit(x*3)
}

val observable: Observable[Int] = run(produceNumbers)
observable.foreachL(println).runAsync  // => 100, 200, 300

Auf der Suche nach Blogtexteditorkomponenten

Unter den Verbesserungen, die ich neulich in MulkCMS eingebaut habe, ist auch ein Rich-Text-Wysiwyg-Editor. Nun produziert Trumbowyg, die Editorkomponente, den ich dafür ausgewählt habe, leider kein hinreichend gutes HTML, so daß ich weiter nach Alternativen suche. Ich habe vor allem mit <pre>-Bereichen meine Probleme. Der Editor macht mir die regelmäßig kaputt.

Kennt jemand ein gutes Editorskript, das ich in mein Blog einbauen könnte?

Die wesentlichen Kriterien sind:

  • muß <pre>-Tags auf naivstmögliche Weise unterstützen, d.h. Zeilenumbrüche bleiben Zeilenumbrüche, Einrückung bleibt Einrückung, et cetera
  • muß händisch gesetzte HTML-Attribute (wie z.B. hinzugefügte class-Einträge) in Frieden lassen
  • muß <code>-Tags benutzen – ich möchte bitte keine expliziten Stil- und Schriftangaben in meinem HTML-Code haben!
  • muß mir die Möglichkeit geben, selbst am HTML Hand anzulegen, weil ich z.B. HTML-Attribute hinzufügen oder den Feed fixen können möchte, wenn er kaputtgegangen ist
  • darf mein bestehendes HTML nicht wesentlich ändern
  • sollte eine Textarea mit HTML füllen (damit ich faul sein kann und nichts an meinem eigenen Code anpassen muß)
  • sollte möglichst gültiges generisches XML erzeugen (d.h. es wäre nett, wenn es Entities immer numerisch machen würde und nicht mit den nur in HTML gültigen Namen, weil es mir nämlich sonst meinen Atom-Feed zerhaut)

Update 10.6.2018: Ich habe MulkCMS auf WYMeditor umgestellt. Der scheint soweit sehr gut zu sein.

Haskell: Machines in ST

I recently tried to define a machine in the sense of Edward Kmett's Machines library with access to an efficient, mutable array for tracking its state. In order to make use of this kind of array in Haskell, you need to be in the ST monad, so I looked for a way to let a machine run in ST, and preferably without exposing this implementation detail in the machine's signature.

I didn't manage to do it. Perhaps somebody can help me with some wisdom here—I may have gotten something trivial wrong. Or it might just be that my thinking is flawed and it's just not possible. Here's the code I've got so far:

{-# LANGUAGE UnicodeSyntax, MagicHash, Unsafe, UnboxedTuples #-}

-- | Given an initial ST state tag and a 'MachineT' that runs in
-- 'ST', produces a pure 'Machine'.
execST ∷ ∀ s k o. State# s → MachineT (ST s) k o → Machine k o
execST s m = MachineT $ do
    let ST st = runMachineT m
        (# s', stp #) = st s
    case stp of
      Stop →
        return Stop
      Yield o m' →
        return $ Yield o (execST s' m')
      Await f k q →
      return $ Await (execST s' . f) k (execST s' q)

-- | Given a 'MachineT' that runs in 'ST', produces a pure 'Machine'.
execST' ∷ ∀ k o. (∀ s. MachineT (ST s) k o) → Machine k o
execST' m = runST $ ST $ (\s → (# s, execST s m #))

The result of some quick testing was that a machine I ran under execST' would reach its first yield step and then stop.

Repositories of my source code

I have finally taken the time to collect most of the source code I have published in recent years in a single place. This place is a CGit installation, and you can find it at https://git.benkard.de/.

Erweiterungen von MulkCMS

Heute habe ich nach langer Zeit einmal wieder am MulkCMS-Code herumgehackt. MulkCMS ist die Webanwendung, auf der dieses Blog aufbaut.

Die Verbesserungen sind:

  • Ich kann jetzt Artikel ohne Titel schreiben. Da nicht jeder Kommentar, den ich abgebe, einen Titel braucht, macht das realistischer, daß ich auch einmal kleinere Einträge in meinem Blog mache.
  • Der Artikeleditor ist jetzt nicht mehr nur ein Textfeld, in das ich rohes HTML eintippen muß, sondern ein grafischer Editor, der auf dem HTML-Attribut contenteditable basiert. Das macht es mir leichter, von Mobilgeräten aus zu bloggen.

Zu einer Änderung, die ich noch gerne machen würde, bin ich noch nicht gekommen, und zwar, das Erstellen neuer Artikel leichter zu machen. Derzeit muß ich für jeden neuen Artikel die Artikelnummer noch händisch eintragen. Das ist etwas nervig, werde ich aber auch noch irgendwann weggepatcht kriegen.

Insgesamt bin ich fast ein wenig überrascht davon, wie anpaßbar MulkCMS ist und wie verständlich der Code – und das, obwohl ich jetzt schon einige Jahre nichts Größeres mehr mit Common Lisp gemacht habe.

Man kann sagen, was man will, aber Common-Lisp-Code altert in aller Regel wirklich gut.

Souveräne Geldpolitik

Fefe zitiert (Hervorhebung von mir):

Donald Trump erläutert seine Wirtschaftspolitik:
Late last week, Trump said that as president, he would “borrow, knowing that if the economy crashed, you could make a deal.” He added, “And if the economy was good, it was good. So, therefore, you can't lose.” […] In an attempt to clarify his stance regarding potential default on national debt payments, Republican presidential candidate Donald Trump stated that, in the US, “you never have to default because you print the money.”

Zu gern würde ich jetzt einen unserer GröKaZ-Vertreter fragen, wie sie dieses Argument entkräften würden.

So gern sich unsere Medien nämlich über Trump lustigmachen, hat er an dieser Stelle völlig recht. Ein Staat mit souveräner Geldpolitik, das heißt mit einer Notenbank, die unter seiner Kontrolle steht (was auf die amerikanische Fed zutrifft, aber – törichterweise! – nicht z.B. auf die Zentralbank der europäischen Währungsunion), kann nicht zahlungsunfähig werden. Es gibt auch keinen vernünftigen Grund, sich zu weigern, davon, wenn nötig, gebrauch zu machen, und zum Beispiel so einen Unfug wie eine „schwarze Null“ anzustreben oder sich mit einer „Schuldenbremse“ künstlich selbst handlungsunfähig zu machen.

h2o-Webserver mit SELinux-Policy

Ich verwende seit inzwischen doch geraumer Zeit h2o als Reverse-Proxy auf einem meiner Server. Da ich es immer aus dem Git-Depot kompiliere, habe ich ein bißchen Sorge, daß die Version, die ich laufen lasse, gravierende Sicherheitslücken haben könnte. Deshalb habe ich mir eine SELinux-Policy für h2o zusammengeschustert, die etwaige Gefahren ein wenig eindämmen soll. Teile davon habe ich handgestrickt, andere mittels audit2allow semiautomatisch generiert und kurz überflogen. Ich kann allerdings nicht garantieren, daß die Policy nicht irgendwelche großen Angriffstüren sperrangelweit offen läßt. Insofern würde ich mich sehr freuen, wenn ihr mir Feedback zu ihr geben könntet. Ihr könnt aber auch einfach töricht sein und sie ungeprüft übernehmen. In diesem Fall wünsche ich schon mal viel Glück.

### h2o.te ###

policy_module(h2o, 1.0.0)
require {
  type urandom_device_t;
  type unconfined_t;
  type fs_t;
  type bin_t;
  type usr_t;
  type proc_t;
  type shell_exec_t;
  type http_port_t;
  type http_cache_port_t;
}

type h2o_t;
type h2o_exec_t;
init_daemon_domain(h2o_t, h2o_exec_t)

type h2o_log_t;
logging_log_file(h2o_log_t)

type h2o_var_run_t;
files_pid_file(h2o_var_run_t)

type h2o_share_t;
allow unconfined_t h2o_share_t:file { setattr read write open relabelto getattr unlink };
allow unconfined_t h2o_share_t:dir { search setattr read write open relabelto getattr unlink };
allow h2o_share_t fs_t:filesystem associate;

allow h2o_t self:capability { setgid setuid kill };
allow h2o_t self:process { getpgid setrlimit getsession signal };
allow h2o_t self:fifo_file rw_fifo_file_perms;
allow h2o_t self:unix_stream_socket create_stream_socket_perms;

allow h2o_t cert_t:file { open read getattr ioctl lock };
allow h2o_t h2o_share_t:file { execute_no_trans execute open read getattr ioctl lock };

manage_dirs_pattern(h2o_t, h2o_log_t, h2o_log_t)
manage_files_pattern(h2o_t, h2o_log_t, h2o_log_t)
manage_lnk_files_pattern(h2o_t, h2o_log_t, h2o_log_t)
logging_log_filetrans(h2o_t, h2o_log_t, { dir file lnk_file })

manage_dirs_pattern(h2o_t, h2o_var_run_t, h2o_var_run_t)
manage_files_pattern(h2o_t, h2o_var_run_t, h2o_var_run_t)
manage_lnk_files_pattern(h2o_t, h2o_var_run_t, h2o_var_run_t)
files_pid_filetrans(h2o_t, h2o_var_run_t, { dir file lnk_file })

domain_use_interactive_fds(h2o_t)

files_read_etc_files(h2o_t)

auth_use_nsswitch(h2o_t)

miscfiles_read_localization(h2o_t)

sysnet_dns_name_resolve(h2o_t)

allow h2o_t fs_t:filesystem getattr;
allow h2o_t usr_t:file execute;
allow h2o_t bin_t:file { execute execute_no_trans };
allow h2o_t proc_t:file { read getattr open };
allow h2o_t shell_exec_t:file { execute execute_no_trans };
allow h2o_t urandom_device_t:chr_file { getattr open ioctl read };
allow h2o_t http_port_t:tcp_socket name_bind;
allow h2o_t http_port_t:tcp_socket name_connect;
allow h2o_t self:capability { dac_override net_bind_service };
allow h2o_t self:tcp_socket { accept listen };
allow h2o_t tmp_t:dir { write rmdir setattr read remove_name create add_name };
allow h2o_t tmp_t:file { write create unlink open };
allow h2o_t http_cache_port_t:tcp_socket name_connect;
allow h2o_t tmp_t:sock_file { create write unlink setattr };
allow h2o_t self:unix_stream_socket connectto;
allow h2o_t self:capability chown;
### h2o.fc ###

/usr/local/stow/h2o/bin/h2o             --      gen_context(system_u:object_r:h2o_exec_t,s0)

/usr/local/stow/h2o/share/h2o/.*                gen_context(system_u:object_r:h2o_share_t,s0)

/var/log/h2o(/.*)?                              gen_context(system_u:object_r:h2o_log_t,s0)

/var/run/h2o(/.*)?                              gen_context(system_u:object_r:h2o_var_run_t,s0)

Rechtspopulisten auf dem Vormarsch – kein Wunder

Da reiben sich jetzt die Vertreter der Regierungsparteien die Augen und sind ratlos. Wie konnte hier in Deutschland, wo doch jeder hier ganz besonders gut weiß, welche Schrecken von rechts ausgehen, eine rechtspopulistische Partei so viele Wähler für sich begeistern?

Nun ja, liebe Sozial-, Christ- und Gründemokraten, wenn man jahrelang Politik gegen den Großteil der Bevölkerung macht und zugleich die daraus resultierenden Probleme auf das Ausland schiebt – ob in Form von den „faulen Griechen, die uns auf der Tasche liegen“ oder in Form von einem mystischen „Kuchen“, der zwischen In- und Ausländern aufzuteilen wäre, als wäre die Volkswirtschaft etwas Statisches, das nicht mit der Bevölkerung wachsen könnte, dann braucht man sich nicht zu wundern, daß sich die Rechten die Hände reiben und das alles als Steilvorlage nutzen. Jetzt zu jammern, daß die Wähler auf „einfache Lösungen“ hereinfallen, die gar nicht funktionieren, ist ja wohl mehr als scheinheilig, wenn man selbst die Wählerschaft ständig mit komplexen Lösungen hinters Licht führt, die genausowenig funktionieren. Was denn nun: Soll der Wähler den Lösungen der Politik vertrauen oder nicht? Soll er über viele Jahre tatsächliche Verschlechterung akzeptieren in der Hoffnung auf die versprochene bessere Zukunft, oder soll er nicht? Und wenn er das soll, wo soll das Vertrauen denn bitteschön herkommen? Aus dem durchschlagenden Erfolg der konservativen Wirtschaftspolitik, die solche beachtenswerten Errungenschaften vorzuweisen hat wie Armutsquoten von 20%, Wachstumsraten von satten 1,6%, negativen Realeinkommensentwicklungen, steigenden Arbeitszeiten und die schrittweise Vernichtung der öffentlichen Infrastruktur und Daseinsvorsorge zugunsten von Privatprofiten?

Zugleich verteufelt ihr alles und jeden, der von links her versucht, die Situation zu verbessern, ohne auf ausländerfeindliche Parolen zu setzen. Ihr legt proeuropäischen Sozialisten wie Syriza und Konsorten so viele Steine in den Weg, daß sie handlungsunfähig sind und den Wählern gar nichts anderes übrigbleibt, als auf die antieuropäische Rechte auszuweichen, die im Fall der Fälle ja wenigstens handlungsfähig wäre (indem sie nämlich einfach die ganzen Verträge einseitig aufkündigt oder sich nicht an sie hält). Was sollen die Wähler denn eurer Meinung nach tun?

Schauen wir doch den Tatsachen ins Auge, liebe Verbündete auf Zeit aus der Mitte: Es geht den Menschen in diesem Land schlechter als ihr wahrnehmen wollt. Ändert etwas daran! Und kommt bitte nicht wieder mit denselben Bullshitausreden ala Sachzwang und Staatsverschuldung. Bei Hesinde, ihr seid die Regierung! Wenn ihr wollt, dann könnt ihr auch. Tut es einfach!

Supporting the Software Freedom Conservancy

Become a Conservancy Supporter!

Die Eurokrise: Analyse und Vorschau. Warum Wachstum ohne Schulden ein Märchen ist und Syriza recht hat

Ich möchte hier zum besseren Verständnis besonders der Eurokrise einen kurzen Überblick über die ökonomischen Zusammenhänge geben, die die Eurokrise verursacht haben und weiter am Kochen halten und am Ende auch meine persönliche Sicht darlegen.

Kurzfassung

  1. Der Sparkurs ist ein Irrweg. Daß mit ihm der griechische Staatshaushalt saniert werden kann, ist ein Irrtum.
  2. Die europäischen Institutionen haben nicht verstanden, wie eine Währungsunion funktioniert. Wenn Deutschland weitermacht wie bisher, scheitert der Euro.
  3. Wenn der Euro scheitert, landet Deutschland in einer tiefen Rezession mit hoher Arbeitslosigkeit und einem unberechenbaren Loch im Staatshaushalt.

Wettbewerbsfähigkeit und Außenhandelsbilanzen

Export und Import, Außenhandelsbilanzüberschüsse und -defizite

Wenn Länder am Weltmarkt teilnehmen, dann exportieren und importieren sie dort Güter. Ist der Wert des Exports höher als der des Imports, wird also mehr exportiert als importiert, so erzielt die Volkswirtschaft insgesamt einen Überschuß, d.h. das Ausland macht Schulden bei der Volkswirtschaft. Wird umgekehrt mehr importiert als exportiert, so erzielt sie entsprechend ein Defizit und macht ihrerseits Schulden.

Wettbewerbsfähigkeit

Untereinander stehen Länder insofern in einem Wettbewerbsverhältnis. Länder, die gerne einen Überschuß erzielen möchten, müssen sich gegen andere Länder beim Export durchsetzen und dürfen zugleich nicht zu viel importieren. Dafür gibt es verschiedene Mechanismen, von denen allerdings nur eine in der heutigen liberalisierten Weltwirtschaft eine wesentliche Rolle spielt, nämlich den Preis der Waren. Ist auf dem Weltmarkt das Preisniveau einer Volkswirtschaft A niedriger als dasjenige einer Volkswirtschaft B, so hat es A sowohl leichter, seine Waren zu exportieren als auch besteht ein Anreiz für die Teilnehmer der Volkswirtschaft A, bei sich einzukaufen anstatt aus B zu importieren. In diesem Fall sagt man, A sei wettbewerbsfähiger als B oder habe einen Wettbewerbsvorteil gegenüber B.

Preisniveaus, Inflation

Da verschiedene Länder verschiedene Währungen haben können, müssen wir zwischen dem Weltmarktpreis (den wir in einer beliebigen Währung angeben können, z.B. U.S.-Dollar) und dem nominalen Preis in der jeweiligen nationalen Währung unterscheiden.

Betrachten wir zunächst eine einzelne Volkswirtschaft und die in ihr bestehenden nominalen Preise. Das Preisniveau wird praktisch ausschließlich von den Löhnen im Verhältnis zur Produktivität, den sogenannten Lohnstückkosten, bestimmt, da die Löhne, die in die Herstellung and Anbietung eines Produkts fließen, im wesentlichen seinen Preis bestimmen. Steigt die Produktivität um, sagen wir, 2% und die Löhne um 5%, so ergibt sich eine Erhöhung des Preisniveaus von ungefähr 3% (bei solchen relativ kleinen Werten, die nicht weit auseinanderliegen, kommt man auf einen ganz guten Näherungswert, indem man einfach Lohnrunde minus Produktivitätserhöhung gleich Preisniveauanstieg rechnet -- das nennen wir die goldene Lohnregel). Einen Anstieg des Preisniveaus nennen wir Inflation. Das Gegenteil, also eine Situation sinkender Preise, wäre Deflation.

Auf- und Abwertungen

Haben Länder verschiedene Währungen und sind die Wechselkurse zwischen den Währungen flexibel, dann kann eine Währung gegenüber allen anderen seinen Wechselkurs anpassen. Wird eine Währung in ausländischer Währung gerechnet teurer (d.h. eine Einheit kostet mehr Einheiten anderer Währungen als vorher), so nennt man das eine Aufwertung. Der umgekehrte Fall ist eine Abwertung.

Eine Aufwertung der nationalen Währung hat zur Folge, daß die Produkte des Landes auf dem Weltmarkt teurer und dafür die Produkte des Auslandes im Inland billiger werden (da z.B. mehr Dollar für dieselben Produkte der Volkswirtschaft A auf dem Weltmarkt bezahlt werden müssen als vorher, obwohl der nominale Preis in der nationalen Währung gleich geblieben ist). Sie bedeutet also eine Schwächung der Wettbewerbsfähigkeit und bedingt ein Verrutschen der Außenhandelsbilanz nach unten. Umgekehrt führt eine Abwertung zu verbesserter Wettbewerbsfähigkeit.

Hat ein Land mit nationaler Währung hohe Wettbewerbsfähigkeit und erzielt permanent Überschüsse, nennen wir die Währung unterbewertet. Andere Länder verspüren dann den starken Druck, ihre Währungen ihrerseits abzuwerten (z.B. durch Verkauf von Fremdwährung oder durch Ankauf der Nationalwährung durch die Zentralbank) wodurch der Unterschied in der Wettbewerbsfähigkeit ausgeglichen wird.

Binnenmarkt, Binnennachfrage, Investitionen

Eine Investition ist das Aufnehmen von Schulden (bzw. der Abbau von Guthaben) mit der Absicht, später mehr zurückzubekommen als man hergegeben hat.

Investitionen werden in einer Marktwirtschaft von Unternehmen getätigt, wenn die Aussicht auf Profit, d.h. auf hinreichende Nachfrage nach dem angebotenen Produkt, besteht. Auch der Staat kann investieren, üblicherweise mit dem Ziel, die Wirtschaft zu beleben oder ein bestimmtes politisches Ziel wie die Energiewende zu erreichen.

Wird viel investiert, so wächst die Produktion an und Arbeitsplätze entstehen. Mangelt es an Investitionen, so „schrumpft“ die Wirtschaft, d.h. die Produktion geht zurück, und Arbeitsplätze gehen verloren, wodurch die Arbeitslosigkeit steigt. Hält dieser Zustand länger an, so spricht man von einer Rezession.

Staatshaushalt, Saldenmechanik

Die Wirtschaft kann man aus Sicht einer gegebenen Volkswirtschaft in vier Sektoren unterteilen: den Staat, die Unternehmen, die privaten Haushalte und das Ausland.

Die Defizite bzw. Überschüsse der vier Sektoren addiert sich immer zu 0. Daraus ergibt sich logisch zwingend, daß, wenn ein oder mehrere Sektoren einen Überschuß erzielen wollen, z.B. um Wohlstand und Vermögen aufzubauen oder um Schulden zurückzuzahlen, einer der Sektoren ein Defizit aufweisen muß.

Traditionell ist der sich verschuldende Sektor der Unternehmenssektor. In anderen Worten kommen aus dem Unternehmenssektor im Normalfall die Investitionen, die die Wirtschaft und den Wohlstand wachsen lassen.

Wenn es Effekte gibt, die dazu führen, daß der Unternehmenssektor nicht investitionsbereit ist, dann gibt es, soll die Volkswirtschaft nicht schrumpfen und Arbeitslosigkeit grassieren, nur noch den Staat und das Ausland, von denen die nötigen Schulden kommen können, da die privaten Haushalte grundsätzlich kaum politisch vom Sparen abzubringen sind.

Der Außenhandelsbilanzüberschuß entspricht, wie gesagt, einer Verschuldung des Auslands. In Deutschland verschuldet sich das Ausland jedes Jahr mit 220 Milliarden Euro. Das versetzt den Staat und die Unternehmen in die Lage, zu sparen, ohne daß deswegen die Wirtschaft zum Erliegen kommt. (Das ist es, was Kanzlerin Merkel meint, wenn sie von Wachstum ohne Schulden spricht.)

Währungsunionen

Wenn sich mehrere Volkswirtschaften dazu entschließen, eine gemeinsame Währung einzuführen, nennen wir das eine Währungsunion.

Eine Währungsunion zeichnet sich dadurch aus, daß es keine Wechselkurse gibt. Das bedeutet auch, daß Auf- und Abwertungen innerhalb einer Währungsunion ausgeschlossen sind. Folglich bleiben Unterschiede in der Wettbewerbsfähigkeit, die durch niedrigere Preise (d.h. durch niedrigere Lohnstückkosten) in einem Land entstehen, bestehen und können nicht anderweitig ausgeglichen werden.

Deflationspolitik, schwäbische Hausfrau, „Kaputtsparen“

Dies können Länder zum Anlaß nehmen, innerhalb der Währungsunion einen Vorteil zu erlangen, indem sie ihre Lohnstückkosten senken. Die Wirtschaftspolitik kann das durch verschiedene Maßnahmen forcieren (ein markantes Beispiel ist die Agenda 2010 unter dem damaligen Kanzler Schröder). Eine solche Lohnpolitik nennen wir Deflationspolitik, weil sie auf Deflation abzielt. Die deflationierenden Länder exportieren damit ihre Arbeitslosigkeit: im eigenen Land werden Güter billig produziert und die Exportgewinne ersetzen oder übertreffen gar die Nachfrage aus dem Inland, die durch die niedrigen Löhne wegfällt.

Da es sich bei der Wettbewerbsfähigkeit um ein Nullsummenspiel handelt, steigen in den übrigen Ländern derselben Währungsunion in der Folge die Arbeitslosigkeit und das Außenhandelsbilanzdefizit, weil die Nachfrage nach Gütern der Volkswirtschaft durch steigende Importe und zurückgehende Exporte sinkt. Die schrumpfende Wirtschaft zwingt den Staat, sowohl fürsorgerisch einzugreifen und mehr Geld in die soziale Absicherung zu stecken als auch mit Investitionen die Wirtschaft am Laufen zu halten, da diese vom natürlichen Investor, dem Unternehmenssektor, mangels Nachfrage zunehmend zurückgehen. Letzten Endes ist der Staat also gezwungen, Schulden zu machen.

Fährt der Staat in einer solchen Rezession dagegen die Ausgaben zurück und erhebt höhere Steuern (besonders auf Konsum), um seine Schulden zu verringern, verstärkt das den Effekt der Rezession: Durch die sinkende Kaufkraft der Arbeitnehmer, die sich daraus ergibt, sinkt die Nachfrage weiter und damit wiederum die Investitionen. Die Wirtschaft schrumpft stärker, die Steuereinnahmen gehen zurück, der Staat muß erst recht wieder für das Sozialsystem Geld in die Hand nehmen, und das staatliche Haushaltsdefizit wächst. Der Staat spart sich kaputt: Sein Sparen beschert ihm nur immer größere Schulden.

Die Eurokrise

Status quo

Seit 2010 befindet sich Griechenland unter der Aufsicht der Troika (IWF, EZB, europäische Kommission). Die Troika gewährt dem griechische Staat Kredite unter Auflagen, die darauf abzielen, seine Wettbewerbsfähigkeit zu verbessern und einen staatlichen Haushaltsüberschuß zu erzielen. Die Auflagen laufen auf ein extremes Deflations- und Sparprogramm hinaus.

Die Deflationspolitik trägt Früchte, aber nicht die erhofften: Die Lohnstückkosten sind um über 30% gefallen und liegen inzwischen deutlich unter denen Frankreichs. Zugleich ist das BIP stark eingebrochen und die Arbeitslosigkeit auf grob 25% gestiegen. Entgegen anders lautender Meldungen gibt und gab es nie Anzeichen einer Erholung.

Was passiert, wenn wir so weitermachen wie bisher?

Wenn wir so weitermachen wie bisher, d.h. wenn Deutschland stur dabei bleibt, auf Schulden des Auslands zu setzen, dann wird zunächst die Währungsunion aufgrund der nicht zu haltenden Situation der immer mehr zunehmenden Verschuldung der anderen Euroländer zusammenbrechen. Denn machen wir uns nichts vor: Griechenland ist lediglich die Spitze des Eisbergs. Die gesamte Eurozone befindet sich seit sechs Jahren in der Rezession, ohne Aussicht auf wesentliche Besserung.

Wenn aber die Währungsunion zerbricht, dann steht Deutschland mit seiner neuen Währung (egal, wie diese dann heißt) im wesentlichen allein da. Diese Währung wird gegenüber den restlichen Euroländern massiv aufwerten. Selbst, wenn wir ignorieren, daß die Schulden des Auslands voraussichtlich dann nach und nach platzen werden und damit viel Vermögen in Deutschland vernichtet werden wird, wird die Aufwertung in einer Situation, in der in Deutschland der Binnenmarkt aufgrund niedriger Löhne schwächelt und die Unternehmen gewohnt sind zu sparen statt zu investieren, zu einer wirtschaftlichen Katastrophe führen. Die Wirtschaft wird aufgrund des Einbruchs der Nachfrage schrumpfen, Arbeitslosigkeit wird grassieren, und der Staat wird in großem Stil Schulden aufnehmen müssen, um die tiefe Rezession zu dämpfen (Schuldenbremse hin oder her – die Neuverschuldung kommt zwangsweise entweder durch höhere Ausgaben oder durch wegbrechende Einnahmen zustande).

Wie ist die Katastrophe zu verhindern?

Das einzige, was die Eurokrise langfristig beenden kann, ist eine Rückbesinnung auf die Frage, was eine Währungsunion ist und was sie bedeutet. Eine Währungsunion funktioniert genau dann, wenn die Preisniveaus der teilnehmenden Länder nicht auseinanderlaufen, d.h. wenn überall die gleiche Inflationsrate besteht. Das bedeutet, daß darauf geachtet werden muß, daß niemand langfristig über, aber auch niemand langfristig unter seinen Verhältnissen lebt.

Im Moment gilt es zuallererst, das gescheiterte Sparprogramm für Griechenland zu beenden und zu einem Investitionsprogramm überzugehen, das diesen Namen auch verdient – die bisherigen Angebote der Troika sind in dieser Hinsicht vollkommen lächerlich. Der griechische Staat hat genug Deflationspolitik betrieben. Jetzt muß er den nötigen Spielraum erhalten, die Wirtschaft anzukurbeln, indem er für den Moment weiter Schulden macht. Das bedeutet im Klartext, daß das Hilfsprogramm erweitert werden muß, bis die Wirtschaft sich erholt hat, und zwar ohne Sparauflagen. Wenn die Troika der Meinung ist, daß der griechische Staat nicht in der Lage sei, selbst für die durchaus ebenfalls noch nötigen Reformen, z.B. den Aufbau einer effektiven Steuerbehörde, zu sorgen, dann darf man sie dabei gern auch mit fachlichem Rat und sonstigen Ressourcen unterstützen.

Zweitens muß Deutschland endlich aufhören, auf seinem Außenhandelsbilanzüberschuß, d.h. auf das permanente Schuldenmachen des Auslands bei Deutschland, zu bestehen. In Deutschland müssen die Löhne kräftig steigen (so etwas wie 5% pro Jahr für die nächsten 10 Jahre wären angemessen – das, was die Gewerkschaften dieses Jahr wieder alle als Erfolg verkaufen, ist angesichts der Situation wirklich lachhaft!). Das würde dem Ausland die Möglichkeit geben, aufzuholen, und den deutschen Arbeitnehmern die Gelegenheit, das, was sie durch harte Arbeit erwirtschaftet haben, endlich auch einmal zu verkonsumieren, anstatt es ans Ausland letztlich zu verschenken (denn was ist ein Kredit, der mangels Einnahmemöglichkeiten vom Schuldner nie zurückgezahlt werden kann, anderes als ein Geschenk?).

An alle Bahnfahrer

Klar, Sie sind alles andere als begeistert darüber, daß der Bahnbetrieb eingeschränkt ist. Das ist auch ganz richtig. Es macht keinen Spaß, Termine absagen zu müssen, nicht zur Arbeit zu kommen oder irgendwo in der Pampa festzusitzen. Sie haben ganz und gar recht damit, sich zu ärgern.

Aber ärgern Sie sich bitte über den Richtigen, nämlich über Ihren Vertragspartner.

Ihr Vertragspartner ist die Deutsche Bahn. Die Lokomotivführer sind nicht dafür verantwortlich, daß der Fahrplan eingehalten wird. Die Bahn ist es. Wenn die Bahn es nicht auf die Reihe bekommt, Personal zu angemessenen Konditionen zu beschäftigen, um den Bahnbetrieb sicherzustellen, dann ist das nicht der Fehler derer, die sich gezwungen sehen, für genau dieses Anliegen zu kämpfen. Ganz im Gegenteil.

Seeking the Pit of Success

Design is about making choices.  When you do design, you narrow the design space down successively until you have found a space narrow enough that the recipient of the design can grasp it and make safe and effective use of it easily.

Whenever you make a choice—and remember, this is the essence of design—you're taking choice away from the recipient.  Hence, good design is about making choices that the recipient benefits from and will therefore not feel motivated to second-guess.  Bad design is anything that isn't good design.  You can't wiggle your way out of it: While there are many different degrees of goodness, refusing to do design at all is generally bad design.  If you leave a choice to the recipient that they don't care about, you haven't lived up to your responsibility as a designer, as you have made it harder for the recipient to use your product effectively, forcing them to make arbitrary choices that can only make things inconsistent and end up in disaster.  You have pushed the recipient into the Pit of Despair.


Quick: Which of the following two APIs for a string→int key--value store do you prefer, and why?

// ------- API #1 -------
class transaction {
  int get(string);
  void put(string, int);
  void del(string);
  void commit();
  //will abort when it goes out of scope unless you commit() first
};
class int_db_1 {
  unique_ptr<transaction> begin();
};
// ------- API #2 -------
class int_db_2 {
  void begin();
  void commit();
  void abort();
  int get(string);
  void put(string, int);
  void del(string);
};

I do not know about you, but even without considering the question of whether int_db_2 does or does not permit the use of get, put, and del outside of a transaction, it is obvious to me that int_db_1 is the better design.  Why?  Because int_db_2 is a pit-of-despair API.  It forces you deal with choices that you don't really care about, and which you will invariably get wrong at some point.  For example, you can easily fall into the trap of doing something like this:

void bump_access_count() {
  // Assume that db is declared in some kind of enclosing scope
  // (e.g. it is a field in the class that this is a method of).
  db.begin();
  int old_access_count = db.get("access_count");
  if (user_not_yet_seen(current_user)) {
    db.put("access_count", 1+old_access_count);
    remember_user(current_user);
  }
  db.commit();
}

Consider what happens when remember_user throws an exception.  Will this commit the transaction?  No.  (So far, all is well.)  Will it abort the transaction?  No.  So most likely, the DB object is now in a problematic state.  It might still hold a connection to some backend database service and hog resources that way.  It might fail to start a new transaction when you call begin the next time.  In any case, you have introduced a bug—and not only did you introduce a bug, you did it by being extra careful and using transactions (which is supposed to be the right thing)!  Because if you hadn't been using transactions in the first place, this wouldn't be a problem.  Maybe you'll avoid transactions next time.  And suddenly you'll get weird behavior because multiple processes are accessing the same database and seeing inconsistent data.  You have fallen into the Pit of Despair.

int_db_1, on the other hand, doesn't let any of this happen.  It won't let you think about whether to use transactions or not. It simply forces you to do so.  It won't let you neglect (or refuse) to handle exceptional cases. It simply forces you to either commit or abort (by aborting automatically if you don't commit).  There is no way to mess up accidentally.  And if the author of the transaction class has taken care to prevent copying and moving transaction objects, you can't even realistically cheat your way out.  Sure, you can make a wrapper and pass it around, or you can use low-level memory manipulation tricks to get at the internals of a transaction object and mess with them, but if you do any of that, you have explicitly taken responsibility for the disaster such a kluge might cause.  Surely, if you try, you can break it.  But you can't accidentally make a mess.  You have been pushed right into the Pit of Success.

This is what design is about.  When you do design, be it a UI or an API or anything else that requires making choices, make them.  Don't let the user make a mess.  If the user accidentally misuses your API, it's not because they are stupid.  It's because your design just isn't good enough.  Take the opportunity to learn from the user's mistakes and improve your design.  Not only will it make your users happy, it will also inform your future design.  A Pit of Success if ever there was one.

Derivation rules are kind of like fractions.

Derivation rules kind of look like fractions, don't they? Let's try applying our usual calculation rules for fractions and see what we get.

× =

Hmm. If we interpret A B to mean A∧B, that makes sense. By multiplying two rules, we get another valid rule, for if we know that A→B and C→D, then clearly A∧C→B∧D.

Let's try addition.

+ =

Now, this one is less obvious. Let us interpret A+B to mean A∨B. Can we deduce (A∧D)∨(B∧C)→(B∧D) from A→B and C→D? Indeed we can:

So derivation rules do, in fact, behave a bit like fractions, and by applying some rules from high-school algebra, we can create new, valid rules from given derivations. Weird stuff. 😉

ICFP 2012 poster presentation: Type Checking Without Types

My 10-minute talk on a full-featured functional programming language with a purely subtyping-based type system is viewable online under the catchy title of Type Checking Without Types. The system is designed with the aim of functionally modelling relational data in both the object and type spaces in mind.

After the talk, a commenter referred to Hutchins' work on pure subtype systems, which explores a related idea in a rather different manner and with a different focus. If the topic seems interesting to you, you might want to take a look.

ECL and C++

It might not be obvious, but not only does ECL enable the embedding of C code in Lisp code, it supports C++ as well. The following works, for instance (assuming your ECL was compiled --with-cxx):

(in-package #:cl-user)

(ffi:clines "#include <sstream>")
(ffi:clines "#include <iostream>")
(ffi:clines "#include <cstring>")
(ffi:clines "#include <gc/gc_cpp.h>")

(defun c++hex (x)
  (ffi:c-inline (x) (:int) :cstring "{
    std::ostringstream out;
    out << std::hex << #0;
    @(return) = new (GC) char[out.str().size() + 1];
    strcpy(@(return), out.str().c_str());
  }")))

Then, at the REPL:

CL-USER> (c++hex 123456)
"1e240"

I think that's pretty nifty.

Dataflow and Reactive Programming in C++

What is Dataflow Programming?

You may have heard of lazy evaluation. It works like this: You specify the meaning of an expression not by putting a value into a box, but by declaratively writing down a way of computing the expression. Whenever someone asks for the value of an expression, it is computed on-demand and cached. Thus, we have a dependency graph between expressions (that is hopefully acyclic). Since we compute only what someone actually asks for, control flows from the consumer to the producer, up the dependency chain. The data is passive, waiting for the consumer to ask for things to be computed; the consumer actively initiates all computation. This is the pull-model of declarative computation.

Rather less widespread, there is a technique that is dual to lazy evaluation. As in lazy evaluation, you specify the meaning of something by writing down how to compute it from other things. But now, you do not wait for someone to ask for the value. Instead, you compute the value right away; and whenever one of the inputs changes, you immediately recompute the value. Thus, control flows from the producer to the consumer, down the dependency chain. The data is active, reacting to events from the outside world as soon as they happen; the consumer sits waiting passively and gets notified whenever something changes. This is the push-model of declarative computation. It is sometimes called the spreadsheet model, since it operates like a spreadsheet: You have a bunch of cells, and whenever you modify a cell, the change immediately propagates to all dependent cells, prompting visual adjustments as well.

But reactive patterns naturally arise not just in spreadsheets, but also in multimedia programming. Take a game object, a person, say, consisting of various parts, such as legs, arms, and a head. You may want to draw each part independently from the others, but you always know the relative locations of the parts with regard to the object. Whenever you move the object, you want the parts to move accordingly. Hence, while specifying something like arm.x := person.x - 10 is very natural, you expect arm.x to update itself whenever person.x changes. In fact, the multimedia-oriented scripting language JavaFX provides just such a kind of limited dataflow mechanism.

There have been various implementations of the general idea. Most academic research has been done under the moniker of Functional Reactive Programming. Notable implementations are Racket's FrTime and the Elm programming language, among others. But there have also been implementations focussing on the dataflow paradigm itself. The Oz programming language is one of the best-known. Another interesting and very mature implementation is Kenny Tilton's Cells library, which extends the Common Lisp Object System and has sparked a number of ports to other languages.

Implementation in C++

For my own immediate use, however, I needed something C++-based. Now, I have not put the time into this endeavor necessary to optimize and debug it; but I have got an apparently working implementation in C++11 that seems to be passable for my rather low requirements at this time. Get it with Git:

$ git clone https://matthias.benkard.de/code/cells++

If you prefer, you can also pull it from GitHub. It is a header-only library. The distribution contains a small example program that I reproduce below.

Update 2012-12-13: I have updated the example program to illustrate automatic detection and deregistering of destroyed cells.

Hint: You can use smart pointers or a garbage collector to ensure that cell dependencies are never destroyed as long as something depends on them. You need not, however, take into account the reverse relationships (i.e., the push-graph). The library deals with all the necessary deregistering.

// Copyright 2012, Matthias Andreas Benkard.

#include "cells.hpp"

#include <cstdlib>
#include <iostream>

struct unit { };

using namespace cells;
using std::cout;
using std::endl;

typedef formula_cell<double> fcell;
typedef formula_cell<unit>   ucell;

int main(int argc, char** argv) {
  fcell x0, x1, x2, y;
  ucell b;

  {
    fzell z;
    ucell a;

    with_transaction([&](){
      x0.reset(10);
      x1.reset([&](){ return *x0 + 5; });
      x2.reset([&](){ return *x0 * 2; });
      y.reset([&](){ return *x1 * *x2; });
      z.reset([&](){ return *x0 * *y; });

      // a is an observer on z.  b is an observer on x2.
      a.reset([&]() -> unit { cout << "z is now " << *z << "." << endl; return unit(); });
      b.reset([&]() -> unit { cout << "x2 is now " << *x2 << "." << endl; return unit(); });
    });
    // Output:
    // z is now 3000.
    // x2 is now 20.

    x0.reset(15);
    // Output:
    // x2 is now 30.
    // z is now 9000.

    x0.reset(-20);
    // Output:
    // z is now -12000.
    // x2 is now -40.

    y.reset(-3);
    // Output:
    // z is now 60.

    x1.reset([&]() { return *x0; });
    // No output, y (and hence z) does not depend on x1 right now.

    y.reset([&]() { return *x1 + *x2; });
    // Output:
    // z is now 1200.
  }

  x0.reset(10);
  // z and a aren't there anymore, so only b will produce output now:
  // x2 is now 20.

  return EXIT_SUCCESS;
}


/*
 * z
 * |\
 * | \
 * |  \
 * |   \
 * |    \
 * |     \
 * |      \
 * |       y
 * |      /|
 * |     / |
 * |    /  |
 * |   x1  x2
 * |  /    |
 * | /     |
 * x0------+
 */

Discordian Calendar (printable)

Based on the Ek-sen-trik Discordian Holydays list, I have created a PDF rendition of a Discordian calendar in German (PDF) and English (PDF) that you can print out and pin onto your wall.

The code used to create the PDFs is written in Clojure and available under the terms of the AGPLv3+ as discordian-calendar.clj.

Have fun!

node.js Reverse Proxy: Handling Self-Signed SSL Client Certificates

Imagine that your web application wants to accept standard SSL certificate-based authentication, but for one reason or another, you don't want to deal with having to sign client certificates with a CA and telling your web server about it. That is, you want to accept client certificates without them being validated against a list of known CAs. (If you want to support WebID login for your web site, for example, you don't have a choice—you must accept self-signed certificates.) In principle, this is an easy problem: You simply store your users' public keys in your database and check the client's certificates against those for authentication. (WebID works differently, of course, but on the frontend side, the difference doesn't matter.)

Unfortunately, most web server software, like Apache or nginx, assume the presence of a CA that client certificates will be checked against. If you don't supply a CA, all certificates will be rejected and you will have no opportunity to process them.

On the other hand, node.js does permit the use of self-signed certificates, so we can easily build a reverse proxy that passes such certificates on to our web application as HTTP headers. Here's how.

For the skeleton reverse proxy server, refer to my blog post about reverse-proxying HTTP with node.js. Extend the https_opts map in the example code there with the relevant options:

var https_opts = {
  key: fs.readFileSync('/etc/ssl/private/https.key', 'utf8'),
  cert: fs.readFileSync('/etc/ssl/private/https.chain.crt', 'utf8')
  requestCert: { value: true },
  rejectUnauthorized: { value: false }
};

This will cause the reverse proxy to request a client certificate from the user agent as soon as a connection is made. Now extend the request handler to encode the client's public key information into a JSON object and to pass that object on to the backend:

var handleRequest = function (data, next) {
  var peer_cert = req.connection.getPeerCertificate();
  if (peer_cert.valid_to) {
    peer_cert.valid_to = Date.parse(peer_cert.valid_to);
    peer_cert.valid_from = Date.parse(peer_cert.valid_from);
  }
  req.headers['x-mulk-peer-certificate'] = JSON.stringify(peer_cert);
  return next();
};

The object passed in the X-Mulk-Peer-Certificate header will be a map that contains a number of keys, such as (in the case of an RSA key) modulus, exponent, fingerprint, valid_to, valid_from, subject, and subjectaltname. Your backend can use this information to authenticate the user.

Caution: Make absolutely certain that the user will not be able to forge the X-Mulk-Peer-Certificate header! Any connection not established through our specially crafted reverse proxy must unset any such header passed by the user agent, or your security will be completely broken. (Unless you have the frontend authenticate with your backend in some way. Passing a shared secret from the frontend to the backend through an HTTP header is a good way of achieving that.)

A Programmable Reverse Proxy Using node.js

Most of the time, well-supported Web server software like Apache or nginx provides the path of least resistance to achieving security and reliability while still offering plenty of flexibility in organizing your Web site. Sometimes, however, you need that bit of extra flexibility that you can only get by implementing your own server software.

Now, you could do that with Java, Ruby, Common Lisp, or what-have-you, but any one of these feels like shooting birds with a cannon. If you want your little server program to look a bit more like a configuration file, node-http-proxy, an HTTP reverse-proxying module for node.js, is an option worth considering.

A simple HTTPS frontend for your HTTP-only server could be implemented like this:

var fs = require('fs'),
    http = require('http'),
    https = require('https'),
    httpProxy = require('http-proxy');


var https_opts = {
  key: fs.readFileSync('/etc/ssl/private/https.key', 'utf8'),
  cert: fs.readFileSync('/etc/ssl/private/https.chain.crt', 'utf8')
};

var proxy = new httpProxy.RoutingProxy({
  enable: {
    xforward: true
  },
  router: {
    'hub.benkard.de': '127.0.0.1:3001',
    '': '127.0.0.1:80'
  }
});

var handleRequest = function (data, next) {
  // Do something interesting here, like manipulating
  // `data.req` in some way or handing the request
  // over to some other handler.
  return next();
};

var server = https.createServer(https_opts, function (req, res) {
  return handleRequest({req: req, res: res},
                       function() {
                         proxy.proxyRequest(req, res);
                       });
});
server.listen(443, '::');
server.listen(443, '0.0.0.0');
server.on('upgrade', function (req, socket, head) {
  return handleRequest({req: req, socket: socket, head: head},
                       function() {
                         proxy.proxyWebSocketRequest(req, socket, head);
                       });
});

Any kind of interesting logic may be put inside the handleRequest function. The above piece of code is already useful in itself, though: Since WebSocket connections are handled separately from the rest, you can bypass your normal reverse proxy when you need to handle one. Since nginx, among others, is yet to add support for WebSocket, you can use this to connect your WebSocket-ready backend to the outside world without messing up the rest of your server setup.

Enumerating the Cartesian Product of Two Enumerable Sets

From the recursion-theory-is-fun dept.

Consider a language with support for lazy sequences (or streams). You get two streams, xs and ys, and you are asked to produce the cartesian product (cartesian xs ys) of the two streams. To be precise, the requirement is that the result is a stream that enumerates each pair [x y] where x is produced by xs and y is produced by ys.

Natural Numbers are Easy, Right?

Let us first try this in the case that xs and ys are each an enumeration of the natural numbers, starting from 0. Then (cartesian xs ys) is simply an enumeration of the set of pairs of natural numbers. It is easy to see that the naive implementation does not yield the correct result:

(def nat (iterate inc 0))

(def natXnat
  (for [x nat
        y nat]
    [x y]))

(take 10 natXnat)
  ;=> ([0 0] [0 1] [0 2] [0 3] [0 4] [0 5] [0 6] [0 7] [0 8] [0 9])

What has happened here? Sure enough, we do get a stream of distinct elements of (cartesian xs ys)—but somehow, the stream fails to enumerate any elements with a first component different from 0.

The reason is that a naive sequence comprehension like the above will simply traverse the right-hand side sequence for each element of the left-hand side sequence. Since the right-hand side sequence never terminates, the left-hand side sequence is never advanced.

Surely, we can do better. Let us plot the set we want to enumerate and try to come up with an enumeration sequence.

(Source: Wikimedia Commons)

The idea is rather simple. In the first step, we generate one element with x = 0. Next, we generate one element with x = 0 and one with x = 1. Next, we generate one element with x = 0, one with x = 1, one with x = 2. And so on.

Therefore, what we effectively have to do is enumerate the diagonals as illustrated in the picture above. We do this by starting at one end of a diagonal, and in each successive step, decrement the one component of the pair while incrementing the other. In more graphical terms, we take successively larger pairs of prefices (representing the parts of the axes that each diagonal encompasses) of the stream of natural numbers and traverse them in parallel, in opposing order.

For our natural number example, we can thus write the enumeration down as follows.

(def natXnat
  (letfn [(iter [a b]
            (lazy-seq
             (cons [a b]
                   (if (zero? b)
                     (iter 0       (inc a))
                     (iter (inc a) (dec b))))))]
    (iter 0 0)))

(take 10 natXnat)
  ;=> ([0 0] [0 1] [1 0] [0 2] [1 1] [2 0] [0 3] [1 2] [2 1] [3 0])

Much better!

(Note: You can use the inverse Cantor pairing function to generate the same sequence in a less recursive way. Also, see Christoph's post on countable sets for a more mathematical treatment.)

Generalizing the Method

Having done all that, how hard is it to generalize the procedure to arbitrary sets? It turns out that it is, while tricky in technical terms, quite easy to do the adaptation:

(defn cartesian [xs ys]
  (letfn [(reverse-prefices [items]
            (reductions conj nil items))]
    (let [xpres (reverse-prefices xs)
          ypres (map reverse (reverse-prefices ys))]
      (mapcat (fn [xprefix yprefix]
                (map vector xprefix yprefix))
              xpres
              ypres))))

This version of cartesian does essentially the same thing as the natural-number-specific one above in that it successively generates larger and larger subsequences and traverses them in parallel, one of them in natural order, the other in reverse.

Dealing with the Finite Case

If we only care about infinite sequences, we are done at this point. If we need to accomodate finite sequences as well, some technicalities arise, like having to skip pairs where one of the components would ordinarily be outside the range of one of the input sequences. The resulting definition looks a lot scarier than the one above, though it really does the same thing:

(defn cartesian [xs ys]
  (let [skip (gensym)]
    (letfn [(reverse-prefices [items]
              (reductions conj nil items))
            (pad [items]
              (lazy-cat items (repeat skip)))
            (skip? [x]
              (identical? skip x))]
      (let [xpres (rest (reverse-prefices (pad xs)))
            ypres (map reverse (rest (reverse-prefices (pad ys))))]
        (apply concat
               (take-while          ;deal with the finite case
                seq
                (map (fn [xprefix yprefix]
                       (filter (fn [pair] (not (some skip? pair)))
                               (map vector xprefix yprefix)))
                     xpres
                     ypres)))))))

It is an easy generalization to make the cartesian function accept an arbitrary (finite) number of input sequences. This is left as an exercise to the reader.

MulkyID, an IMAP-based BrowserID Primary

Since password-based authentication based on credentials specific to each web site you sign up with is both a hassle and a security liability, a couple of alternative web authentication mechanisms have emerged. Notable examples include TLS client certificates, OpenID, and OAuth. Since none of these mechanisms have found widespread use as a replacement for username/password authentication, Mozilla have been developing a protocol called BrowserID, which combines a decentralized authentication system à la OpenID with the email-based login procedure users are familiar with.

While the BrowserID infrastructure is still undergoing active development, the API is stable and ready to use, and a relying party is very easy to implement. On signup, users are faced with a familiar-looking, password-based authentication dialog based on JavaScript.

While BrowserID works without special support from the email provider (in which case the Mozilla-operated browserid.org website will do the authentication), such support is necessary for full decentralization. In particular, if you are your own email provider, you may want to set up your own authentication service, which in this case, Mozilla call a primary.

Therefore, I have implemented a basic BrowserID primary called MulkyID, which piggybacks on an existing IMAP server for authentication, as a set of simple CGI scripts ready for deployment.

Note that this is alpha-quality software, so expect some rough edges. Contributions are, as always, welcome. Since the code base is quite small, I encourage you to read the code in order to get a feel for what it does and possibly spot any remaining bugs.

Adding BrowserID Support to a Clojure-based Web App is Ridiculously Easy

This week, I added BrowserID login support to my personal, Clojure-based family web hub (code is available as a Git repository at https://matthias.benkard.de/code/benki.git). I was quite astonished to see how easy deploying BrowserID is, especially as compared to OpenID or OAuth. In particular, the deployment does not depend on any BrowserID-specific libraries; any sufficiently featureful HTTP client library will do.

This article describes a moderately naïve implementation of BrowserID on top of an existing user session infrastructure. In particular, it does not deal with live-updating pages after login via JavaScript; instead, it simply reloads the current page when the user has completed the login procedure. (Adding live login capability should be easy enough by simply editing the client-side JavaScript login and logout handlers not to do a full page refresh.)

The client-side JavaScript code can be based on the official tutorial, like the following:


// -*- js-indent-level: 2 -*-

jQuery(function($) {  
  var loggedIn = function(res) {
    console.log(res);
    if (res.returnURI) {
      window.location.assign(res.returnURI);
    } else {
      window.location.reload(true);
    }
  };
  var loggedOut = function(res) {
  };

  var gotAssertion = function(assertion) {
    // got an assertion, now send it up to the server for verification
    if (assertion) {
      $.ajax({
        type: 'POST',
        url: '/login/browserid/verify',
        data: { assertion: assertion },
        success: function(res, status, xhr) {
          if (res === null) {
            loggedOut();
          }
          else {
            loggedIn(res);
          }
        },
        error: function(res, status, xhr) {
          alert("Whoops, I failed to authenticate you! " + res.responseText);
        }
      });
    } else {
      loggedOut();
    }
  }

  $('#browserid').click(function() {
    navigator.id.get(gotAssertion, {allowPersistent: true});
    return false;
  });

  // Query persistent login.
  var login = $('head').attr('data-logged-in');
  if (login === "false") {
    navigator.id.get(gotAssertion, {silent: true});
  }
});

Put that code into a file somewhere and reference it from a script tag after also loading https://browserid.org/include.js and the jQuery library.

Now you need to program your server to reply to AJAX requests to /login/browserid/verify. Let's say you're using Noir and storing users' email addresses in a database table. In addition, I am assuming that your login session management works by putting a :user key into the session map. In this case, your server-side code might look like this (with https://example.com replaced by your site's URI):

(ns eu.mulk.benki.auth
  (:use [hiccup core page-helpers]
        [noir   core])
  (:require [clojure.java.jdbc       :as sql]
            [com.twinql.clojure.http :as http]
            [noir.response           :as response]
            [noir.session            :as session]))

(defpage [:post "/login/browserid/verify"] {assertion :assertion}
  ;; NB.  We delegate verification to browserid.org.
  ;; Can implement this ourselves sometime if we want.
  (let [reply  (http/post "https://browserid.org/verify"
                          :query {:assertion assertion
                                  :audience "https://example.com"}
                :as :json)
        result (:content reply)
        status (:status result)
        email  (:email  result)]
    (if (= (:status result) "okay")
      (sql/with-connection
        (sql/transaction
          (let [record  (first (query "SELECT * FROM user_email_addresses WHERE email = ?" email))
                user-id (and record (:user record))]
            (if user-id
              ;; I'm assuming that your login page stores the desired return
              ;; URI (i.e., the login page's referrer) using flash-put!.
              ;; If it doesn't, you might want to do something slightly different
              ;; here.
              (let [return-uri (session/flash-get)]
                (session/put! :user user-id)
                (response/json {:email email, :returnURI return-uri}))
              ;; If this is a public site, you might want to create a database
              ;; record for the new user here.  We'll be content in denying
              ;; authorization instead.
              {:status 418,
               :headers {"Content-Type" "text/plain"},
               :body "I couldn't find you in the database."}))))
      {:status 418,
       :headers {"Content-Type" "text/plain"},
       :body "Your BrowserID request could not be validated."})))

And that's basically it. The only thing left is to put a sign-in button (<a href="#" id="browserid">Sign in</a>) somewhere on all the pages that need sign-in capability.

Updating Chromium with Common Lisp

Since the Chromium web browser does still not feature a built-in build update mechanism, and since Mac OS X lacks a comprehensive, high-quality package management system, users need to download and install Chromium updates manually at regular intervals. Even though there are both stand-alone applications and browser add-ons that try to help, most of them have seemed to be broken since the last time Google changed their build archive location.

Therefore, I have written a small Common Lisp program that downloads and unpacks the latest Chromium build for me. It's very simple and could easily be replaced by a Perl script, but it works well enough for me.

Note that the program naively unpacks the ZIP file into the /Applications folder, which results in a separate chrome-mac folder being created.

Source code follows.

(eval-when (:compile-toplevel :load-toplevel :execute)
  (dolist (module (list "drakma" "alexandria"))
    (ql:quickload module)))

(defpackage #:mulk.update-chromium
  (:use #:common-lisp #:alexandria))
(in-package #:mulk.update-chromium)


(defparameter *base-uri*
  "https://commondatastorage.googleapis.com/chromium-browser-continuous")

(defparameter *os-subdir* "Mac")
(defparameter *filename* "chrome-mac.zip")
(defparameter *target-dir* #p"/Applications/")

(defun latest-version ()
  (parse-integer
   (drakma:http-request (format nil "~A/~A/LAST_CHANGE" *base-uri* *os-subdir*))))

(defun file-uri (revision)
  (format nil "~A/~A/~A/~A" *base-uri* *os-subdir* revision *filename*))

(defun main ()
  (let* ((revision (latest-version))
         (uri      (file-uri revision))
         (file     #p"/tmp/chrome-update.zip"))
    (format t "~&Latest revision: ~A" revision)
    (format t "~&Downloading.")
    (unwind-protect
         (progn
           (with-open-file (out file
                                :direction :output
                                :if-exists :supersede
                                :element-type '(unsigned-byte 8))
             (write-sequence (drakma:http-request uri) out))
           (format t "~&Extracting archive contents.")
           (sb-ext:run-program "/usr/bin/unzip"
                               (list "-o" (namestring file)
                                     "-d" (namestring *target-dir*))
                               :wait t))
      (delete-file file))))

Mulkrypt Update

At the suggestion of commenter Rodolfo, I have submitted the Mulkrypt cryptographic library for Racket on PLaneT.

Currently, Mulkrypt implements a sufficient number of advanced cryptographic algorithms (a stream cipher, HMAC, and two hashing functions) to securely send messages over a network, which is the use case it is geared towards. What it lacks is a good block cipher—while the library does contain something that likes to think of itself as an implementation of Threefish, that implementation is apparently buggy. One problem I encountered when writing it was that I was unable to locate sufficient test data, the only set I could test against being the example vectors contained in PySkein.

If you happen to run across other implementations of the Threefish cipher and get around to generating a couple of non-PySkein test vectors, I would be thankful for a comment on this blog post.

Instadump: Semi-automatic disk serialization of Clojure refs

Summary

While large-scale, mission-critical data that needs high-performance access warrants the use of dedicated database systems, it is sometimes useful to have a way of simply dumping the current in-memory state of an application to disk without sacrificing reliability and data integrity. Fortunately, Clojure's STM architecture is well-suited to the task; the only thing it lacks is some way of safely serializing data to disk.

This is where Instadump comes in. You tell it where to put stuff and when to save the current state, and it will manage the rest. No need to explicitly reload the application state the next time your application starts up—Instadump does that automatically (but if you need to, you can explicitly revert to the last saved state at any time).

Data is stored on the disk using Oracle Berkeley DB Java Edition.

Installation

Instadump is available from Clojars.

Leiningen

[instadump "0.0.2"]

Maven

<dependency>
  <groupId>instadump</groupId>
  <artifactId>instadump</artifactId>
  <version>0.0.2</version>
</dependency>

Development Access

If you want to hack Instadump, you can fetch the Git repository using the following command:

$ git clone https://matthias.benkard.de/code/instadump.git

Examples

Example code:

(use 'eu.mulk.instadump)

(setup-instadump! "db")  ;tell Instadump where to put the dump files

(defstate testvar1 150)
(defstate testvar2 "abc")

@testvar1  ;=> 150
@testvar2  ;=> "abc"

(dosync (ref-set testvar1 1000))
(save-all-global-state!)

;; ------
;; Restart the Clojure process...
;; ------

;; Then:

(use 'eu.mulk.instadump)

(setup-instadump! "db")

(defstate testvar1 150)
(defstate testvar2 "abc")

@testvar1  ;=> 1000
@testvar2  ;=> "abc"

API

; -------------------------
; eu.mulk.instadump/defstate
; ([sym default])
; Macro
;   Define a global ref managed by Instadump.  The supplied default
;   value is used if the variable cannot be found in the database.
;   Otherwise, the value stored in the database is used.
; 
; -------------------------
; eu.mulk.instadump/setup-instadump!
; ([dirname])
;   Set up the Instadump database at the file system location indicated
;   by dirname.
; 
; -------------------------
; eu.mulk.instadump/save-all-global-state!
; ([])
;   Direct Instadump to dump a snapshot of all variables created by
;   defstate into the database.  save-all-global-state! runs in an
;   implicit transaction in order to ensure data consistency.
; 
; -------------------------
; eu.mulk.instadump/reload-all-global-state!
; ([])
;   Direct Instadump to revert all variables created by defstate to the
;   state saved by the last invocation of save-all-global-state!.  Will
;   fail if any variables cannot be restored (e.g. if some variables
;   have never been saved before).

Inaction and Neutrality

On Hacker News, user jimmyjim comments on the recent GoDaddy-related outrage,

In the words of Elie Wiesel, "Take sides. Neutrality helps the oppressor. Never the victim. Never the tormented." ... Too often people think inaction implies neutrality. It does not. It's an enabling behavior. Passivity is a free permission slip for the status quo to persist onward.

Immanuel Wallerstein et al. über die Grenzen des Wirtschaftssystems

Bei Kontext TV gibt es ein interessantes Interview mit Immanuel Wallerstein (deutsch, englisch) zu den „Grenzen des Kapitalismus“ und sein absehbares Ende.

Auch sehenswert ist der Beitrag zum „Wachstum bis zum Kollaps“ (deutsch, englisch) mit Vandana Shiva und Tim Jackson.

Mulkrypt: A Library of Cryptographic Algorithms for Racket

Summary

While examining Racket's built-in, continuation-based web application library, I noticed that by default, serialized continuations are sent to the HTTP client both unencrypted and unsigned. After a bit of browsing the manual, I discovered that there is some built-in support for signing serialized data using HMAC-SHA1. However, HMAC-SHA1, while not as insecure as a naive approach using plain SHA1, isn't the cryptographically strongest algorithm around. This encouraged me to write my own little library of cryptographic algorithms.

In addition to HMAC and Whirlpool, which were originally intended to be used as a drop-in replacement for Racket's HMAC-SHA1 function, this library provides implementations of Dan Bernstein's CubeHash hashing function and Salsa20 stream cipher.

Installation

You can require the PLaneT version directly from Racket:

(require (planet mbenkard/mulkrypt))

Development Access

If you want to hack Mulkrypt (there's an apparently incorrect implementation of Threefish in there; if you can fix it, that would be great!), you can fetch the Mercurial repository using the following command:

hg clone http://matthias.benkard.de/code/mulkrypt-for-racket/

(Mind the trailing slash. My web server is overly picky there.)

Implemented Algorithms

  • Cryptographic hashing: Whirlpool, CubeHash
  • Message authentication: HMAC
  • Stream ciphers: Salsa20

Implementation Features

  • No dependencies other than Racket's built-in libraries
  • Pure Racket (no use of C code or foreign libraries)
  • AGPLv3 license
  • Stream ciphers operate on (possibly infinite) sequences, including ports, and output (again, possibly infinite) sequences.

Examples

> (define fox-and-dog #"The quick brown fox jumps over the lazy dog")
> (printf "~x" (cubehash-512x fox-and-dog))
a0e462f1cc35c81f73b6e0aad5f92a20ff5cc25d2bf607ff6db9fa47711db65d
65fd2e1df20857609480968e9189825ddb1f7dd57ac1e0d985e9a27fe5f0a5ec
> (printf "~x" (whirlpool fox-and-dog))
b97de512e91e3828b40d2b0fdce9ceb3c4a71f9bea8d88e75c4fa854df36725f
d2b52eb6544edcacd6f8beddfea403cb55ae31f03ad62a5ef54e42ee82c3fb35
> (printf "~x" (hmac cubehash-256 32 16 #"key" fox-and-dog))
a40cf15be135c55c9977650bf50cc4cf0c85539efc47aeaf29f0ce8696097d55
> (sequence->list (salsa20 #"<<<<<key of 16 or 32 bytes.>>>>>"
                           #"<nonce.>"
                           fox-and-dog))
(122 146 89 152 145 152 42 248 229 225 154 77 160 250 22 247 6 191 4 202 154 
 186 141 181 124 241 166 3 1 133 42 128 25 136 203 216 165 24 195 21 159 96 105)

Purely Functional Integer Maps in C

Some time ago, I wrote an implementation of an optionally functional, bitmapped Patricia Tree as part of my project thesis. The project thesis developed into something quite different from what I had imagined, and I consequently never got around to using any Patricia Trees in the actual code. Since it might be useful in a variety of situations, I hereby release the implementation as a library.

Downloading and Building

You can fetch the Mercurial repository using the following command:

hg clone http://matthias.benkard.de/code/mulklib/

(Mind the trailing slash. My web server is overly picky there.)

Build with something like the following:

make CC=gcc LIB_PREFIX=lib LIB_SUFFIX=.so

The defaults are CC=clang LIB_PREFIX=lib LIB_SUFFIX=.dylib, which is all right for Mac OS X but not so great on other systems. They are also subject to change, since they depend on my development environment. (Of course, if you feel the urge to make the build system a little smarter, patches are always welcome.)

If building and installing a library consisting of a single object file seems like overkill to you (it sure does to me), you may prefer simply integrating the source files (bitmapped_patricia_tree.c, bitmapped_patricia_tree.h) into your own project directly.

Motivation

// 1. Patricia trees are very amenable to structure sharing.
//
// 2. Furthermore, big-endian Patricia trees are especially efficient
//    when indices are allocated sequentially.
//
// 3. Finally, bitmapping improves the performance of copying because
//    copying an array is much cheaper than copying an equivalent branch
//    in a tree.

Usage

Main Concepts

A Bitmapped Patricia Tree (BPT) maps integer keys to void* values.

BPTs provide a functional, Lisp-like interface. All modifying operations (bpt_assoc and bpt_dissoc, which add and remove entries, respectively) return a new BPT with the requested changes applied, although by default, they may destructively modify the original BPT for improved performance.

Destructive modification can be prohibited by sealing the original BPT using the bpt_seal procedure. Note that there is practically no performance overhead in sealing (the seal being just a boolean flag), so there is nothing wrong with using bitmapped Patricia Trees in a purely functional manner by calling bpt_seal after every operation.

An empty BPT is represented as NULL.

Memory Management

Memory management is done through a reference counting scheme inspired by OpenStep conventions. Newly allocated objects are returned with a reference count of 1. In order to increment the reference count, call bpt_retain; in order to decrement it, bpt_release. bpt_dealloc should never be called directly, although it is part of the public interface.

Client programs can have themselves be notified when a leaf node is deallocated by setting a deallocation callback with bpt_set_dealloc_hook. The callback is passed the key and value that the deallocated node represents. This can, among other things, be used to free the memory pointed to by the value (assuming it represents a pointer).

Example

//
// This is a very basic example of using BPTs that blissfully neglects memory
// management in favor of didactic simplicity.
//
// For a more complete example, see bpt_test.c in the source distribution.
//

#include "stdio.h"
#include "stdlib.h"
#include "bitmapped_patricia_tree.h"

void print_tree(bpt_t b) {
  int i;
  for (i = 0; i < 10; i++) {
    if (bpt_has_key(b, i)) {
      printf(" %d -> %s\n", i, bpt_get(b, i));
    }
  }
}

int main() {
  bpt_t b1, b2;

  // Create b1.
  b1 = bpt_assoc(NULL, 0, "zero");     //functional (obviously)
  b1 = bpt_assoc(b1,   1, "one");      //|
  b1 = bpt_assoc(b1,   2, "two");      //|destructive!
  b1 = bpt_assoc(b1,   3, "three");    //|
  b1 = bpt_assoc(b1,   4, "four");     //|

  // Make b1 functional.
  bpt_seal(b1);

  // Make b2 a structure-sharing copy of b1.
  b2 = bpt_assoc(b1,   0, "null");     //functional, as b1 is sealed
  b2 = bpt_assoc(b2,   3, "a triple"); //destructive

  // Modifying b1 does not affect b2.
  b1 = bpt_assoc(b1,   5, "five");     //destructive

  printf("Map 1:\n");
  print_tree(b1);
  printf("\nMap 2:\n");
  print_tree(b2);

  return EXIT_SUCCESS;
}

Output:

Map 1:
 0 -> zero
 1 -> one
 2 -> two
 3 -> three
 4 -> four
 5 -> five

Map 2:
 0 -> null
 1 -> one
 2 -> two
 3 -> a triple
 4 -> four

API

typedef int32_t bpt_key_t;

// Querying
void *bpt_get(bpt_t bpt, bpt_key_t key);
bool bpt_has_key(bpt_t bpt, bpt_key_t key);

// Adding and Removing Entries
bpt_t bpt_assoc(bpt_t bpt, bpt_key_t key, void *item);
bpt_t bpt_dissoc(bpt_t bpt, bpt_key_t key);

// Managing Memory
void bpt_retain(bpt_t bpt);
void bpt_release(bpt_t bpt);
void bpt_dealloc(bpt_t bpt);
void bpt_set_dealloc_hook(bpt_t bpt, bpt_key_t key, void (*hook)(bpt_key_t key, void* value));

// Making Maps Functional
void bpt_seal(bpt_t bpt);

Using a Bounded Prime Sieve Algorithm to Generate Infinite Prime Number Lists

Generators and Streams

Arguably, one of the most elegant ways of representing a recursively enumerable set such as the set of prime numbers in a computer program is as an enumeration, or, as some might call it, a generator. A generator, simply speaking, is an object that repeatedly generates the next value of a set when asked for one by the user.

Generators are a sufficiently useful concept that even mainstream programming languages like Python support them with special syntax:

def squares():
    i = 0
    while True:
        yield i*i
        i += 1

sqs = squares()   # Instantiate generator
print sqs.next()  # => 0
print sqs.next()  # => 1
print sqs.next()  # => 4
print sqs.next()  # => 9

Some less mainstream programming languages, such as Haskell, support the concept of a stream instead, which is basically an infinite list:

squares = [x*x | x <- [0..]]

main = do
  println $ take 4 squares

The main difference between a generator and a stream is that a stream need not be instantiated. Whereas a generator has a state that changes every time the user requests a value, a stream always behaves as the same infinite list. As with all things, this entails both disadvantages and benefits, which I am not going to talk about here. However, one thing that makes streams particularly interesting is that since they are static, an implementation can choose to cache the values that have already been generated, so that subsequent uses of the same stream can simply use the cached values instead of generating them from the start again.

This caching behavior is particularly important when generation is expensive—such as is the case for the set of prime numbers.

Generating Primes

Clearly, it is not particularly hard to generate all prime numbers. Simply provide a primality test and filter the natural numbers:

def is_prime(n):
  for p in primes():
    # Break out of the loop if there is
    # no chance left of finding a factor.
    if p*p > n:
      return True
    # If p is a factor of n, n ain't a
    # prime number.
    if n % p == 0:
      return False

def primes():
  yield 2
  i = 3
  while True:
    if is_prime(i):
      yield i
    i += 1

Pretty simple, right? Note that the primes generator is used in the definition of is_prime. This works because primes is defined to yield the first prime number without calling is_prime at all (otherwise, this would lead to an infinite recursion).

Now, the above code is pretty inefficient since is_prime repeatedly generates the primes up to sqrt(n). This could easily be solved by making primes a stream, so let's try making it one. In order to do this, I shall switch to the Scheme-derived Racket programming language for the rest of the article, since it provides a rich set of features dealing with generators and streams:

;; Some helper definitions.
(define (divides? p n)
  (zero? (remainder n p)))

(define (nats [n 0] [step 1])
  (stream-cons n (nats (+ step n) step)))

;; This is the interesting part.
(define (prime? n)
  (for/and ([p (stop-after primes (λ (p) (>= (* p p) n)))])
    (not (divides? p n))))

(define primes
  (stream-cons 2 (stream-filter prime? (nats 3))))

Again, prime? and primes are mutually recursive, but this time, one and the same stream of primes is reused in every invocation of prime?, making it unnecessary to generate the list of primes up to (sqrt n) every single time.

If the functional style is unfamiliar to you, it would have been quite possible to define primes just as in the Python example above, albeit with a little more syntactic overhead (since we still want to convert the generated sequence into a stream):

(define primes
  (sequence->stream
   (in-generator
    (yield 2)
    (for ([n (in-naturals 3)])
      (when (prime? n)
        (yield n))))))

Measuring Performance

So the above prime generation technique is nice and elegant and everything, but it's also hopelessly inefficient.

> (time (stream-ref primes 20000))
cpu time: 24822 real time: 25316 gc time: 379
224743

Surely we can do better. There are a couple of possible optimizations here. One is to test for primality only those numbers that are +/- 1 mod 6. (Figuring out the reason every prime number greater than 3 has this form is left as an exercise for the reader.) This gives us the following improved primality test:

(define (prime? n)
  (or (= n 2)
      (= n 3)
      (let ([mod6 (modulo n 6)])
        (and (> n 1)
             (or (= mod6 1)
                 (= mod6 5))
             (for/and ([p (stop-after primes (λ (p) (>= (* p p) n)))])
               (not (divides? p n)))))))

This improves performance by a bit, but it still leaves a lot to be desired.

> (time (stream-ref primes 20000))
cpu time: 23223 real time: 23424 gc time: 554
224743

Prime Sieves

The reason for the sub-par performance is that we are still using a trial-division algorithm. A much more efficient way of generating primes is by using a prime sieve algorithm.

A simple prime sieve algorithm works like this (this is known as the Sieve of Eratosthenes): Assume some bound n, below which we need to enumerate all prime numbers. Allocate an array of length n. Initialize the array with ones, marking all numbers greater than 1 as potential prime numbers. Now, for every new prime number that you find (by simply looking at the first number in the list that is not yet marked), mark off all multiples by setting their array cells to zero. You may stop when you have reached sqrt(n).

Since the times of Eratosthenes, mathematicians have discovered more elaborate prime sieve algorithms. Of note in particular is the Sieve of Atkin, which is known to be significantly more efficient than the Sieve of Eratosthenes.

The Problem

Unfortunately, it is not at all clear how to use a traditional prime sieve algorithm to generate an infinite stream of primes. Just like the Sieve of Eratosthenes, modern sieve algorithms assume some upper bound n which primes should be generated below of. The reason is that a sieve works by marking off all multiples of some number, and this marking-off process must end at some point. In addition, some of the efficiency benefits that sieve algorithms provide is the fact that they are able to stop relatively early. For instance, the Sieve of Eratosthenes can terminate as soon as the last prime smaller than sqrt(n) has been processed.

Certainly, you could set some preliminary bound and restart the algorithm whenever the bound is crossed, setting a new bound in the process. There may even be a way of choosing the new bound so as to avoid making the asymptotic runtime significantly worse (there's probably a paper or two in here somewhere); but even so, an approach that recomputes a lot of prime numbers again and again is still dissatisfactory.

The Solution

You would think that there must be some other way—and indeed there is. Let's take a step back for a moment. What is a sieve algorithm actually about? The algorithm steps through the already filtered part of the array, and for each value that it finds, it starts to check off its multiples (or square multiples, or whatever it is the specific algorithm does) up to the end of the array. But wait—why should such a subprocess have to terminate as soon as the end of the array is reached? It might as well sleep until the array becomes larger, at which point it can continue to check off items—after which we can be sure that the enlarged array is also filtered. So if we can somehow make it do that, we have magically solved all of our problems. (Admittedly, this isn't quite true, since the sieve process itself also needs to be suspended whenever it is okay to do so—but that is a negligible issue, since we can just make it stop and continue as well.)

This is indeed feasible. In fact, there are multiple ways of doing something like this. We will take a rather simple and intuitive route: Make every check-off process a coroutine, collect the resulting coroutines into a list, and run them in order every time we resize the array. Each time, the coroutines will stop at the end of the array, waiting for the next iteration to arrive. In pseudo-code,

define sieve coroutine:
  loop:
    for i from 1 to infinity:
      if sieve[i] is 1:
        spawn check-off coroutines for i
      if i >= stopping number (depending on size of array):
        yield to parent
          (and sleep until reentered)

define primes:
  set up sieve
  set coroutines = []
  loop:
    run sieve coroutine,
      saving spawned coroutines into the coroutines list
    run coroutines from coroutines list
    enumerate all primes from sieve
    resize sieve

Implementation in Racket

We implement the above by first defining a facility for creating coroutines:

(define (make-coroutine fn)
  (let ([cont #f]
        [return #f])
    (λ ()
      (let/cc return*
        (set! return return*)
        (if cont
            (cont)
            (fn (λ () (let/cc k
                        (set! cont k)
                        (return)))))))))

Next, we need to implement the main loop. We do this by writing it directly as a stream—though we could also have used a generator, as noted above.

;; Parameters:
;;  sieve-constructior — the function that constructs the sieve coroutine
;;  starting-numbers   — a list of numbers that the sieve does not yield
;;                       but that still need to be in the stream
;;  index->number      — a procedure that maps an array index to the number it represents
;;                       (in the Sieve of Eratosthenes, this is simply the identity function)
(define (sieve->stream sieve-constructor starting-numbers index->number)
  ;; Starting array size: 100'000 items.
  (let* ([sieve      (box (make-vector 100000 #t))]
         [coroutines (box (list))]
         [sieve-coro (sieve-constructor sieve coroutines)])
    (define (iter pos)
      (letrec
          ([len
            (vector-length (unbox sieve))]
           [init
            ;; Run sieve and filter coroutines.
            (λ ()
              (sieve-coro)
              (for ([coro (unbox coroutines)])
                (coro)))]
           [loop
            ;; Yield the generated primes by iterating over the sieve.
            (λ (i)
              (if (< i len)
                  (begin
                    (if (vector-ref (unbox sieve) i)
                        (stream-cons (index->number i)
                                     (loop (add1 i)))
                        (loop (add1 i))))
                  (next-iteration i)))]
           [next-iteration
            ;; Resize array, repeat.
            (λ (newpos)
              (let* ([old-sieve (unbox sieve)]
                     [new-size (inexact->exact
                                (round
                                 (* (vector-length old-sieve)
                                    1.2)))])
                (printf "Resizing prime sieve.  New size: ~A\n" new-size)
                (set-box! sieve (make-vector new-size))
                (vector-fill! (unbox sieve) #t)
                (vector-copy! (unbox sieve) 0 old-sieve)
                (iter newpos)))])
        (init)
        (loop pos)))
    (define (start-iter starting-numbers)
      (match starting-numbers
        [(list)
         (iter 1)]
        [(list* x xs)
         (stream-cons x (start-iter xs))]))
    (start-iter starting-numbers)))

For the sieve algorithm, we can use basically whichever one we want. I have chosen to adapt an algorithm published on StackOverflow.com by one Robert William Hanks (which he says is not his real name). The original version posted on SO is the following:

import numpy
def primesfrom2to(n):
    """ Input n>=6, Returns a array of primes, 2 <= p < n """
    sieve = numpy.ones(n/3 + (n%6==2), dtype=numpy.bool)
    for i in xrange(1,int(n**0.5)/3+1):
        if sieve[i]:
            k=3*i+1|1
            sieve[       k*k/3     ::2*k] = False
            sieve[k*(k-2*(i&1)+4)/3::2*k] = False
    return numpy.r_[2,3,((3*numpy.nonzero(sieve)[0][1:]+1)|1)]

We translate this into a coroutine-based implementation as below. Note that the sieve routine destructively modifies the sieve array. In addition, we use explicit boxes in order to communicate changes in the list of coroutines from the sieve algorithm to the main loop as well as for the array, which is reallocated on every major iteration.

;; A little helper definition.
(define-syntax-rule (while cond body ...)
  (when cond
    (let loop ()
      body ...
      (when cond
        (loop)))))
(define (make-rwh-prime-sieve2 sieve coroutines)
  (make-coroutine
   (λ (yield)
     (let loop ([pos 1])
       (let* ([n (add1 (* (vector-length (unbox sieve)) 3))])
         (for ([i (in-range pos (add1 (quotient (integer-sqrt n) 3)))])
           (set! pos (add1 i))
           (when (vector-ref (unbox sieve) i)
             (let* ([k (bitwise-ior (+ (* 3 i) 1)
                                    1)]
                    [make-filter
                     (λ (starting-pos)
                       (make-coroutine
                        (λ (yield)
                          (let loop ([ii (quotient starting-pos 3)])
                            (while (>= ii (vector-length (unbox sieve)))
                              (yield))
                            (vector-set! (unbox sieve) ii #f)
                            (loop (+ ii (* 2 k)))))))])
               (set-box! coroutines
                         (list* (make-filter (sqr k))
                                (make-filter (* k
                                                (+ k
                                                   (- (* 2 (if (odd? i) 1 0)))
                                                   4)))
                                (unbox coroutines)))))))
       (yield)
       (loop pos)))))

Finally, putting it all together, we finally get a stream of primes:

(define primes
  (sieve->stream make-rwh-prime-sieve2
                 '(2 3)
                 (λ (i)
                   (bitwise-ior (+ (* 3 i) 1) 1))))

What Did We Gain?

So after all this, have we gained anything with regard to performance? Indeed we have:

> (time (stream-ref primes 20000))
cpu time: 387 real time: 391 gc time: 148
224743

In fact, we can now easily generate one order of magnitude more prime numbers than before (note that this does not merely generate the prime numbers up to 100'000—those would be quite a bit fewer!):

> (time (stream-ref primes 100000))
cpu time: 2120 real time: 2322 gc time: 621
1299721

Not too shabby, I'd say, considering that the program has now stored every single prime up to 1'299'721 in a stream of length 100'001. We can verify that this is true by summing over all of them and measuring the time:

> (time (stream-fold + 0 (stream-take primes 100001)))
cpu time: 301 real time: 301 gc time: 0
62261998442

Similarly, generating some more primes reuses the existing stream:

> (time (stream-take (stream-drop primes 100001) 10))
cpu time: 270 real time: 269 gc time: 0
'(1299743
  1299763
  1299791
  1299811
  1299817
  1299821
  1299827
  1299833
  1299841
  1299853)

Conclusion

We can conclude that using prime sieves is an efficient way of enumerating primes. More interestingly, even though they are inherently imperative in nature, heavily leveraging side-effects and mutable arrays, they can be given an elegant functional interface using streams and can, with some work, even be used to enumerate unbounded sequences of prime numbers by making use of filtering coroutines.

Compressed Mail Storage with Dovecot and Procmail

Storing all of your electronic mail on your mail server tends to take a lot of hard drive space, especially if you never delete any messages.

Fortunately, Dovecot can easily be configured to use zlib-based compression. Less fortunately, actually storing mail in a compressed format requires either compressing your mail manually (by the use of a cron job, say) or delivering it through Dovecot's custom local delivery agent (LDA). At first sight, the latter seems to preclude you from using procmail, since the mail transfer agent, which is the usual entity to call procmail, is supposed to hand all mail over to the LDA instead. However, procmail can easily be configured to deliver compressed mail. There are two approaches: having procmail run bzip2 itself (which I shall call the low-level approach), or preferably, piping procmail's output to Dovecot's LDA (the high-level approach).

The high-level approach incidentally enables you to make use of Dovecot's custom mail storage formats. The low-level approach, on the other hand, is independent of Dovecot and can be used in conjunction with any mail server that can handle gzip- or bzip2-compressed files.

High-Level Approach: Using Dovecot's LDA

First, put the following somewhere near the top of your .procmailrc:

LDA=/usr/local/libexec/dovecot/dovecot-lda

Customize the file path as appropriate.

Next, let's say you have a rule like the following:

:0
* ^Subject:.*This is spam.*
$MAILDIR/.Spam/

(If only it was that easy!)

Replace it with the following:

:0
* ^Subject:.*This is spam.*
| $LDA -m Spam

Repeat this for all the rules in the file. You're done!

Low-Level Approach: Using bzip2 from procmail

This approach assumes you're storing your mail in a Maildir.

First, we need the ability to generate Maildir file names. Put the following somewhere near the top of your .procmailrc:

GENID='echo -n `date +%s`; echo -n _R; hexdump -e "/4 \"%u\"" -n 16 /dev/urandom'

The above is for FreeBSD; other operating systems may use some slightly different command syntax. The only thing we need to care about is that $GENID is a shell command that generates a hopefully unique file name on each invocation.

Again, consider a rule such as this one:

:0
* ^Subject:.*This is spam.*
$MAILDIR/.Spam/

Replace it with the following:

:0:
* ^Subject:.*This is spam.*
| bzip2 -c >$MAILDIR/.Spam/new/$(eval $GENID).gz

Repeat this for all the rules in the file. Done.

Warning: Having bzip2 write directly to the Maildir may be unsafe in case another process tries to read the file while it is being written. You may want to consider combining the above with the use of safecat.

Key Bindings in DrRacket

Emacs-style key bindings

A post by Shriram Krishnamurthi on the racket-users mailing list uncovers a hidden gem in the DrRacket programming environment:

I turn off menu key bindings:

Edit | Preferences... | Editing | General | Enable keybindings in menus

(turn it off). Now you have Alt at your disposal, and with it a good chunk of Emacs keybindings.

Indeed, turning off menu key bindings does a lot more than what its description might suggest. It rebinds a whole slew of key bindings to make the environment feel more Emacs-like.

Rebinding auto-completion

Emacs-style key bindings are nice and all, but they don't resolve the problem non-US users face when trying to use auto-completion (hint: it's to do with the way that typing [Ctrl]-[/] can be pretty awkward depending on the keyboard layout).

Fortunately, DrRacket's key bindings are customizable, if only by putting a couple of suboptimally documented incantations into a file and adding that file to DrRacket by selecting the “Add User-defined Keybindings...” menu item from the DrRacket “Edit” menu. Try using the following magical formulae:

#lang s-exp framework/keybinding-lang

;; Painfully cribbed from the stuff that racket-5.1.1/collects/drracket/private/unit.rkt
;; does.

(keybinding "m:ß"
            (λ (editor evt)
              (if (is-a? editor text:autocomplete<%>)
                  (send editor auto-complete)
                  #f)))

The above binds auto-completion to [Meta]-[ß], which is most probably pretty much useless to you. Customizing the actual key to be bound is left as an exercise for the reader.

JSON Template for R6RS

Summary

As announced previously, I recently wrote an implementation of JSON Template in Typed Racket. This is a port of the original untyped Racket code to R6RS.

Like the Racket version, it is likely to contain a number of bugs because of lack of real-world usage.

JSON Template is a minimalistic, declarative template language.

Downloading

You can fetch the Mercurial repository using the following command:

hg clone http://matthias.benkard.de/code/json-template-r6rs/

(Mind the trailing slash. My web server is overly picky there.)

Documentation and Installation

See the manual for installation and usage instructions.

Dependencies

JSON Template for R6RS depends on the pregexp library.

Implementation Features

  • HTML and URI escaping through the use of formatters
  • Supports both association lists and hash tables as input data
  • Configurable meta characters
  • GPLv3 license

Missing Things

  • Literals (like {.space} and {.meta-left}/{.meta-right})
  • Multiple-argument formatters
  • Some kind of compilation for efficiency

Examples

Example code:
(define template-string "
<h1>{title|html}</h1>
{.section people}
<ul>
{.repeated section @}
  <li>{name} ({age} years)</li>
{.end}
</ul>
{.or}
<p>No one's registered.</p>
{.end}")

(define template (make-template template-string))

(template '((title . "<Registered People>")
            (people .
                    (((name . "Nathalie") (age . 24))
                     ((name . "Heinrich") (age . 28))
                     ((name . "Hans")     (age . 25)))))))

Result:

<h1>&#60;Registered People&#62;</h1>
<ul>
  <li>Nathalie (24 years)</li>
  <li>Heinrich (28 years)</li>
  <li>Hans (25 years)</li>
</ul>

JSON Template for Regular and Typed Racket

Summary

I recently wrote an implementation of JSON Template in Typed Racket, mainly in order to get a feel for DrRacket's advantages and disadvantages relative to Common Lisp and SLIME.

In contrast to the Common Lisp version, this implementation is not used in any production code, so it is likely to contain a couple of bugs. On the other hand, the implementation is more featureful and probably prettier overall, one reason being Racket's built-in support for regular expressions.

JSON Template is a minimalistic, declarative template language.

Downloading

You can fetch the Mercurial repository using the following command:

hg clone http://matthias.benkard.de/code/json-template-typed-racket/

(Mind the trailing slash. My web server is overly picky there.)

Alternatively, you can require the PLaneT version directly from Racket:

(require (planet mbenkard/json-template/json-template))

Documentation

See the manual for installation and usage instructions.

Implementation Features

  • No dependencies other than Racket's built-in libraries
  • HTML and URI escaping through the use of formatters
  • Supports arbitrary <dict?> objects as input data
  • Configurable meta characters
  • GPLv3 license

Missing Things

  • Literals (like {.space} and {.meta-left}/{.meta-right})
  • Multiple-argument formatters
  • Some kind of compilation for efficiency

Examples

Example code:
(define template-string "
<h1>{title|html}</h1>
{.section people}
<ul>
{.repeated section @}
  <li>{name} ({age} years)</li>
{.end}
</ul>
{.or}
<p>No one's registered.</p>
{.end}")

(define template (make-template template-string))

(template '((title . "<Registered People>")
            (people .
                    (((name . "Nathalie") (age . 24))
                     ((name . "Heinrich") (age . 28))
                     ((name . "Hans")     (age . 25)))))))

Result:

<h1>&#60;Registered People&#62;</h1>
<ul>
  <li>Nathalie (24 years)</li>
  <li>Heinrich (28 years)</li>
  <li>Hans (25 years)</li>
</ul>

Running Debian on a Mostly ZFS Filesystem

With btrfs still unstable (I did give it a chance) and Ext4 lacking support for on-the-fly compression and deduplication, ZFS may yet be the only choice if you need such features. Of course, without its being in the kernel, using it as the main filesystem for a GNU/Linux machine is tricky. There are various ways to get Debian GNU/Linux to run on a ZFS root filesystem using zfs-fuse, most of which involve the use of debootstrap and a fair bit of initramfs hacking.

If you don't have a ZFS-ready boot medium on hand that you can use debootstrap with, however, this may not be an option. In particular, the official Debian CD set does not include zfs-fuse as a loadable installer module.

On the other hand, if you can live with / being on a more conventional filesystem, ZFSing /home, /usr, and /var, while not for the faint of heart, is perfectly possible. The following instructions are from memory, so proceed with care, and in particular, keep a rescue disc handy. I cannot guarantee the correctness of any of the following instructions. In fact, errors are very likely! You follow them on your own risk.

Step One: Install Debian GNU/Linux

Note: This step assumes that you are installing a fresh copy of Debian GNU/Linux. If your system is already up and running (on a non-ZFS filesystem), you may skip this step.

First, create a root partition large enough to hold the base system. In theory, a couple hundred megabytes should suffice, but reserving a gigabyte or so doesn't hurt unless you're low on disc space. You may choose to reserve a partition for ZFS at this point as well.

Next, install the base system as usual and let the system reboot.

Finally, on the new Debian system, install the “zfs-fuse” package:

$ aptitude install zfs-fuse

Step Two: Create the ZFS pool and volumes

Note: This step assumes that you are not planning to use an existing ZFS pool. If you do, you may skip this step.

Assuming that /dev/sdaZ is your designated ZFS pool partition, issue the following commands (if you want to use raidz or desire the zpool to span multiple physical devices, you need to consult the ZFS manual):

$ zpool create -O compression=on -O dedup=on tank /dev/sdaZ
$ for x in tank/HOME tank/DEBIAN tank/DEBIAN/var tank/DEBIAN/usr
  do
    zfs create -o compression=on -o dedup=on $x
  done

Depending on your use case, you may want to create additional volumes for, say, /usr/src, /usr/share/doc, /usr/local, et cetera. In this case, deduplication and compression can be enabled on a case-by-case basis.

Step Three: Move /home, /usr, and /var to ZFS

Now for the tricky part. Be sure to question each step thoroughly before hitting the enter key to confirm it.

/home

Moving /home is ridiculously simple:

$ cd /
$ mv home home.OLD
$ zfs set mountpoint=/home tank/HOME
$ mv home.OLD/* /home/
$ rmdir home.OLD

/var

For /var, boot into single-user mode, and do the following:

$ mv /var /rawvar
$ mkdir /var
$ cd /var
$ ln -s ../rawvar/{lock,log,run,tmp} .
$ mv -f /rawvar/{backups,cache,lib,local,mail,opt,spool} /tank/DEBIAN/var/
$ cp -a /rawvar/* /tank/DEBIAN/var/
$ zfs set mountpoint=/zfsvar tank/DEBIAN/var
$ mount -o bind /zfsvar /var

The reason for the general complication is that the zfs-fuse tools rely on at least /var/run to communicate with the zfs-fuse daemon. The reason for not mounting the ZFS var volume at /var directly is that for some reason, the ZFS tools refuse to mount volumes in non-empty directories.

/usr

For /usr, remain in single-user mode, and do the following:

$ cd /usr/lib
$ for x in libz.so* libfuse.so* libcrypto.so*
  do
    dpkg-divert --divert /lib/$x /usr/lib/$x
    mv -vi $x /lib/
    ln -vs /lib/$x
  done
$ cd /
$ mv /usr /usr.OLD
$ zfs set mountpoint=/usr tank/DEBIAN/usr
$ zfs set mountpoint=legacy tank/DEBIAN
$ zfs set mountpoint=legacy tank
$ mv /usr.OLD/* /usr/
$ rmdir /usr.OLD

Yes, this is a bit of a hack, and it will break whenever the libraries' version numbers change. Remember to maintain the library diversions whenever the respective packages are updated or you may end up with an unbootable system!

Step Four: Do startup script fixups

As it is, Debian will not boot properly. This is because of two things: One, zfs-fuse refuses to import the filesystem on startup (don't ask me why). Two, /var is not populated with the stuff that ought to be there.

We will solve both of the above by writing a simple init script that will initialize the zpool and bind-mount /zfsvar at /var.

Create a file called /etc/init.d/mulkzfs with the following contents:

### BEGIN INIT INFO
# Provides:          mulkzfs
# Required-Start:    zfs-fuse
# Required-Stop:     zfs-fuse
# X-Start-Before:    console-setup networking
# Default-Start:     S
# Default-Stop:      0 6
# Short-Description: Mulky ZFS post-initialization
# Description:       Post-initializes ZFS mulkily.
### END INIT INFO

case "$1" in
  start)
    echo "Setting mulky ZFS stuff up."
    /sbin/zpool import tank || :
    /bin/mount -o bind /zfsvar /var
    ;;
  stop)
    echo "Shutting mulky ZFS stuff down."
    /bin/umount /var
    ;;
esac

Make the file executable:

$ chmod +x /etc/init.d/mulkzfs

Adapt the zfs-fuse and FUSE init scripts a bit so they don't depend on the networking stuff to be set up before they are called (lest insserv shall complain about cyclic dependencies):

$ for x in zfs-fuse fuse; do
    sed --in-place 's/remote_fs/local_fs/g' /etc/init.d/$x
  done

And finally, run insserv to have all init scripts rearranged as needed:

$ insserv -v mulkzfs
$ insserv -v

Wrapup

The above should leave you with a hopefully booting, ZFS-based system. If you stumble upon any mistakes in the instructions, please let me know in the comments so I can fix the bugs. If, on the other hand, the instructions actually worked for you, that would be useful information as well.

Good luck!

Lisp Is Not An Acceptable Java

Every once in a while, some kid discovers Lisp and thinks, “hey, that's actually a pretty neat language, why not use it for more than just school?” Well, there are reasons not to do that. Lots of them, actually. That's because Lisp just can't compete with Java in almost any imaginable way.

Lisp is not Web-ready

First of all, since Lisp has only one data structure—lists—it has no way of representing XML or HTML data. Since XML is the basis of the Web, you cannot provide enterprise-ready web services using Lisp as you can in Java. So what are you going to do with it? Exactly. You might as well stop at this point.

Lisp does not support enterprise features

In Lisp, there is built-in support for neither SOAP nor CORBA. This alone makes the language impossible to use in an enterprise setting, since there is no way of accessing cloud-computing or multi-tenancy services without being able to encode requests in enterprise-ready cloud communication formats.

Lisp is slow

You would think that with electrical energy becoming more expensive each day, people cared about efficiency and scalability. Not so—anyone using Lisp is using an interpreted language with huge memory requirements. Also, you can't use Lisp in an interactive application, since garbage collection can kick in at any time and stop your program for a couple of seconds at a time.

In contrast, Java and C# programs are compiled into efficient bytecode and run on an advanced, highly optimized virtual machine. Also, specialized CPUs like ARM Jazelle grant Java applications native performance—Lisp just can't compete with this because it is much too complex to implement on the metal.

Lisp is too old to be useful

Did you know that Lisp is from the 50s? It hasn't changed a bit since then. It's still the same academic parenthesis mess lacking any kind of consistency or reasonable design, and it's still all based on lists. This is in contrast to C# and Java, which are much more modern designs based on real-world experience with enterprise C++ application programming.

Lisp does not have any advantages

I know, I know, Lisp was innovative for its time. That time is long over, though. Other languages have a GC, other languages have slow interpreters, and other languages have a REPL. Get over it.

Lisp is inappropriate for teams

Granted, there is one thing that Lisp has which other languages still lack: macros. But really, using macros in production code is a disaster waiting to happen. With functions, you at least know what the evaluation rules are. But what if you call a macro like the following?

(with-open-file (file "test.txt" :direction :output)
  (print "hello" file))

This might open a file called “test.txt” and write “hello” into it—or it might reformat your hard drive! How are you to know?

Things like this make Lisp impossible to use in a team setting. Give people too much freedom, and they will do stupid things with it, annihilating any hope of documenting the resulting mess or, God forbid, making it maintainable. Enterprise languages like Java abort compilation even when dead code is detected. This makes debugging much easier.

Lisp's features have become obsolete

As can be seen in enterprise C++ applications, garbage collection is pretty much an obsolete feature, since data is practically never managed manually within the program. Instead, everything is stored in highly optimized XML databases, which gives you more flexibility and scalability. Other features, like CLOS and the condition system, have similarly become obsolete by advancing technology.

Face it: Noone cares about “interactive programming”

Then there's this neverending talk about “interactive programming,” as if that was something desirable. In Lisp, you apparently type stuff into the REPL instead of writing it into files. Great! Except... you can't store the code in files that way. Lispers tend to learn their lesson the hard way, as all code is lost when you need to reboot your computer. But then again, noone bears to write Lisp code more than 100 lines long anyway, so it probably doesn't matter much.

Tool support is stuck in the '70s

Whenever you see a misguided newbie ask on an online forum which IDE they should use for Lisp, the answer is always the same: Emacs. Really. These guys prefer an editor from the 70s over an enterprise-class IDE like Netbeans or Microsoft Visual Studio with innovative features like syntax highlighting and documentation lookup. Masochism is the only valid reason for this, but since you need to be a masochist to program in Lisp in the first place, that's not surprising.

Practically all Lispers are Smug Lisp Weenies that are a bitch to deal with

Really, the most annoying aspect of Lisp is that its followers are smug weenies. Every single one of them. You can see this on Usenet: Whenever someone writes an objective article about the benefits and disadvantages of Lisp (like this one, for example), the trolls come out and hammer you with flames. How can a community expect to garner support for its cause if it reacts so hostilely to constructive criticism?

The fact that so much of the Lisp community is centered around Usenet is a bad sign in and of itself. Do you think that successful companies like Google or Amazon ask Usenet for support? Get a grip on reality here for a moment. Real programmers don't rely on net punks for support. They have learned to buy commercial-grade enterprise support, and so should you. Good luck finding a Lisp support company, though—they've gone extinct around 30 years ago.

Lisp is used by noone

Because of all of the above, nobody actually uses Lisp for anything that approaches real-world programming. In contrast, lots of people have made real money using Java and .NET. Everyone knows that swimming against the tide is equivalent to doom. Therefore, it would be prudent to do it like the big ones and avoid Lisp like the plague.

Noone can read Lisp syntax

Finally, just look at the following typical piece of Lisp code:

((lambda([])((lambda(|| |()| |(| |)|)(+ || |(| 1 |)| |()| |(| |(| |(|)) [] [] [] [])) 0)

It's true, that's completely valid, real-world code! Can you guess what it does? Yeah, me neither. And did you notice that the parentheses aren't even balanced? Simple syntax—yeah, right.

Conclusion

You can draw your own conclusions based on the points above. However, before you do that, I advise you to take a close look at this article's publishing date. Happy programming!

CL-JSON-Template

Summary

In the course of rewriting my website software, I've written a Common Lisp implementation of (most of) JSON Template.

JSON Template is a minimalistic, declarative template language.

Downloading

You can fetch the Mercurial repository using the following command:

hg clone http://matthias.benkard.de/code/cl-json-template/

(Mind the trailing slash. My web server is overly picky there.)

Implementation Features

  • No dependencies
  • Portable Common Lisp (tested on SBCL, Clozure CL, ECL, XCL, ABCL)
  • HTML and URI escaping through the use of formatters
  • Apache license

Missing Things

  • Literals (like {.space} and {.meta-left}/{.meta-right})
  • Multiple-argument formatters
  • Options (like changing the meta character or the default formatter)
  • Some kind of compilation for efficiency

Examples

JSON-TEMPLATE> (defparameter *tmpl* (parse-template-string "
<h1>{title|html}</h1>
{.section people}
<ul>
{.repeated section @}
  <li>{name} ({age} years)</li>
{.end}
</ul>
{.or}
<p>No one's registered.</p>
{.end}"))
*TMPL*
JSON-TEMPLATE> (expand-template *tmpl*
                                '(:title "<Registered People>"
                                  :people ((:name "Nathalie" :age 24)
                                           (:name "Heinrich" :age 28)
                                           (:name "Hans"     :age 25))))
"
<h1>&#60;Registered People&#62;</h1>
<ul>
  <li>Nathalie (24 years)</li>
  <li>Heinrich (28 years)</li>
  <li>Hans (25 years)</li>
</ul>
"
JSON-TEMPLATE> (expand-template *tmpl*
                                '(:title "<Registered People>"
                                  :people ()))
"
<h1>&#60;Registered People&#62;</h1>
<p>No one's registered.</p>
"

META: New Software

After leaving this web site's server software as-is for a couple of years, I recently felt the urge to rewrite it once more. (This happens every couple of years or so. Maybe next time I'll try using C++ or O'Caml or something, just to get the hang of doing it in every way imaginable.)

Apart from the error pages being different, nothing much has changed on the user interface front yet, but stay tuned. This huge thing called “internationalization” is still lingering somewhere deep within the data model, and I intend to publish some of my future content in multiple languages. You may have noticed that I've already begun doing so: My latest article is available both in English and in German.

Under the hood, it's all different. The server is now based on a persistent Hunchentoot process, which I can tell you is infinitely more convenient to program against than CGI. The backend is still based on PostgreSQL, albeit with a lot more stuff taken care of by the database system. Relational database programming sure is nice stuff, now that I know a bit about data modelling and querying.

Oh, I almost forgot. There's one more thing that you may have noticed: If JavaScript is enabled in your browser when you're writing a comment, Akismet is now circumvented in favor of a Hashcash-like proof-of-work system. You know, have to spread awareness and all that (of privacy, I mean, and, of course, of decentralized systems accomplishing great things).

What is Source Code?

What is code, or more specifically, source code? As usual, Wikipedia leaves no doubt:

In computer science, source code is text written in a computer programming language.

Curiously, the Common Lisp HyperSpec disagrees:

source code n. code representing objects suitable for evaluation (e.g., objects created by read, by macro expansion, or by compiler macro expansion).

where code is defined as follows:

code n. 1. Trad. any representation of actions to be performed, whether conceptual or as an actual object, such as forms, lambda expressions, objects of type function, text in a source file, or instruction sequences in a compiled file. This is a generic term; the specific nature of the representation depends on its context. 2. (of a character) a character code.

Indeed, Common Lisp may be the only moderately popular language that does not define code as a piece of program text. So, while the situation seems to be pretty clear-cut in other languages, what is source code in the sense that the HyperSpec understands it, and why is it a useful concept?

Code is Data

As per the HyperSpec, source code is (some representation or instance of) a set of objects “suitable for evaluation.” Apparently, therefore, Lisp source code is, by definition, Lisp data. Now, what kinds of data objects are “suitable for evaluation?” The answer may surprise you: all of them!

Indeed, any object is evaluable. Objects intended for evaluation are called forms, which the HyperSpec classifies into three types: symbols, conses, and self-evaluating objects. All objects that are neither symbols nor conses are, without exception, self-evaluating objects.

This means that the following forms are all well-defined:

(eval (list 'print "Hello world!"))    ;prints “Hello, world!”
(eval 100)                             ;=> 100
(eval (make-hash-table))               ;=> #<a hash table>

Though it might seem ridiculous, hash tables, structs, and class instances are actually code as per the HyperSpec. Their usefulness as code may be restricted, since they merely evaluate to themselves. However, there are situations in which embedding objects in code is pretty darn useful. Consider the following code, for instance:

(defun find-mulkey-word (string)
  (scan "([Mm]ulk|[Kk]lum|Munoca)" string))

That's just a simple function call there, you might say. Right you are. However, a function may optionally be overloaded with a compiler macro that the compiler may choose to invoke in order to transform function calls involving the function. Through this, the above example actually expands to the following:

(defun find-mulkey-word (string)
  (scan (load-time-value (create-scanner "([Mm]ulk|[Kk]lum|Munoca)")) string))

Now what the hell is that supposed to mean, you ask? That, my friends, is an example of optimization by load-time evaluation. At load-time (that is, when the code above is loaded into the Lisp runtime), the create-scanner form is executed. It compiles the regular expression into a ready-to-use scanner object. This object then replaces the load-time-value form in the code. As a result, the scanner is only created once instead of every time find-mulkey-word is called (which is a lot of times, clearly, since it's such a useful function).

What's that? I can do this in C with a static variable, you say? Sure you can. Except it's you who has to worry about it. In Lisp, it's the library author. You don't even have to muck around with “objects” and useless crap like that—just write it as a simple, honest-to-God function call, because that's what it is, right? Don't you think that's at least a tiny bit nicer? Yeah, I think so, too.

Church-Numerale in C++

Vielleicht nicht die idiomatischste Implementierung, aber durchaus von einem gewissen Unterhaltungswert — Church-Numerale in C++0x:

#include <cstdlib>
#include <functional>
#include <iostream>

using namespace std;

template <typename T>
struct church {
  // ----- Typen -----
  typedef function<T(T)>         fn;
  typedef function<fn(const fn)> numeral;

  // ----- Null und Nachfolger -----
  static fn zero(const fn f) {
    return [](T x) -> T {
      return x;
    };
  }

  static numeral succ(const numeral num) {
    return [=](const fn f) -> fn {
      const numeral num_ = num;
      return [=](const T x) -> T {
        return f(num_(f)(x));
      };
    };
  }

  // ----- Hilfsfunktionen -----
  static numeral make_numeral(unsigned int n) {
    numeral num = zero;
    for (int i = 0; i < n; i++) {
      num = succ(num);
    }
    return num;
  }
};

// ----- Hauptprogramm -----
int twice(int n) {
  return 2*n;
}

int main(int argc, char **argv) {
  auto eight = church<int>::make_numeral(8);
  auto ten = church<int>::succ(church<int>::succ(eight));
  cout << "ten(twice)(1) = " << ten(twice)(1) << endl;
  return EXIT_SUCCESS;
}

Weitere Implementierungen von Church-Numeralen gibt's bei Christoph:

PostgreSQL für Ad-Hoc-Daten

Für mein nächstes Websitedesign suche ich momentan unter anderem nach dem richtigen Datenmodell. Das ist deshalb nicht ganz einfach, weil mir sowohl das relationale als auch das „document-store“-artige zusagen, wenn auch jeweils aus verschiedenen Gründen. Sowohl die eine wie auch die andere Wahl wäre jeweils gut zu rechtfertigen. Einerseits werden viele der Daten nämlich immer gleicher Art sein: Artikel mit Veröffentlichungsdatum und Urheber, Kommentare, verschiedene Sprachversionen zu jedem Artikel. Andererseits gibt es sicherlich auch Ad-hoc-Daten, die entweder nur selten auftreten, oder derer ich mir zu Anfang noch gar nicht bewußt sein kann.

Die Wahl fällt also nicht leicht. Wäre es nicht schön, gäbe es ein relationales Datenbanksystem, das auch Ad-hoc-Daten sinnvoll modellieren und abfragen kann — jedenfalls, wenn man auf die leichte Skalierung, die „richtige“ NoSQL-Systeme bieten, verzichten kann?

Interessanterweise tut PostgreSQL das. Es gibt ein Modul namens „hstore“, das es ermöglicht, einfache Schlüssel-Wert-Paare in eine Extraspalte einer Tabelle zu legen. Das ist nun zunächst nichts Besonderes — man könnte auch ohne spezielle Unterstützung durch das Datenbanksystem auf die Idee kommen, beispielsweise zusätzliche JSON-Daten in eine eigens dafür vorgesehene Spalte abzulegen. hstore erlaubt es aber auch, die Daten in Queries zu verarbeiten und abzufragen:

=> CREATE TABLE people (id   SERIAL PRIMARY KEY,
                        name TEXT,
                        info HSTORE);
=> INSERT INTO people(name, info)
     VALUES ('Heinrich',
             hstore(ARRAY['age', '25',
                          'hobbies', '{science,chemistry,math}',
                          'town', 'Munoca']));
=> INSERT INTO people(name, info)
     VALUES ('Andrius',
             hstore(ARRAY['age', '24',
                          'hobbies', '{magic,science,languages}']));
=> INSERT INTO people(name, info)
     VALUES ('Nathalie',
             hstore(ARRAY['age', '22',
                          'favorite_meal', 'Schlutzkrapfen']));
=> SELECT * FROM people;
 id |   name   |                                 info                           
----+----------+----------------------------------------------------------------------
  1 | Heinrich | "age"=>"25", "town"=>"Munoca", "hobbies"=>"{science,chemistry,math}"
  2 | Andrius  | "age"=>"24", "hobbies"=>"{magic,science,languages}"
  3 | Nathalie | "age"=>"22", "favorite_meal"=>"Schlutzkrapfen"
(3 rows)
=> SELECT name FROM people WHERE (info->'age')::INTEGER < 25;
   name   
----------
 Andrius
 Nathalie
(2 rows)
=> SELECT name FROM people WHERE 'chemistry' = ANY ((info->'hobbies')::TEXT[]);
   name   
----------
 Heinrich
(1 row)

Wie man sieht, besteht ein Nachteil von hstore gegenüber JSON oder ähnlichem darin, daß alle Daten untypisiert als Zeichenketten gespeichert werden müssen. Da man solche in SQL in fast alles Erdenkliche casten kann, hat man dagegen ein Mittel zur Verfügung, schöner wäre eine irgendwie geartete Typisierung der Daten aber natürlich trotzdem.

Eine spätere Version von PostgreSQL (9.1?) könnte da zumindest etwas Abhilfe schaffen: Wie man hört, soll JSON in Zukunft nativ unterstützt werden.

Wenn man ein bißchen herumspielen möchte, kann man Views verwenden, um aus den im hstore gespeicherten Daten tabellarische zu machen:

=> CREATE OR REPLACE VIEW people2 AS
     SELECT id,
            name,
            (info->'age')::INTEGER AS age,
            info->'town'           AS town 
       FROM people;
=> CREATE OR REPLACE VIEW hobbies AS
     SELECT id, unnest((info->'hobbies')::TEXT[]) AS hobby
       FROM people;
=gt; SELECT * FROM people2;
 id |   name   | age |  town  
----+----------+-----+--------
  1 | Heinrich |  25 | Munoca
  2 | Andrius  |  24 | 
  3 | Nathalie |  22 | 
(3 rows)
=> SELECT name, hobby FROM people2, hobbies WHERE people2.id = hobbies.id;
   name   |   hobby   
----------+-----------
 Heinrich | science
 Heinrich | chemistry
 Heinrich | math
 Andrius  | magic
 Andrius  | science
 Andrius  | languages
(6 rows)

In dem Fall bekommt man die Typisierung und angenehme Syntax wieder zurück. Dafür hätte man die Daten hier auch gleich tabellarisch speichern können; allerdings handelt es sich bei dem Beispiel freilich um ein didaktisches. In der Praxis können tabellarische Views in Ad-hoc-Daten sich durchaus als nützlich erweisen, besonders wenn eine Abwärtskompatibilität des Datenbankschemas gewährleistet werden muß oder eine schrittweise Umstellung der Ad-hoc- auf tabellarische Daten geschehen soll.

META: Neuer Server

Meine Website ist auf einen neuen Server umgezogen. Falls es deshalb in der letzten Zeit technische Probleme gab, entschuldige ich mich hiermit dafür. Eigentlich sollte das Ganze jetzt wieder einigermaßen robust laufen.

Immerhin sollten mit der mit dem Serverumzug einhergehenden Datenbankmigration auch die ganzen Zeichenkodierungsprobleme der Vergangenheit angehören. Ab jetzt darf folglich in der Kommentarfunktion nach Lust und Laune mit Unicode-Zeichen um sich geworfen werden. Ich wünsche dabei viel Spaß!

Neu ist zudem die Möglichkeit, die Website (inklusive Newsfeeds) verschlüsselt zu erreichen, wenn einem danach ist.

Der Mac ist tot

Die vergangene Woche markiert allem Anschein nach den Anfang vom Ende des Macs, wie wir ihn kennen.

Vergangene Woche kündigten Steve Jobs und seine Gefolgsleute Mac OS X 10.7 „Lion“ an. In typischer Apple-Manier wurden alle vorgestellten Neuerungen wahlweise als awesome , incredible oder revolutionary angepriesen. Im wesentlichen handelte es sich dabei um Mission Control , eine in kuriosem Maße an die GNOME Shell erinnernde, doch eigentlich konsequente Weiterentwicklung von Exposé , sowie den lange erwarteten App Store für den Mac.

Der App Store ist ein offensichtlicher Schritt in Richtung Apple-kontrollierter Monokultur.

Ebenfalls in dieser Woche wurden zwei weitere Ankündigungen gemacht.

Im Zuge eines Java-Updates für Mac OS X erfuhr man, daß Java nicht weiter aktiv von Apple unterstützt werden wird — ein Schritt in Richtung Apple-kontrollierter Monokultur.

Schließlich wird Flash nicht mehr mit neuen Macs ausgeliefert — ein Schritt in Richtung Apple-kontrollierter Monokultur.

Sieht jemand ein Muster?

Natürlich wird die Umstellung des Macs auf ein kontrolliertes, abgeschlossenes System nicht von heute auf morgen geschehen (daher auch die bewußt betonten vorläufigen Einschränkungen, Java und Flash nachinstallieren und Software am App Store vorbei aus Fremdquellen beziehen zu können). Apple, eine Firma, deren Erfolg nicht zuletzt darauf beruht, daß sie zugleich einen Kult mit einer unüberschaubaren Zahl von loyalen Anhängern pflegt, kann es sich nicht leisten, seinen treuesten Fans vor den Kopf zu stoßen, indem abrupt die Richtung geändert wird.

Schrittweise lassen sich ideologische Veränderungen aber grundsätzlich immer durchsetzen — nicht nur die stetig voranschreitende ideologische Abwertung der Solidargemeinschaft in Deutschland ist dafür ein eindrucksvolles Beispiel. Genug ehemals überzeugte Fans der (Individualismus predigenden, kreativitätsorientierten) Mac-Philosophie werden so lange ausharren, bis sich in ihren Köpfen der Wandel schleichend vollzogen hat und sie ebenso überzeugte Verfechter der (bevormundenden, konsumorientierten) i-Philosophie geworden sind.

So paßt es nur allzu gut ins Schema, wenn Apple gleich einen ganzen Abschnitt der Keynote der Beteuerung widmet, der Mac sei ein ach-so-wichtiges Standbein der Firma.

Der „Mac“ (das Produkt) — freilich. Aber der Mac ?

Die Hoffnung stirbt bekanntlich zuletzt; man sollte nur aufpassen, daß man von ihr nicht allzu sehr geblendet wird.

Merke: Keine CD-Abbilder auf die Festplatte dd'en (Mac)

Aus der Beschreibung zu den CD-Abbildern für die Arch-Linux-Installation:

All available images can be burned to a CD, mounted as an ISO file, or be directly written to a USB stick using a utility like ‘dd’.

Aus der Möglichkeit einer USB-Stick-Variante schloß ich, daß es nicht schaden könne, doch einfach einmal zu versuchen, das Abbild stattdessen direkt in eine Partition auf meiner Festplatte zu dd'en.

Weit gefehlt.

Nach einem Reboot stellte sich heraus, daß der Bootloader meines Macs (MacBook Pro 13" (Mid 2009)) nicht nur nicht in der Lage ist, von einer so präparierten Partition zu booten, sondern auch gleich den Dienst ganz verweigert, wenn eine vorhanden ist. Praktischerweise schließt das sowohl das Booten von der Festplatte als auch das von CDs ein (welche, nebenbei bemerkt, auch nicht mehr durch Mausklick ausgeworfen werden konnten) — und den Target Disk Mode obendrein.

Ein Glück, wenn man noch einen ausrangierten Laptop mit demselben Festplatteninterface und einem guten, alten (und weniger absturzgefährdeten) BIOS hat.

Produktivitätssteigerung vs. Lohn- und Arbeitszeitentwicklung 1991-2006

Das Statistische Bundesamt berichtet in seiner Pressemitteilung vom 27.9.2007:

Wie das Statistische Bundesamt mitteilt, ist die gesamtwirtschaftliche Arbeitsproduktivität ... in Deutschland im Zeitraum 1991 bis 2006 ... je Erwerbstätigenstunde um 32,4% gestiegen.

Stellt sich die Frage, warum die Arbeitszeiten dann nicht entsprechend um 32,4% (statt tatsächlich 7,4%[1]) abgenommen oder zumindest die Reallöhne im entsprechenden Maße zugenommen haben (anstatt wie tatsächlich um 2% zu sinken[2]).

Auch die folgende Feststellung macht einen nach näherem Hinsehen nachdenklich:

Die Lohnstückkosten, die die Veränderung der Lohnkosten in Relation zur Arbeitsproduktivität darstellen, stiegen von 1991 bis 2006 nach dem Personenkonzept um 12,4% und nach dem Stundenkonzept um 13,8% an.

Bedenkt man nämlich die Inflation von ungefähr 34%[3][4] im betreffenden Zeitraum, muß man von einer beträchtlichen Abwertung geleisteter Arbeit ausgehen. Da die Einkommen in Deutschland im betreffenden Zeitraum zugleich auseinanderdrifteten[5], darf angenommen werden, daß diese die unteren und mittleren Einkommen entsprechend schwerer traf.


[1] Bundeszentrale für politische Bildung
[2] Statistisches Bundesamt, Pressemitteilung vom 27.11.2006
[3] Gapminder
[4] Statista (ungenaue Schätzung durch Zusammenrechnung der Jahreswerte, gerade übereinstimmend mit [3])
[5] Johannes Gernandt, Friedhelm Pfeiffer, Zentrum für europäische Wirtschaftsforschung, Diskussionspapier 06-019, November 2007

Google Street View vs. Gemeinden

Radio Oberland (13.8.2010):

Der Streit um die Einführung des umstrittenen Kartendienst[es] "Street View" des Internetunternehmens Google auch im Oberland nimmt kein Ende ... Aber auch schon viele Gemeinden in der Region, wie zum Beispiel Herrsching oder Seeshaupt sagen Google den Kampf an.

Natürlich: Die Diskussion über die Wahrung der Privatssphäre durch Konzerne (sowie durch den Staat!) muß geführt werden, allein schon um die Bevölkerung für das Thema zu sensibilisieren und dem Einzelnen zu erlauben, sich eine informierte Meinung zu bilden. Anstatt Google aber pauschal „den Kampf anzusagen“, wäre es möglicherweise eine sinnvollere Verwendung derselben Ressourcen seitens der Gemeinden, einen Rundbrief an alle Haushalte zu schicken, der sachlich auf die anstehende Aufzeichnung der Umgebung sowie die Möglichkeit des Widerspruchs gegen die Aufnahme des eigenen Grundstücks hinweist und in einer bebilderten Anleitung erklärt, wie dieser durchgeführt werden kann.

Die zur Meinungsbildung nötigen ungefärbten Informationen über die Tatsachen sind es nämlich, woran es meist am deutlichsten mangelt. Das ist in diesem Fall leider nicht anders.

Das Metaobjektsystem von ECMAScript Harmony und das CLOS-MOP: ein Vergleich

JavaScript ist eine interessante Sprache. Sie hat sich jahrelang mit ihrem Ruf als das häßliche kleine Browser-Scripting-Ding zufriedengegeben, mit dem man sich als Webentwickler eben herumschlagen muß. Seit relativ kurzer Zeit wandelt sich jedoch sowohl das Selbstverständnis der JavaScript-Kultur als auch die Sprache selbst. JavaScript wird nicht mehr nur verwendet, sondern von einigen noch Wenigen auch geliebt. Es entwickelt sich langsam eine Gefolgschaft wie sie bei Python, Ruby und Lisp existiert und die die weitere Entwicklung der Sprache lenkt.

Sie ist es, die versucht, JavaScript aus einer Skriptsprache für Webbrowser in eine auch anderweitig eingesetzte Sprache zu verwandeln. Solche Dinge wie ein (mit den bisherigen Konzepten integriertes) Klassensystem und bessere Unterstützung für lexikalische Variablenbindung und Metaprogrammierung soll die Sprache mit ECMAScript Harmony bekommen.

Das für ECMAScript Harmony vorgeschlagene Metaobjektprotokoll erscheint dabei zunächst einmal unspektakulär. Sicher, es wird möglich sein, Zugriffe auf Attribute abzufangen. Man kann Aufzählungen mit for in umprogrammieren. Man kann Funktionen simulieren. Das sind aber allesamt Dinge, die auch zum Beispiel in Python leicht sind.

Es gibt allerdings einen kleinen, feinen Unterschied zu dem, was Python mit den Unterstrich-Methoden bietet*: Die Objekte (in diesem Kontext Proxies genannt), deren Verhalten umprogrammiert wird, tun das nicht selbst. Stattdessen ist dafür ein sogenanntes Handler-Objekt zuständig. Auf alles, was mit dem Proxy passiert, kann das Handler-Objekt reagieren. Attributszugriffe, -veränderungen und -löschungen, Aufzählungen, das Ändern von Eigenschaften von Attributen, Methodenaufrufe — das Verhalten des Proxys wird vollständig durch das Handler-Objekt implementiert.

Das ist kein unbekanntes Konzept: Es ist das Konzept des Metaobjekts. Grundsätzlich ist ein Metaobjekt ein Objekt, das das Verhalten anderer Objekte beschreibt. (Dabei wird es natürlich seinerseits unter Umständen von einem Metaobjekt beschrieben, welches auch es selbst sein kann.) Offenbar ist ein Handler-Objekt genau das. Sehen wir uns das API einmal an: Es gibt im vorgeschlagenen Metaobjektprotokoll die Methode get, die für jeden Attributslesezugriff aufgerufen wird und das Ergebnis berechnet. Das erinnert sehr an — richtig — AMOP, das de-facto-Standardmetaobjektprotokoll für Common Lisp, nämlich konkret an dessen Methode slot-value-using-class: Genau wie get in JavaScript ist slot-value-using-class nicht polymorph im konkreten Ziel-, sondern dessen Metaobjekt (in diesem Fall der Klasse). Gleiches gilt für alle anderen Methoden im Harmony-Metaobjektprotokoll.

Entsprechung von AMOP- mit JavaScript-Handler-Methoden für den Attributzugriff
delete slot-makunbound-using-class
get slot-value-using-class, compute-effective-method
set (setf slot-value-using-class)
has slot-boundp-using-class

Natürlich bricht die Analogie spätestens an dem Punkt zusammen, an dem es einerseits in Common Lisp um Klassen und generic functions geht, die es in JavaScript nicht gibt — und andererseits bei der generischen Aufzählung von Containerobjekten, die Common Lisp nicht bietet. Dennoch: Die Nähe ist vorhanden, und, jedenfalls gefühlt, gegenüber Lisp stärker als gegenüber Python, Ruby oder gar Java.

Eine Einführung in das Metaobjektsystem von JavaScript (welche möglicherweise auch hilft, die Ideen hinter AMOP zu verstehen) gibt der Vortrag „Changes to ECMAScript, Part 2: Harmony Highlights — Proxies and Traits“ von Tom Van Cutsem.


(*) Mit Deskriptoren gehen — zumindest für Objekte, die nicht selbst Klassen sind — ähnliche Dinge. Attributszugriffe folgen in Python grundsätzlich recht komplizierten Regeln, und philosophisch finde ich die Entfernung zu JavaScripts Proxy-Objekten ziemlich fühlbar.

Eduroam/802.1X mit dem Palm Pre (speziell LMU/TU/LRZ München)

Nicht nur in Deutschland ist es an Universitäten üblich, WLAN-Zugriff via Eduroam anzubieten. Da die Verbindung dabei auf Standards aufbaut und die Konfiguration eines VPN-Clients oder ähnlichem wegfällt, ist das prinzipiell eine sehr schöne Lösung. Leider machen unterschiedliche Rechner dann doch hin und wieder Anstalten, den Eduroam-Verbindungsaufbau zu verstehen.

Nun sollte man annehmen, daß ein so modernes, zudem noch Linux-basiertes Gerät wie der Palm Pre in der Lage ist, einen schnöden WLAN-Verbindungsaufbau problemlos zu meistern. Tatsächlich fragt das Gerät beim Versuch, sich einzuloggen, nach Benutzernamen und Paßwort, doch der naive Ansatz, es wie am PC auch mit der Campus-Kennung der LMU München zu versuchen, schlägt eigenartigerweise fehl.

Die Lösung ist, wie es nach einigem Herumprobieren scheint, sich an der Konfigurationsanleitung für Nokia-Handys zu orientieren. Aus dieser erfährt man, daß der korrekte Benutzername von der Form LRZ-Kennung@ads.mwn.de ist.

Die eigene LRZ-Kennung, die man als Mitglied des LRZs zugewiesen bekommen hat, kann man (als LMU-Student) notfalls über das LRZ-Helpdesk erfragen, Studenten und Mitarbeiter externer Hochschulen können unter Umständen eine Gastkennung beantragen. In der Regel handelt es sich dabei um eine mehr oder weniger zufällig anmutende Ziffern- und Buchstabenfolge.

Das geforderte Kennwort ist das LRZ-Kennwort — bei LMU-Studenten in der Regel identisch mit dem Campus-Kennwort.

Der Grund dafür, daß mit der Campus-Kennung der Login nicht klappt, ist mir nicht bekannt. Möglich auch, daß lediglich die meinige zu lang ist.

Multiple Dispatch in JavaScript (ECMAScript 5)

Folgender Code implementiert Mehrfachdispatch in JavaScript (ECMAScript 5, um genau zu sein). Ich habe ihn unter die MIT-Lizenz gestellt, und er kann als mulkiple-dispatch.js heruntergeladen werden.

Implementierung

"use strict";

function MulkipleDispatchObject(prototype, slots) {
  var inheritsFrom = function(object, other) {
    return (object !== null
            && (object === other
                || inheritsFrom(Object.getPrototypeOf(object), other)));
  };

  var realSlots = {};
  for (var slot in slots) {
    if (!(slots[slot] instanceof Array)) {
      realSlots[slot] = slots[slot];
    } else {
      (function(slot) {
         var mulkiSlot = { method: true, value: function() {
           for (var alternative in slots[slot]) {
             var applies = true;
             alternative = slots[slot][alternative];
             var typelist = alternative['dispatch'];
             for (var i = 0; i in typelist && i in arguments; i++) {
               if (!(arguments[i] === null
                     || arguments[i] instanceof typelist[i]
                     || inheritsFrom(arguments[i], typelist[i]))) {
                 applies = false;
                 break;
               }
             }
             if (applies) {
               return alternative['value'].apply(this, arguments);
             }
           }
           return Object.getPrototypeOf(this)[slot].apply(this, arguments);
         }};
         realSlots[slot] = mulkiSlot;
      })(slot);
    }
  }
  return Object.create(prototype, realSlots);
}
MulkipleDispatchObject.create = MulkipleDispatchObject;

Beispiel

Verwendet wird der Code zum Beispiel wie folgt (hier mit dem printf-Modul von Narwhal):

var printf = require('printf');

// ----- Konstruktoren -----
function Being(name) {
  return MulkipleDispatchObject.create(Being.prototype, {
    name: { value: name, enumerable: true },
    toString: { method: true, value: function() {
      return printf.sprintf("%s", this.name);
    }},
    greet: [{ dispatch: [], value: function(other) {
      printf.printf("%s grüßt %s.", this, other);
    }}]
  });
}

function Person(name) {
  return MulkipleDispatchObject.create(Person.prototype, {
    name: { value: name, enumerable: true },
    greet: [
      { dispatch: [Person], value: function(other) {
          printf.printf("%s sagt: \"Mulk, %s\".", this, other.name);
        }},
      { dispatch: [Cat], value: function(other) {
          printf.printf("%s streichelt %s.", this, other);
        }}]
  });
}
Person.prototype = Being(null, null);
Person.prototype.constructor = Person;

function Cat(name, owner) {
  return MulkipleDispatchObject.create(Cat.prototype, {
    name: { value: name, enumerable: true },
    owner: { value: owner, enumerable: true, modifiable: true },
    greet: [{ dispatch: [], value: function(other) {
      printf.printf("%s miaut %s an.", this, other);
    }}]
  });
}
Cat.prototype = Being(null, null);
Cat.prototype.constructor = Cat;


// ----- Hauptprogramm -----
var matthias = Person("Matthias");
var simon = Person("Simon");
var magdalena = Person("Magdalena");
var mauzi = Cat("Mauzi", magdalena);

matthias.greet(magdalena);  // => Matthias sagt: "Mulk, Magdalena".
magdalena.greet(matthias);  // => Magdalena sagt: "Mulk, Matthias".
simon.greet(matthias);      // => Simon sagt: "Mulk, Matthias".
matthias.greet(simon);      // => Matthias sagt: "Mulk, Simon".
mauzi.greet(matthias);      // => Mauzi miaut Matthias an.
matthias.greet(mauzi);      // => Matthias streichelt Mauzi.

Anmerkungen

Die Implementierung ist sehr naiv, und entsprechend unterwältigend wird vermutlich die Performance sein. Es gibt momentan außerdem keine Möglichkeit, nachträglich Mehrfachmethoden hinzuzufügen.

Äquivalenz von Daten und Code — in Lisp und anderswo

Viele Dinge zeichnen Lisp aus (das Objektsystem, das Makrosystem, die interaktive Entwicklungsweise, das condition system etc.), aber eines, das besonders häufig genannt wird, ist die Äquivalenz von Daten und Code.

Nun sind Daten und Code in Lisp in der Tat gewissermaßen „äquivalenter“ als in anderen Sprachen. Das Wort Äquivalenz klingt aber, als würde es etwas Absolutes bezeichnen. Das ist unglücklich, denn es gibt dabei wie bei den meisten Aspekten aller Dinge in dieser Welt durchaus Abstufungen und verschiedene Ausprägungen. Um das zu durchschauen, muß man aber erst einmal verstehen, worin diese sogenannte Äquivalenz von Code und Daten eigentlich besteht.

Daten sind Code!

Zunächst kann man feststellen, daß Lispcode quasi in einem mit XML vergleichbaren (aber besser für Code geeigneten) Format geschrieben wird. S-Expressions (zu deutsch: symbolische Ausdrücke) kann man sowohl für die Darstellung von Daten als auch von Code sehr gut verwenden. Da das in Lisp auch getan wird, kann Code wie ein Datensatz behandelt werden. Makros machen sich das zunutze, um Code zur Kompilierzeit zu transformieren (Stichwort: statische Metaprogrammierung).

Doch ziehen wir es einmal von der anderen Seite auf. Angenommen, wir wollten eine Klassenhierarchie (also Daten) beschreiben. In XML könnte man die Definition einer Klasse person wie folgt angeben:

<class name="person" superclass="creature">
  <property name="name" accessor="person-name" initarg="name" initform="Mary Unnamed"/>
  <property name="birthday" accessor="person-birthday" initarg="birthday"/>
</class>

Wir können das direkt in S-Expressions übersetzen:

(class :name "person" :superclass "creature"
  (property :name "name" :accessor person-name :initarg :name :initform "Mary Unnamed")
  (property :name "birthday" :accessor person-birthday :initarg :birthday))

Wenn wir die Konventionen ein bißchen weg von XML hin zu Lisp anpassen, ist es ebenso natürlich, stattdessen folgendes zu schreiben:

(defclass person (creature)
  ((name :accessor person-name :initarg :name :initform "Mary Unnamed")
   (birthday :accessor person-birthday :initarg :birthday)))

Hier ist jedoch etwas interessantes geschehen: Der vorangehende S-Ausdruck beschreibt nicht mehr nur Daten. Vielmehr handelt es sich um ausführbaren Code. (Man überzeuge sich selbst davon, indem man ihn in eine Common-Lisp-Implementierung eingibt.)

In anderen Worten: Die „Äquivalenz“ von Code und Daten in Lisp beschränkt sich gerade nicht auf die lediglich oberflächliche Gleichheit der verwendeten Datenstrukturen. (Tatsächlich wird der Stellenwert von Listen als Datenstrukturen in Lispprogrammen weithin überschätzt.) Vielmehr verschwimmt die Grenze zwischen Daten und Code auf eine ganz konkrete Art und Weise, nämlich indem die Daten, die wir schreiben, zugleich als Code dienen.

Einerseits könnte man die fehlende Trennung von Daten und Code als Schlampigkeit werten. Tatsächlich wird ja häufig davor gewarnt, zu viel zu „hardcoden“. Natürlich gilt das für Lispcode genauso, aber die Frage, was eigentlich unter „Hardcoden“ fällt und was nicht, ist nicht so einfach wie in einer Sprache wie C++. Schließlich können solche Dinge wie Klassendefinitionen in Lisp ganz einfach im laufenden Betrieb neu geladen werden, wonach jede Instanz der Klasse die neue Klassenstruktur automatisch übernimmt — ganz so also, als handelte es sich „nur“ um Daten, die man mal eben neu einliest.

In der Praxis: Eingebettete Daten...

Interessant wird es aber spätestens dann, wenn man sich nicht sicher ist, ob etwas, das man definieren will, eher als Daten oder als Code dargestellt werden soll. Stellen wir uns ein Computerspiel mit verschiedenen Monstertypen vor. Wie modellieren wir die Monster? Bekommt jeder Monstertyp eine Klasse auf der Programmebene, oder schreiben wir eine gemeinsame Klasse Monster für alle Monstertypen und lesen die Monsterdaten dann aus Dateien ein? Falls wir den datengetriebenen Ansatz wählen: Was tun wir, wenn wir das Verhalten von Monstern je nach Klasse anpassen wollen? Betten wir eine Skriptsprache in die Datendefinitionen ein oder hardcoden wir die Verknüpfung zwischen Klasse und Verhalten dann doch wieder direkt? Aber inwiefern handelt es sich noch um Daten, wenn wir plötzlich eine ganze Programmiersprache eingebettet haben?

Offenbar verschwimmt hier die Grenze zwischen dem, was man als Daten und dem, was man als Teil des Programms betrachten kann. Ein Lispprogrammierer ist da fein heraus: Er bastelt sich ein Makro, sagen wir define-monster-class, und mischt Daten frei mit Code:

(define-monster-class computer-scientist (humanoid)
  (stats (health 30)
         (strength 20)
         (charisma -10)
         (intelligence 90))
  (extra-talents (coffee-tolerance 100) (keyboard-cleaning 40))
  (natural-enemy-reactions (patent-lawyer (enemy)
                             (make-jokes-about enemy)
                             (glare-at enemy))
                           (end-user ()
                             (run-away)))
  (on battle-entry (me enemies)
    (cry "You will be repartitioned!")
    (tell-others
      (protect me)
      (attack enemies))
    (make-awkward-gesture)
    (attack enemies)))

...gibt es nicht nur in Lisp.

So weit, so gut. Aber widmen wir uns anderen Programmiersprachen. Wie nah an Lisp können wir herankommen, um unsere Daten in Code einzubetten? In Ruby bekommt man mit entsprechender Metaprogrammierung folgendes hin:

class ComputerScientist < Humanoid
  stat :health, 30
  stat :strength, 20
  stat :charisma, -10
  stat :intelligence, 90
  extra_talents { :coffee_tolerance => 100, :keyboard_cleaning => 40 }
  on :natural_enemy_encounter, PatentLawyer do |enemy|
    make_jokes_about enemy
    glare_at enemy
  end
  on :natural_enemy_encounter, EndUser do
    run_away
  end
  on :battle_entry do |enemies|
    cry "You will be repartitioned!"
    tell_others { |me|
      protect me
      attack enemies
    }
    make_awkward_gesture
    attack enemies
  end
end

Zugegeben: Es ist insofern ein konstruiertes Beispiel, als es sich hier um etwas handelt, das ein traditionelles Klassensystem gut modellieren kann. Aber ähnliche Techniken kommen zum Beispiel in Ruby on Rails für die Angabe von Datenbankschemata zum Einsatz:

ActiveRecord::Schema.define do
  create_table :articles do |table|
    table.column :id, :integer
    table.column :title, :string
    table.column :author, :string
    table.column :body_text, :string
    table.column :published, :datetime
  end
end

Es ist sicherlich nicht weit hergeholt, zu behaupten, es handle sich bei einem Datenbankschema um Daten, nicht so sehr um Code. Ruby erlaubt es, diese Daten recht natürlich in Code einzubetten. Die Sprache hat eben eine große philosophische Nähe zu Lisp.

Was ist mit anderen Sprachen? In JavaScript könnte man so etwas wie das Folgende relativ leicht umsetzen:

var ComputerScientist = makeMonsterType(Humanoid, {
  stats: {
    health: 30,
    strength: 20,
    charisma: -10,
    intelligence: 90
  },
  extraTalents: { CoffeeTolerance: 100, KeyboardCleaning: 40 },
  naturalEnemies: {
    PatentLawyer: function(enemy) {
                    this.makeJokesAbout(enemy);
                    this.glareAt(enemy);
                  },
    EndUser: function() {
               this.runAway();
             }
  },
  onBattleEntry: function(enemies) {
    this.cry("You will be repartitioned!");
    this.tellOthers(function(me) {
      this.protect(me);
      this.attack(enemies);
    });
    this.makeAwkwardGesture();
    this.attack(enemies);
  }
});

Das ist schon nicht mehr ganz so lispig wie die Ruby-Variante, weil Code und Daten strikter getrennt sind. Außerdem hat man das Problem des Neuladens: Wie sorgt man nach dem Verändern des Codes dafür, daß bestehende ComputerScientists die neuen Attribute bekommen? (Ruby verhält sich diesbezüglich wie Lisp.) Immerhin aber gibt es die passende Notation für Daten sowie starke dynamische Metaprogrammierungsfähigkeiten in der Sprache — damit ist schon einiges machbar.

In Python dagegen sieht es schon wesentlich düsterer aus. Man müßte etwas wie das Folgende schreiben, um noch einigermaßen innerhalb der Konventionen der Sprache zu bleiben (und auch das geht nur deshalb noch relativ gut, weil wir etwas modellieren, das sich gut in Klassen ausdrücken läßt):

class ComputerScientist(Humanoid):
    health = 30
    strength = 20
    charisma = -10
    intelligence = 90
    talents = dict(Humanoid.talents, CoffeeTolerance=100, KeyboardCleaning=40)
    
    def on_battle_entry(self, enemies):
        self.cry("You will be repartitioned!")
        self.tell_others(Creature.protect, self)
        self.tell_others(Creature.attack, enemies)
        self.make_awkward_gesture()
        self.attack(enemies)
    
    @natural_enemy_reaction(PatentLawyer)
    def patent_lawyer_reaction(self, enemy):
        self.make_jokes_about(enemy)
        self.glare_at(enemy)
    
    @natural_enemy_reaction(EndUser)
    def end_user_reaction(self, enemy):
        self.run_away()

Zugegeben: Es könnte schlimmer sein. Dennoch weicht der Code schon stärker von dem ab, was man intuitiv schreiben würde, um die Daten zu beschreiben — ganz zu schweigen davon, daß man mit Python (im Gegensatz zu Ruby!) dieselben Probleme wie mit JavaScript hat, was das Neuladen der Klassendefinition angeht.

In Sprachen wie Java oder C++, deren Metaprogrammierungsfähigkeiten wesentlich geringer sind, geht das Ganze natürlich erst recht nur mit argen Verrenkungen (wie z.B. externen Codegeneratoren und fragwürdigen Linker-Tricks), so daß stattdessen dann meist eine Skriptsprache eingebettet und jede Form von Vermischung von Daten und Code als Hardcoding geächtet wird.

Conclusio

Das Verschwimmenlassen von Code mit Daten ist keine simple Ja/Nein-Frage. Natürlich macht auch die oberflächlichere strukturelle Äquivalenz von Daten und Code in Lisp es einem leichter, die entsprechende de-facto-Äquivalenz herzustellen und auszunutzen. Und doch gibt es sie in verschiedenen Ausprägungen in unterschiedlichen Programmiersprachen und Kontexten. Letztlich ist sie nichts weiter als eine Form von Deklarativität, in der der Programmierer die deklarativen Elemente zu einem gewissen Grad selbst definiert.

Man darf bei alledem aber nicht vergessen, daß die Praktikabilität eines Paradigmas in einer gegebenen Programmiersprache auch stets eine kulturelle Frage ist. Wie realistisch es ist, Metaprogrammierung und in die Sprache eingebettete anwendungsspezifische Subsprachen zu verwenden, hängt schließlich nicht zuletzt damit zusammen, wie gut es von den verfügbaren Entwicklungswerkzeugen und Bibliotheken unterstützt wird.

Ein Tool-Wrapper als Programmiersprachentest

In der Unix-Welt gibt es ein nützliches Programm namens du (disk usage). Es zeigt einem innerhalb eines gegebenen Verzeichnisbaums an, wieviel Platz jeweils jedes Unterverzeichnis auf dem Datenträger einnimmt. Leider sortiert es die Liste nicht, so daß es nicht leicht ist, die größten Platzverschwender auf einen Blick auszumachen.

Einst baute ich mir einen Shell-basierten Wrapper um es herum, der genau das tat. Dann kam ich auf die Idee, einmal zu testen, wie schwierig es wäre, etwas Vergleichbares in einer „richtigen“ Programmiersprache zu schreiben. So entstand du.cl, eine Common-Lisp-Version des Wrappers.

Als sie fertig war, stellte ich fest, daß ich für die Umsetzung bereits einiges an Techniken benötigt hatte: Stringverarbeitung (Auseinanderpflücken und Wiederzusammensetzen), Aufruf von Unterprozessen, Kommunikation mit diesen über Pipes, Verarbeitung und Sortierung von (in geringem Maße) komplexen Listen. Dennoch war der Code nicht lang oder kompliziert geworden.

Das brachte mich auf den Gedanken, daß es sich bei diesem Programm um einen passenden zweiten Schritt nach dem guten, alten „Hallo-Welt-Programm“ handeln könnte: Niedrige, aber vorhandene Komplexität, geringer Programmieraufwand und dennoch die Lösung eines konkreten, realen Problems. Gerade Letzteres ist sehr nützlich, um zu evaluieren, wie praxistauglich eine Programmiersprache und ihre Compiler sind: Denn so elegant und schön eine Sprache sein mag — wenn man damit nicht in der Lage ist, ein Alltagsproblem zu lösen, beschränkt das ihre Nützlichkeit bereits in vernehmlichem Maße.

Auch die Qualität der Dokumentation stellt sich schnell heraus, wenn man gezwungen ist, die nötige Funktionalität darin zu suchen. Allein das Aufrufen eines Unterprogramms mit Umleitung der Pipes ist häufig bereits etwas, das man nicht auf Anhieb in den Bibliotheken findet.

Möglicherweise (vermutlich aber eher weniger) ist du.cl auch brauchbar, um sich schnell einen Überblick über den Teil einer Programmiersprache zu verschaffen, der nichts mit Komplexitätsmanagement zu tun hat. (Abstraktere Konzepte wie Klassen und Methoden zum Beispiel sind auf dieser niederen Ebene natürlich unpassend.) Freilich sagt das noch sehr wenig darüber aus, wie sich die Sprache in einem größeren Unterfangen schlägt, doch ist es immerhin ein interessanter Aspekt.

Im Laufe der Zeit haben sich einige Implementierung der du.cl angesammelt, manche davon auf Rechnern, die ich nicht mehr verwende (ironischerweise unter anderem die ursprüngliche du.cl selbst, so daß ich jetzt nur noch Implementierungen besitze, die andere Dateiendungen haben). Ich habe auch derzeit nicht gerade die Motivation, diese alten Versionen herauszusuchen. Dennoch: Diejenigen Implementierungen, die ich gerade zur Hand hatte, habe ich unter http://matthias.benkard.de/code/ducl/ ins Netz gestellt. Sie sind nicht unbedingt idiomatisch, da ich die jeweiligen Sprachen nur zum Teil beherrsche und es sich teilweise um meine ersten Experimente mit ihnen handelt. Wen aber grundsätzlich interessiert, wie man in Io, Factor und SWI-Prolog Unterprozesse aufruft und ihre Ausgaben verarbeitet, kann sich den Code ansehen.

Update 3.5.2010: JavaScript.

Update 3.5.2010 #2: Die ursprünglichsten Varianten wieder ausgegraben: Common Lisp, Haskell, O'Caml, PLT Scheme.

Update 15.4.2010: C++.

Update 23.11.2012: Rust.

Update 31.3.2013: TypeScript, Rust 0.5.

Update 7.4.2013: Scala.

Update 5.5.2015: Perl 6.

Update 1.6.2018: Rust 1.26.

Derzeit vorhandene Implementierungen
C++du.cpp
Common Lisp (ECL)du.cl
Factordu.factor
Haskell (GHC)du.hs
Iodu.io
JavaScript (Narwhal)du.js
Objective Camldu.ml
Perl 6du.p6
Prolog (SWI)du.prolog
Rubydu.rb
Rust (v0.5, 2013)durst.rs
Rust (v1.26, 2018)durst-2018.rs
Scheme (PLT)du.ss
Scala (2.10)DU.scalaDU.jar (kompilierte Version)
TypeScript (Node)du.ts

Die Dualität zwischen Conditions und dynamischen Variablen

Kontroll- und Datenfluß

Auf den ersten Blick sehen Conditions und Sondervariablen nicht so aus, als hätten sie viel mit einander zu tun. Sondervariablen scheinen eine Mischung aus globalen Variablen und Funktionsargumenten zu sein, während Conditions augenscheinlich Exceptions ähneln. Sieht man aber näher hin, so ergeben sich interessante Parallelen. Sehen wir uns einen einfachen Kontrollfluß zwischen mehreren Prozeduren an, einmal mit einem Zugriff auf eine Sondervariable und einmal mit dem Auslösen einer Condition:

Offenbar passiert hier jeweils Ähnliches. Kein Wunder: Sowohl die Bindung für eine Sondervariable als auch die für einen Condition-Handler befinden sich jeweils im dynamischen Kontext der Ausführung.

Interessant wird es aber dann, wenn wir uns den Datenfluß ansehen:

Hier zeigt sich nämlich ein anderes Bild: Wenn der Datenfluß auch stets von der Seite aus initiiert wird, die tiefer in der Aufrufskette liegt, so zeigt er doch in die je umgekehrte Richtung.

In der Tat kann man den Nutzen von Sondervariablen wie von Conditions so auf den Punkt bringen: Sie ermöglichen eine globale Kommunikation zwischen verschiedenen, unter Umständen weit von einander getrennten Teilen der Aufrufskette. Sondervariablen laufen dabei von oben nach unten, Conditions von unten nach oben, und zwar jeweils ohne Mitwirkung des Codes, der zwischen den Kommunikanten liegt. Auf diese Weise ergänzen die beiden Konzepte gemeinsam das Konzept des Funktionsaufrufs, welches mit seinen Argumenten und Rückgabewerten eine lokale Kommunikation ermöglicht.

Gegenseitige Simulation

Interessant ist auch eine weitere Beobachtung: Conditions und Sondervariablen können einander simulieren.

Zuerst der einfache Fall: Wir simulieren Conditions mithilfe von Sondervariablen. Dabei setzen wir voraus, daß unsere Sprache über Closures sowie — bei Bedarf — ein primitives System zur nichtlokalen Kontrollübergabe (z.B. Exceptions) verfügt. In diesem Fall ist es nämlich denkbar einfach: Wir führen eine Sondervariable ein, in der wir eine Liste der aktiven Condition-Handler speichern. Dabei handelt es sich schlicht um Closures. Die Auslösefunktion für Conditions kann dann dadurch implementiert werden, daß sie durch die Liste der aktiven Handler sieht und den richtigen aufruft. Die nichtlokale Kontrollübergabe kommt dabei nur dann ins Spiel, wenn der Condition-Handler sich dazu entschließt, den Stack abzurollen und den Unteraufruf zu beenden, z.B. weil die Condition für einen irreparablen Fehler steht.

Der umgekehrte Fall ist zugegebenermaßen etwas weiter hergeholt — man könnte auch sagen: an den Haaren herbeigezogen. Dennoch: Wir können für jede Sondervariable, die wir simulieren möchten, eine Condition einführen. An der Stelle der Variablenbindung binden wir stattdessen einen Condition-Handler, der als Rückgabewert den Wert zurückgibt, der an die Variable gebunden werden soll. An die Stelle des Zugriffs auf die Variable setzen wir die Auslösung der Condition. Der Wert der Variablen ist dann der Rückgabewert des Condition-Handlers. (Falls das Condition-System keine Rückgabewerte erlaubt, so kann man diese freilich, wenn auch möglicherweise unter Mehraufwand, mithilfe einer globalen Variablen simulieren.)

Conclusio

Es ist immer wieder faszinierend, wie gut die Teile von Common Lisp zusammenpassen. Das Condition-System ist bereits für sich genommen ein Alleinstellungsmerkmal der Sprache, genau wie das Makrosystem, CLOS, die unheimlich dynamische Entwicklungsweise, und und und — aber nur im größeren Zusammenhang erschließt sich, wie durchdacht das Gesamtsystem eigentlich ist. Daß das Condition-System erst im Angesicht von Sondervariablen zeigt, daß es „paßt“, fügt sich in dieses Schema nur zu gut ein.

Pre-Scheme und Freunde: doch noch richtiges Low-Level-Lisp

Mein gestriger Eintrag beschäftigte sich mit verschiedenen Versuchen, ein Lisp zu finden, das sich für systemnahe Entwicklung eignet und endete mit dem Schluß, daß die einzige Lösung ein extrem dünner syntaktischer Layer über C zu sein scheint, wie SC einer ist.

Nachdem ich mit diesem Ergebnis immer noch ein wenig unzufrieden war, startete ich noch einen weiteren, letzten Versuch, etwas anderes zu finden. Und wer hätte es gedacht? Die richtigen Suchbegriffe brachten mich tatsächlich zu einem höchst interessanten Lisp-Dialekt für Systementwicklung: Pre-Scheme.

Pre-Scheme wird verwendet, um die virtuelle Maschine von Scheme48 zu entwickeln. Ein Pre-Scheme-Compiler befindet sich entsprechend in der Distribution von Scheme48.

Der Unterschied zwischen Pre-Scheme und SC ist, daß Pre-Scheme versucht, möglichst viele Features von Scheme zu erhalten, ohne die direkte Übersetzung in C-Code zu gefährden. So bietet Pre-Scheme immer noch anonyme Funktionen, (lokale) Endrekursion und die Standard-Scheme-Datenstrukturen. Dafür ist die Sprache statisch typisiert (mit Hindley-Milner-Typinferenz) und verwaltet Speicher nicht selbständig. Daß außerdem Continuations nicht unterstützt werden, versteht sich von selbst. Die Performance von kompiliertem Pre-Scheme-Code ist laut dem zugehörigen Paper sehr nah an dem von händisch geschriebenen C-Code, und die Kompilierstrategie scheint bis ins Detail ausgesprochen durchdacht zu sein.

An sich sieht Pre-Scheme bereits sehr überzeugend aus. Interessant ist jedoch auch, daß in den Kommentaren zu einem der beiden damit zusammenhängenden Reddit-Einträge (der andere ist der ältere) auf einige verwandte Projekte verwiesen wird, nämlich Schlep (ein weiteres abgespecktes Scheme), ThinLisp (ein abgespecktes Common Lisp), CLiCC (Common Lisp, ähnlich ThinLisp) und BitC (ein ML-inspirierter Ansatz).

Sieht so aus, als gälte es, doch noch einiges auszuprobieren.

Low-Level-Lisp

Der Wunsch: Systemnahes Programmieren mit Lisp

Nehmen wir an, wir möchten für ein kleines, in seinen technischen Fähigkeiten eingeschränktes Gerät Code schreiben, zum Beispiel für ein Handy. In diesem Fall wird man vor allen Dingen versuchen müssen, den Arbeitsspeicherverbrauch in engen Grenzen zu halten. Nehmen wir weiters an, wir wären ein leidenschaftlicher Lispprogrammierer. Welche Programmiersprachen eignen sich für uns in dieser Situation?

Erster Versuch: Scheme

Auf dem Scheme-Wiki liest man, Scheme habe im Gegensatz zu Common Lisp eine kleine Laufzeitumgebung, die leicht in C-Code einbettbar sei. Machen wir uns also zunächst auf die Suche nach einer passenden Scheme-Implementierung. Das scheint am erfolgversprechendsten.

Am sinnvollsten erscheinen zunächst die Implementierungen, die C-Code erzeugen. Diejenigen darunter, die eine einigermaßen hinreichende Auswahl an Bibliotheken bereitstellen, sind, wie es scheint, Chicken, Gambit und Bigloo. Besonders letztere Implementierung scheint C-nah zu sein, ein gutes Zeichen.

Kompiliert man die Implementierungen aber, so zeigt sich ein ernüchterndes Bild: Die Laufzeitumgebungen sind nicht gerade klein, wenn man von einem eingeschränkten Zielgerät ausgeht. Die Runtime von Chicken schlägt mit fast 4 MB zubuche, die von Bigloo mit über 4,5 MB und die von Gambit sogar mit rund 5,5 MB. Damit ist die Option Scheme wohl erledigt.

Zweiter Versuch: Common Lisp

Der Gedanke, den man an diesem Punkt hegen kann, ist: Wenn die Runtimes der Schemes ohnehin recht groß sind, können wir auch gleich Common Lisp verwenden. Das wird zwar nicht kleiner, aber zumindest komfortabler sein.

Wir laden also ECL herunter, das „einbettbare Common Lisp“, kompilieren es und sind erstaunt: Die Runtime ist gerade einmal 2 MB groß — um die Hälfte kleiner als das kleinste praktikable Scheme also. Wir vermuten, daß die Scheme-Seite bis dato einiges an Optimierungspotential ungenutzt gelassen hat.

Wie auch immer: Wir merken uns ECL vor, suchen aber weiter. Kann es wirklich sein, daß man mindestens 2 MB an Runtime mit sich herumschleppen muß, um ein wenig von C wegzukommen?

Dritter Versuch: Exotische Systemlisps

Wir landen auf unserer Suche nach einem möglichst C-nahen Lisp bei einigen Ansätzen, die versuchen, die Auslieferung fertiger Software zu vereinfachen, indem nicht zu weit von C weggegangen wird. Diese sind STELLA, eine Art Multicompiler für Lisp, der seinen Lispdialekt in Java, C++ und Common Lisp übersetzen kann, und Lush, ein speziell auf effiziente numerische Berechnungen ausgelegtes Lisp.

Auch, wenn sich beides ganz nett anhört, die Details machen uns erneut einen Strich durch die Rechnung: Wieder sind die Laufzeitumgebungen einfach zu groß.

Vierter Versuch: Back to the Roots

Langsam müde von der langen Recherche, widmen wir uns der letzten Idee: Wir suchen nach etwas, das so nah an C bleibt, wie möglich. Das heißt: Wir opfern Lispigkeit, um Systemnähe zu erhalten. Wir halten Ausschau nach etwas, das letztlich nur ein Präprozessor ist, der Code direkt und ohne Laufzeitumgebung in reinen, rohen C-Code übersetzt.

Es gibt mit Sicherheit einige Ansätze dazu. Wir beschränken uns auf die Lisp-nahen und finden zwei: C-amplify und SC.

C-amplify: Ambitioniert, und zwar zu sehr

C-amplify ist das ambitioniertere der beiden Projekte. Es versucht, alle Informationen zu sammeln, die es kriegen kann. Dafür nimmt es an, daß der C-Präprozessor nicht verwendet wird. Stattdessen muß der Benutzer dafür Sorge tragen, daß Deklarationen aller Funktionen und Datenstrukturen, auf die man zugreifen will, in für C-amplify verständlicher Form vorliegen.

Der Ansatz von C-amplify verhindert natürlich, daß C-Code direkt eingebettet werden kann, weil ansonsten die Informationen über Rückgabewerte und ähnliches verloren gehen könnten. Leider unterstützt C-amplify noch nicht alles, was in C möglich ist, weshalb das dazu führt, daß man manche Dinge einfach nicht tun kann. Außerdem ist es schwierig, Bibliotheken von Drittanbietern oder des Betriebssystems zu verwenden, wenn man den C-Präprozessor nicht ausnutzen kann. Was aber ist C ohne Bibliotheken? Richtig. Versuchen wir etwas anderes.

SC: Keep it Simple, Stupid

Wir sehen uns zuletzt also SC an. Auf den ersten Blick erinnert es uns sehr an C-amplify, nur mit einem kleinen, doch wesentlichen Unterschied: Die Ambitionen sind merklich kleiner.

SC versucht nicht, Typinferenz zu betreiben oder sonstige Daten über das zu kompilierende Programm zu sammeln, um Optimierungen durchzuführen. Es verlangt auch nicht, daß irgendwelche Informationen aus Header-Dateien dupliziert oder in das System eingelesen werden. Stattdessen macht es eine sture Übersetzung aus einer Lisp-artigen Syntax in die entsprechende C-Syntax, und expandiert nebenbei (prozedurale, lispige) Makros. Mehr tut es nicht, seinen Zweck erfüllt es dafür aber klaglos.

Wir schreiben Code wie diesen:

;;;; -*- mode: lisp -*-

(c-exp "#include <stdio.h>")
(c-exp "#include <stdlib.h>")

(def (main argc argv) (fn int int (ptr (ptr char)))
  (if (> argc 1)
      (printf "Hallo, %s!~%" (aref argv 1))
      (printf "Hallo, Welt!~%"))
  (return EXIT_SUCCESS))

Er wird übersetzt in das Folgende:

#include <stdio.h>
#include <stdlib.h>

int 
main(int argc, char **argv)
{
  if (argc > 1)
    printf("Hallo, %s!\n", argv[1]);
  else
    printf("Hallo, Welt!\n");
  return EXIT_SUCCESS;
}

Hübsch! Fehlt nur noch, daß man SC von der Befehlszeile aus aufrufen kann anstatt von Lisp aus, und auch das ist hinzubekommen, indem man folgende drei Befehle in die CLISP-Befehlszeile eingibt. Wir erzeugen dadurch ein Befehlszeilentool mit dem Namen „sc2c“:

(load "init.lsp")
(ext:saveinitmem "sc2c"
                 :norc t
                 :executable t
                 :init-function (lambda ()
                                  (let ((sc-main::*indent-options* '("-i2")))
                                    (mapc 'sc-main:sc2c ext:*args*))
                                  (ext:exit)))
(ext:exit)

Die Übersetzung durch SC ist einfach und effektiv. Wenn man ein Konstrukt findet, von dem man nicht weiß, wie man es in der Lispsyntax schreiben soll, oder wenn man es aus irgendwelchen Gründen schlicht lieber direkt in C ausdrückt, dann geht das mit der C-EXP-Form. Man kann SC leicht in seinem Makefile vor den C-Compiler schalten, es ist unintrusiv, es verlangt einem nichts ab, und es erfordert keine Änderungen an bestehendem Code. Kurz: Es ist ein Meisterwerk.

Ich werde mit SC auf jeden Fall herumexperimentieren. Vielleicht gewöhne ich mich daran. Vielleicht verwende ich es sogar bald produktiv. Man wird sehen.

Das bayerische Studentenwerk wird ab sofort kaputtgespart

Die Landes-ASten-Konferenz Bayern berichtet:

Während sich die soziale Situation der Studierenden durch die Einführung von Bachelor und Studiengebühren weiter verschärft hat, kürzt die Staatsregierung erneut die Gelder der Studentenwerke ...

„Es ist durchaus denkbar, dass das Kaputtsparen der Studentenwerke den Weg für ihre spätere Privatisierung ebnen soll. Solche Gedankenspiele gibt es unter anderem in den Reihen der FDP“, so Malte Pennekamp.

Nach der erfolgreichen Marodisierung der Post, der Bahn, der gesetzlichen Krankenversicherung, der gesetzlichen Altersvorsorge und vielen weiteren für die Gesellschaft lebensnotwendigen Institutionen soll es nun also auch den Studentenwerken an den Kragen gehen. Es wird hier wahrscheinlich genau wie dort ablaufen: Erst spart man die Einrichtung kaputt, dann erklärt man sie für defizitär (Überraschung!) und folglich nicht mehr finanzierbar, und zum Schluß folgt die Privatisierung. Natürlich wird der Staat die Einrichtung auch nach der Privatisierung noch mit Steuergeldern unterstützen, um sein Gesicht zu wahren, nur werden diese Gelder mehr in die Unternehmensstrukturen fließen, als daß sie wie bislang den Studenten zugutekämen.

Umleiten von Geld aus den Taschen derer, die es brauchen, in die derer, die genug haben, um sich ein ehemaliges Staatsunternehmen zu kaufen: Die rot-grün-schwarz-gelbe Einheitspartei nennt das „Fördern von Eigenverantwortung“.

Was man tun kann: Gegen das Vorhaben der Regierung gibt es eine Petition, in die man sich online eintragen kann. Es ist dafür (wie bei Petitionen üblich) nicht notwendig, betroffen (d.h. Student) zu sein.

„Aber was ist mit denen, die sich dann einfach durchfüttern lassen?“ — Eine Verteidigung des Rechts auf Faulheit

Wenn Müßiggang wirklich der Anfang aller Laster ist, so befindet er sich also wenigstens in der nächsten Nähe aller Tugenden; der müssige Mensch ist immer noch ein besserer Mensch als der tätige. — Friedrich Nietzsche: Menschliches, Allzumenschliches I

Wenn man versucht, seinen Mitmenschen die Idee eines Grundeinkommens oder gar einer Schenkungswirtschaft nahezubringen, ist das erste, was einem vorgehalten wird, stets der Einwand, jede solche neuartige Ökonomie, welche nicht auf Arbeitszwang beruhe, könne niemals gerecht sein, weil sie Menschen dazu befähige, „auf Kosten der anderen“ zu leben, d.h. sich von einem Grundeinkommen zu ernähren und von ihm zu profitieren, ohne eine Gegenleistung zu erbringen.

Keine Leistung ohne Gegenleistung? Aber was ist eigentlich eine „Leistung“?

Zunächst einmal finde ich es kurios, daß Gerechtigkeit, hier: die Berechtigung zur Nutzung der gemeinschaftlichen Dienste, zuallererst darüber definiert wird, was ein Mensch an Arbeit leistet. Ist ein Menschenleben denn nicht von sich aus schon wertvoll genug, daß eine würdevolle Behandlung ihm zustünde? Gibt es nicht darüberhinaus wesentlich wichtigere Eigenschaften eines Menschen als die des Fleißes, wie Einfühlsamkeit, Hilfsbereitschaft, Genügsamkeit und Friedlichkeit? Ich für meinen Teil kann die meisten allzu eifrigen Menschen jedenfalls nicht leiden — das freilich nicht aufgrund ihres Eifers, aber doch aufgrund der Eigenschaften, die erfahrungsgemäß mit Eifer einherzugehen pflegen wie Selbstgerechtigkeit, Hochnäsigkeit ob der eigenen errungenen oder angestrebten Erfolge, Engstirnigkeit, unkritischer Konformismus und die Verachtung alles und aller Andersartigen, welches und welche nicht in ihr Weltbild von der Brave New World passen, in der jeder Bettler und jeder Mensch beliebiger geistiger, körperlicher und sozialer Prägung zum Millionär werden kann, wenn er sich nur genug anstrengt. (Darauf, daß ein solches Weltbild völlig an der Realität vorbei geht, will ich heute gar nicht eingehen, ich setze es vielmehr als offensichtlich voraus.)

Doch selbst, wenn wir so täten, als gäbe es ein gottgegebenes Gesetz, das uns vorschreibt, jeder müsse für eine in Anspruch genommene Leistung auch eine entsprechende Gegenleistung erbringen — selbst, wenn wir das Leistungsprinzip also als Axiom unseres Wertesystems festschreiben — kann ich nicht nachvollziehen, inwiefern ein freieres System weniger erstrebenswert wäre als das unsrige. Sehen wir uns die Situation in unserem real existierenden kapitalistischen System doch einmal nüchtern an: Auf der einen Seite schuften Menschen tag und nacht und verdienen gerade mal einen Hungerlohn, und auf der anderen haben welche eine Position, in der sie kaum schwer arbeiten und doch Unmengen an Geld (also der angeblichen Meßgröße für „Leistung“) verdienen. So sieht die Realität aus.

Man mag nun diese schnöde empirische Argumentation damit abtun, daß man dem allen widerspricht und behauptet, sicher, vereinzelt möge das momentan ja stimmen, könne aber durch entsprechende Regulierungen behoben werden. In Wahrheit aber hält das Leistungsprinzip in einer rein marktwirtschaftlich organisierten Gesellschaft nicht nur der Empirie, sondern auch der Theorie nicht stand, und zwar gleich in zweierlei Hinsicht: zum einen nämlich in Hinblick auf die Lohnarbeit als Gegenleistung für in Anspruch genommene Leistungen, sowie zum anderen im Hinblick auf die Ergebnisorientierung des Marktes.

Mit dem ersten Punkt meine ich folgende Problematik: Nehmen wir an, jemand würde die Dienstleistung einer Krankenpflegerin in Anspruch nehmen und diesen dafür aus seinem Lohn bezahlen, welchen er für die Entwicklung von Software für eine Rüstungsfirma erhält. Hier stellt sich die Frage: Was, realwirtschaftlich betrachtet, hat die Krankenpflegerin für einen Nutzen von der Arbeit des Softwareentwicklers? Sie hat nur insofern einen Nutzen davon, als daß sie sagen kann: „dieser Mensch hat für meine Leistung selbst etwas geleistet“. Dieser „Nutzen“ ist aber rein virtuell. Real hat der Softwareentwickler nichts geleistet, was der Krankenpflegerin in irgend einer Weise materiell oder sonstwie etwas gebracht hätte. Insofern hat er gerade keine Gegenleistung erbracht, außer in Form des Geldes — welches in einem System mit Grundeinkommen aber genau im selben Maße von ihm zu ihr geflossen wäre.

Man könnte freilich sagen: Gut, der Softwareentwickler mag der Krankenpflegerin keinen direkten, doch aber einen indirekten Nutzen gebracht haben. Es ist dies allerdings wohl sehr anzuzweifeln, sobald die Arbeit eine ist, die weniger der (realen) Gesellschaft zugutekommt als einer wirtschaftlichen Entität wie dem Staat oder einer Firma; oder wenn sie gar von so einer Natur ist, daß sie nur der Aufrechterhaltung der Arbeitsmaschinerie selbst dient — und machen wir uns jeder neoliberalen Rhetorik zum Trotz nichts vor: Die meiste Arbeit fällt gerade in eine dieser beiden Klassen.

Das führt uns direkt zum zweiten Punkt, der Ergebnisorientierung im freien Markt. Produkte werden vom Kunden in einem funktionierenden Markt für einen Preis gekauft, der ihrem Wert für den Kunden entspricht. Ebenso wird ein Angestellter nach dem Wert des Ergebnisses seiner Arbeit bezahlt. Dieser Wert wird aber einerseits nicht gesamtgesellschaftlich gemessen, sondern stets in Bezug auf den Arbeitgeber; andererseits ist er losgelöst von der Mühe, die in der Arbeit steckt. So kommt es, daß der eine Mensch, der sich vom Morgen bis zum Abend mit aller Kraft unter den widrigsten Umständen abmüht, weniger bezahlt bekommen kann als der, der in einem wohlgeheizten Büro in angenehmer Atmosphäre verhältnismäßig leichte Denk- und Verwaltungsaufgaben übernimmt. Das heißt: Nirgends ist hier das Leistungs-Prinzip realisiert, sondern es handelt sich vielmehr um ein Ergebnis-Prinzip. Deshalb ist auch ein gewisser Slogan einer gewissen politischen Partei so zynisch: Es ist gar nicht die Leistung, die sich nach deren Vorstellungen „wieder“ lohnen soll (wo auch immer das Wörtchen „wieder“ in diesem Kontext herrühren mag), sondern das wirtschaftliche Ergebnis der Leistung.

Insofern ist es also sogar doppelt unangebracht, davon zu sprechen, in unserem System würden Leistungen mit Gegenleistungen erkauft. Die Absurdität geht aber noch wesentlich weiter als nur bis hierhin.

Das absurde Geschrei nach dem Recht auf Arbeit

Aus der neoliberalen Ideologie ersteht unmittelbar die Forderung nach einem Recht auf Arbeit. Der Gedankengang ist einfach: Wenn nur für eigene Leistung eine fremde Leistung genutzt werden darf, dann muß auch jeder die Möglichkeit erhalten, Leistung zu erbringen, weil sonst die Teilnahme an der Gesellschaft nicht mehr für alle ermöglicht werden kann.

Eine direkte Konsequenz des Rechts auf Arbeit ist, daß es stets ein großes Drama geben muß, wenn eine Firma sich im Untergang befindet. Rein marktwirtschaftlich gedacht, ist der Untergang eines Unternehmens nichts per se Schlechtes: Leistet die Firma nichts mehr, wofür die Gesellschaft zu zahlen bereit ist, hat sie ihren Zweck in der Volkswirtschaft und damit ihre Existenzberechtigung verloren. Die Arbeitsverwalter aber betonen wie in nahezu jedem anderen Kontext auch stets nicht das Volks-, sondern das Individualwirtschaftliche: Menschen verlieren durch die Auflösung einer Firma ihre Arbeit — also muß der Steuerzahler einspringen und ihr Gehalt bezahlen.

Das ganze Spiel ist für einen Außenstehenden natürlich vollkommen lächerlich, ja geradezu satirisch. Eine bestimmte Arbeit wird nicht mehr gebraucht — aus realwirtschaftlicher Sicht müßte man sich unheimlich darüber freuen! Schließlich bedeutet ein unnötig gewordener Arbeitsprozeß ja, daß die Gesellschaft jetzt die Möglichkeit hätte, jedem ihrer Mitglieder ein bißchen mehr Freizeit zu spendieren. Was wird aber stattdessen getan? Die, die noch Arbeit haben, müssen im Gegenteil für die Erhaltung der Arbeit bezahlen, das heißt: sie müssen mehr arbeiten, damit andere auch mehr arbeiten dürfen.

In der Dualität zwischen Individual- und Volkswirtschaft liegt die Lösung

Der Grund für das Funktionieren dieses absurden Prozesses, der einer gewissen Komik nicht entbehrt, ist, daß die von der Propaganda des Kapitals indoktrinierten Menschen in unserer Gesellschaft stets nur individualwirtschaftlich denken und die reale Volkswirtschaft außer Acht lassen. Was nämlich durchgehend unter den Tisch fällt, ist die Tatsache, daß jeder von uns vom Funktionieren der ganzen Gesellschaft abhängt. Jedes individuelle Stück Erfolg wird zu einem Teil von allen anderen in der Gesellschaft gespendet. Was wäre der Informatiker, wenn der Computer nicht erfunden worden wäre, was der Automobilhersteller ohne den Straßenbauer, den Tankwart, den Reifenhändler? Es gibt eben de facto keine reine Individualwirtschaft, sondern nur eine in die Gesellschaft eingebettete, individuell scheinende Situationswirtschaft.

Die Fixierung auf die (virtuelle) Individualwirtschaft hat zur Folge, daß ein Anwuchs des Volksreichtums ebensowenig den individuellen Wohlstand verbessert, wie ein Anwuchs der Produktivität durch Automatisierung einen positiven Einfluß auf die Freizeit des einzelnen Menschen hat. Was in beiden Fällen stattdessen passiert, ist, daß das Volk Güter und Dienstleistungen überproduziert, die dann an den Rest der Welt teuer verkauft, oder wie im Falle der Landwirtschaft einfach weggeworfen werden.

Der primäre Effekt ist offenbar, daß die Menschen von dem von ihnen selbst erwirtschafteten allgemeinen Wohlstand nichts haben. Das ist an sich bereits ungerecht. (Wir erinnern uns: Angebliche Gerechtigkeit ist das Hauptargument für die Beibehaltung dieses Systems.)

Es gibt aber noch einen sekundären, global gesehen sogar fataleren Effekt: Ein Volk, das überproduziert, überstrapaziert auch die natürlichen Ressourcen, wobei zusätzlich Müll und Abgase anfallen. Wenn einige sagen: „Die Gesellschaft kann sich keine Müßiggänger leisten“, dann ist darauf zu erwidern: Vielleicht, aber vor allem kann sich die Erde keine Übereifrigen leisten. Wer darauf hinweist, daß jeder Müßiggänger Kosten für die Gesellschaft erzeuge, den sollte man seinerseits darauf hinweisen, daß das auf jeden Arbeitenden genauso zutrifft. Nicht umsonst ist es so schwierig, beim Thema Umweltschutz alle unter einen Hut zu bringen: Schließlich gefährdet jede Umweltschutzmaßnahme, jedes Zurückdrehen industrieller Produktion den heiligen Volkswohlstand.

Das Grundeinkommen als Bindeglied zwischen Gesellschaft und Individuum

Die Idee des Grundeinkommens ist es nun, die Brücke zwischen Individual- und Volkswirtschaft zu schlagen, indem jeder einzelne am Volkswohlstand gleichermaßen beteiligt wird, und zwar bedingungslos. So wird das institutionalisiert, was ohnehin wahr ist, bei uns aber bislang nicht gewürdigt wird: die Abhängigkeit des Einzelnen von der Gesellschaft wie umgekehrt. Wer mit seinem eigenen Wohlstand nicht zufrieden ist, muß eben in entsprechendem Maße arbeiten gehen. Damit macht er (wie heute) zugleich die Gesellschaft reicher, was (im Gegensatz zu heute) wieder positiv im Grundeinkommen auf ihn zurückschlägt. Wer mit einem einfacheren Leben zufrieden ist, zehrt vom Volkseinkommen und schont die Umwelt, indem er minimalen Ressourcenverbrauch verursacht, was ebenfalls einen Dienst für die Gesellschaft darstellt. So soll sich ein sinnvolles, gesundes Gleichgewicht zwischen Wohlstand, Muße und Nachhaltigkeit einstellen.

In der Diskussion um ein Grundeinkommen geht es letztlich nicht darum, jegliche Arbeit abzuschaffen. Das Ziel ist vielmehr, das Wüten des irrationalen Arbeitswahns einzudämmen. Die Arbeit wird nicht auf 0 reduziert, sondern auf ein rationales, für die Welt wie auch für die freie Entfaltung eines jeden Menschen verträgliches Maß gedrosselt — ein Maß, das jeder von uns nur für sich selbst bestimmen kann.

Ein Rat für Android-Freunde: Wartet auf das Nexus One.

Seit gut einem Monat bin ich nun Besitzer eines Motorola Milestone, eines der bislang meistgehypten Android-Handys (die anderen sind, nehme ich an, das T-Mobile G1, das HTC Hero und das Google Nexus One).

An sich ist es ein sehr nettes Gerät. Die Konzepte, die dem Betriebssystem zugrundeliegen, sind interessant und gut durchdacht, die Präsenz einer Kamera mit LED-Blitzlicht ist eine feine Sache, und die Anzeige auf dem Bildschirm ist wunderschön und klar. Mit Android 2.0 bekommt man eine von Drittanbietersoftware momentan und mit etwas Glück auch mittelfristig gut unterstützte Version des Betriebssystems. Eigentlich könnte man rundum zufrieden sein...

...wäre da nicht die Sache mit den Updates. Motorola scheint das Milestone-Pendant in den USA, dort bekannt als Motorola Droid, schnell mit neuen Softwareversionen zu versorgen. Nachdem 2.0.1 schon seit über einem Monat für das amerikanische Gerät verfügbar ist, sollte man meinen, daß es auch langsam seinen Weg nach Europa finden würde, vor allem, wenn man bedenkt, wie gesegnet die 2.0-Version mit Fehlern ist. Leider dringen aus den Motorola-Firmengebäuden nur vage Versprechungen von einem bald erscheinenden Update heraus. Keine Angaben zum Erscheinungsdatum, nicht einmal die einer Größenordnung von Zeiteinheiten, die man noch warten müssen wird.

Man könnte jetzt aber natürlich fragen: Was ist eigentlich so schlimm daran, daß gerade für den Milestone die Updates mit so geringer Geschwindigkeit eintrudeln? Das Gerät funktioniert schließlich immer noch besser als viele andere Telephone (auch, wenn die Kamera intolerabel langsam ist und das Betriebssystem hin und wieder seltsame Dinge tut oder abstürzt, besonders mitten in Gesprächen). Und überhaupt: Die meisten Handys werden nach dem Erscheinen gar nicht upgedatet. Warum sind Updates fürs Milestone so wichtig?

App-Phones sind nicht „Smartphones“

Früher war die Welt für die Hersteller von mobilen Handfunkgeräten noch einfach: Gerät entwickeln, Software entwickeln, beides zusammenwerfen, ab damit zum Kunden, das Gerät vergessen, ein neues Gerät entwickeln, Software dafür und so weiter. Handys mußten nicht viel leisten, und das, was sie leisten mußten, war nicht allzu aufwendig. Außerdem waren die Geräte nicht ungeheuer teuer. In jedem Handy war ein bestimmtes Featureset fest eingebaut, und man konnte dem Kunden zumuten, für jede Funktion, die neu hinzukam, ein neues Handy zu kaufen.

Dann kam das iPhone, und alles änderte sich. Auf einmal war es nicht mehr das fest eingebaute Featureset, das das Telephon definierte, sondern das, was man dazuinstallieren konnte. Zugleich wurde die Anschaffung so teuer wie die eines einfachen PCs und damit zu einer Investition für mehrere Jahre. Das Handy, das zuvor ein integriertes Gerät gewesen war, wurde zur Plattform.

Nun haben es Plattformen so an sich, daß neue Versionen in der Regel auch neue Funktionen bieten, so daß Anwendungen, die für eine neue Version der Plattform geschrieben wurden, auf älteren Versionen der Plattform nicht mehr laufen. Softwareentwickler müssen sich entscheiden, welche Versionen der Plattform sie unterstützen. Für die Lebendigkeit der Plattform selbst ist es dabei von großer Wichtigkeit, daß neu eingeführte Features auch in den meisten Fällen ohne schlechtes Gewissen verwendet werden können. Nur so kann eine Weiterentwicklung stattfinden.

Apple löst dieses Problem auf die denkbar einfachste Weise: Sie sorgen dafür, daß die neueste Betriebssystemversion stets für die große Mehrheit der iPhone-Besitzer zur Verfügung steht. Selbst auf dem ältesten iPhone, das inzwischen schon zweieinhalb Jahre alt ist, läuft die aktuellste Version des iPhone OS (momentan 3.1.2). Nicht nur das, auch der iPod Touch trägt zur Ausbreitung derselben Plattform bei, indem auch auf ihm stets die aktuelle Version des iPhone OS verfügbar ist.

Natürlich wird ein und dasselbe Gerät nicht unendlich lange unterstützt werden. Aber zweieinhalb Jahre — darauf kann man sich bei Apple eben schon einmal einstellen. Das tut nicht nur der Plattform und den Entwicklern gut, sondern auch den Kunden. Wenn man heute ein iPhone der neuesten Generation kauft, kann man sich darauf verlassen, daß es eben nicht innerhalb eines halben Jahrs schon wieder überholt ist. Es mag dann nicht mehr zur Spitzenklasse gehören, vielleicht ein bißchen langsamer sein als das neueste Modell, aber so gut wie all neue Software wird darauf problemlos laufen. Ein iPhone ist eine sichere Investition.

Warum hat Google ein eigenes Handy herausgebracht?

Ich vermute, daß genau diese Plattform-Problematik es war, die Google dazu bewogen hat, ein eigenes Handy auf den Markt zu bringen. Zuvor hatten sie noch explizit angekündigt, eben dies nicht tun zu wollen. Google gab der Industrie das Werkzeug, um mit dem iPhone zu konkurrieren, und verließ sich darauf, daß diese auch begreifen würde, wie man das macht.

Die Handyhersteller jedoch wollten es nicht verstehen. Sie klammerten sich an ihr überholtes Geschäftsmodell, an ihren gewohnten Aktuelles-Handy-rausbringen-neues-Handy-entwickeln-altes-Handy-vergessen-Zyklus. Das Resultat: Ein Wirrwarr aus nicht upgradebaren Telephonen mit verschiedenen Android-Versionen, Anwendungen, die auf diesen Geräten laufen, aber nicht auf jenen, und frustrierte Käufer, die ewig auf dringend nötige Fehlerbehebungen warten müssen, kurz: eine Katastrophe für Entwickler und Endbenutzer gleichermaßen.

Um die Plattform nach vorne zu treiben, hat Google also notgedrungen das Ruder selbst in die Hand genommen. Wenn Googles Partnern dadurch jetzt Einnahmen verloren gehen und sie von Verrat und Markenmißbrauch zu sprechen anfangen sollten, kann man dem nur entgegenhalten, daß sie die Schuld nur bei sich selbst zu suchen haben. Schließlich hat Google ihnen die Gelegenheit gegeben, sich gegenüber Apple neu aufzustellen. Daß sie zu hochmütig waren, diese zu nutzen, zeigt nur, wie umsatzorientiert und kurzsichtig sie denken. Früher oder später werden sie mit Erschrecken feststellen, daß unmittelbarer Umsatz nicht alles ist. Nur wird es dann für die meisten von ihnen zu spät sein.

Und der Benutzer?

Für den gemeinen Benutzer bedeutet das: Wer ein Motorola-, Sony-Ericsson- oder Samsung-Handy mit Android kauft, wird sich darauf einstellen müssen, stiefmütterlich behandelt zu werden und in ein, zwei Jahren festzustellen, daß das Handy, das man sich gekauft hat, die meisten verfügbaren Programme nicht ausführen kann. Da die Anwendungen aus dem Android Market letztlich fast den ganzen Wert eines Android-basierten Geräts ausmachen, wird der betroffene Kunde enttäuscht sein. Die Plattform wird leiden, die Kunden werden leiden, und, wenn Letztere vernünftig sind, auch die Handyhersteller.

Mein Rat steht also fürs erste fest: Jeder Hersteller von Android-Geräten, bei dem nicht davon auszugehen ist, daß er seine Geräte über längere Zeit (d.h. allermindestens zwei Jahre) mit den neuesten Android-Versionen versorgen wird, ist zu meiden. Man mag hier einem unbeschriebenen Blatt einerseits den Vorzug des Zweifels geben (wie ich das mit Motorola tat); andererseits mag man argumentieren, daß bei Google auch nicht klar ist, wie sie die Updates des Nexus One handhaben werden. Dennoch: Im Moment sieht es danach aus, als wäre, wenn man die Updateproblematik in Betracht zieht (und das sollte man, da bin ich mir sicher), das Nexus One die bessere Wahl als der Motorola Milestone.

Chaosradio Express zu: Mut zur Freiheit

Folge 135 vom Chaosradio Express ist ein Interview mit Ilija Trojanow und Juli Zeh über Angst, Freiheit, den gegenwärtigen Abbau derselben, die Abhängigkeit einer funktionierenden Gesellschaft von jedermanns Vertrauen in seine Mitmenschen und was es alles an Argumenten gibt, mit denen man über die ganze Problematik Überzeugungsarbeit leisten kann.

Die Folge sollte jeder von vorne bis hinten anhören (die einzige erlaubte Ausrede wäre, daß man bereits das besprochene Buch gelesen hat).

Eindrücke vom Notizenprogramm Circus Ponies NoteBook

Ich bin beileibe kein Impulskäufer, aber hin und wieder gibt es bei MacUpdate Promo attraktive Angebote, für bzw. gegen die man sich schnell entscheiden muß. Diese Woche wurde unter anderem NoteBook von Circus Ponies Software angeboten. Da ich schon lange nach einer vernünftigen Methode suche, Notizen unter Mac OS X zu machen (unter GNU/Linux gibt es dafür Tomboy), habe ich das Programm nach kurzer Auffrischung meines Gedächtnisses bezüglich einer mehrere Monate zurückliegenden ausführlichen Web-Recherche kurzerhand erworben. (Eigentlich wäre eine solche Eile kaum nötig gewesen, da Studenten für NoteBook wohl generell nur unwesentlich mehr zahlen als bei der Promo-Aktion. Oh, well.)

Das Timing hätte jedenfalls kaum besser sein können, da ich für kommende Woche einen Vortrag vorbereiten mußte (zu dem übrigens jeder herzlich eingeladen ist!). So hatte ich just die Gelegenheit, die Praxistauglichkeit des Programms zu testen.

Mein Eindruck nach dem ersten Start des Programms war, sagen wir, unterbegeistert. Die Registerreiter am rechten Rand, deren Aufschriften ich aufgrund übermäßiger Transparenz nicht lesen konnte, wirkte ebenso befremdlich wie der linke Rand, der mit einem häßlichen Ringbuchdesign inklusive Papierlöcher geschmückt war. Ich brauchte eine Weile, um herauszufinden, wie man das Ringbuchdesign loswird.

Das automatisch generierte Inhaltsverzeichnis ist an sich eine feine Sache, aber leider läßt die Nutzbarkeit zur Navigation einiges zu wünschen übrig. Die Einträge sind nämlich nur jeweils am äußersten linken (Aufzählungspunkte) und rechten Rand (Seitenzahlen) klickbar, dazwischen landet man stattdessen im Editiermodus. Dafür gibt es zum Glück die Möglichkeit, eine andere, viel bessere Version des Inhaltsverzeichnisses als Ausziehkarte links an das Hauptfenster anzuhängen, was ohnehin der richtige Ort für ein solches Navigationselement ist.

Die Outlining-Funktion funktioniert sehr gut, und die hierarchische Organisierung von Ideen erscheint mir als recht natürlich, obgleich in so einem System freilich gegenüber einer eher Wiki-artigen Struktur Querverweise weniger natürlich sind. Im Kontext der Planung eines Vortrags aber eignet sich das Outlining hervorragend, um Ideen zu sammeln und am Ende auch noch einen nützlichen Spickzettel zu haben.

Sehr interessant ist die Idee, das Konzept der Haftzettelchen aus der realen Welt direkt zu übernehmen. Man kann sich kleine bunte Zettelchen aus der Toolbar ziehen und sie an den Rand der aktuellen Notizbuchseite kleben, so daß sie ein wenig herausschauen und auch von anderen Seiten aus sichtbar sind. Ein Klick auf ein Haftzettelchen bringt einen auf die Seite, zu der es gehört. Wenn man ein großes Notizbuch hat, sind die Haftzettelchen eine willkommene Möglichkeit, die Teile hervorzuheben, die momentan relevant sind.

Auch sehr passend finde ich, daß man mit einem Graphiktablett direkt auf die Notizbuchseiten zeichnen kann. Kleine Skizzen sagen oft mehr als tausend Worte, und abgesehen davon, daß sie aussagekräftiger sind, sind sie auch schneller gemacht — was bei einem so flüchtigen Ideengedächtnis wie meinem ein unheimlicher Vorteil ist.

Leider hat die Skizzenfunktion einige Schwächen. So wollte sie z.B. trotz funktionierenden Treibers (das Mac-OS-eigene InkPad hatte keine entsprechenden Probleme) die Druckkraft auf dem Stift partout nicht registrieren. Stattdessen waren alle Striche stets gleich dick. Außerdem — und das ist ein echter Wermutstropfen — befinden sich die Skizzen nicht auf derselben konzeptionellen Ebene wie Text. Das hat zur Folge, daß die Skizzen einerseits nicht mit nach unten rücken, wenn Text über ihnen eingefügt wird, und andererseits ihre Ausrichtung zum Text auch im Druck nicht beibehalten wird. So mußte ich in der Druckvorschau schmerzlich feststellen, daß mein hübsches kommutatives Diagramm an der völlig falschen Stelle auf dem Papier plaziert worden wäre. Die einzige Lösung für das Problem scheint zu sein, die Skizzen mit einem externen Programm oder InkPad zu tätigen, und sie dann als statische Bilder in das Notizbuch zu importieren — nicht sehr komfortabel, das.

Alles in allem fand ich NoteBook zumindest für die Vorbereitung des Vortrags ausgesprochen angenehm. Jedoch: Dafür allein hätte es auch ein reiner Outliner getan. Wie gut sich das Programm im täglichen Einsatz insgesamt schlägt, wird sich erst mit der Zeit herausstellen.

myBlogEdit — ein einfacher Desktop-Blogging-Client, der seine Arbeit tut

Vor einer Weile recherchierte ich Desktop-Blogging-Clients für meine nicht allzu hohen Ansprüche. Erst landete ich bei ecto, welches mir aber zu viele Probleme bereitete, als daß ich es guten Gewissens hätte kaufen können. Auf der anderen Seite waren mir die meisten Alternativen schlicht soviel Geld nicht wert, wie sie kosteten, besonders im Hinblick auf meine eher sporadischen Bloggingaktivitäten. Andere Programme wiederum beschränkten sich darauf, nur bestimmte Bloggingplattformen zu unterstützen, was ihre Verwendung von vornherein ausschloß.

Mittlerweile verwende ich myBlogEdit, ein hübsches, kleines Bloggingprogramm für € 14, das zwar keine Wysiwyg-Funktionen bietet, dafür aber aufgeräumt und ohne Nervereien daherkommt. Der HTML-Editor sieht nett aus und ist gut zu bedienen, und das Erstellen von Entwürfen für neue Artikel geht schnell von der Hand. Außerdem gibt es eine für Blogsoftware-Eigenbauer nützliche Fehlerkonsole. Vor allen Dingen aber erfüllt das Programm seine Pflicht ganz famos — und das ist letztlich, was zählt.

Die Welt der freien Software vor dem Scheideweg

MacMacken fragt in einem Kommentar auf Christophs Blog:

Gab es in den letzten Jahren wesentliche IT-Entwicklungen als freie Open Source?

Das ist ein guter Punkt. Es gab in der letzten Zeit durchaus einige interessante freie Entwicklungen, die an Relevanz gewonnen haben, z.B. LLVM, WebKit und Android. Diese wurden aber fast immer von Firmen vorangetrieben (in den genannten Fällen interessanterweise gerade Apple und Google, die mittlerweile bisweilen als die „neuen Microsofts“ gesehen werden).

Wirklich offene, Community-getriebene Entwicklungen, die innovativ sind, sind hingegen in der Tat selten; andererseits war das früher meinem Eindruck nach auch nicht anders. Der Unterschied zu früher ist, daß die kommerzielle Konkurrenz bezüglich technischer Qualität wesentlich stärker zugelegt hat als die freie Softwarewelt. Vor nicht allzu langer Zeit blieb einem nichts als GNU/Linux (und andere freie unixoide Systeme), wenn man ein robustes PC-Betriebssystem mit brauchbarer Benutzeroberfläche verwenden wollte. Mac OS X und Windows XP haben das geändert. Es blieb einem nichts als Firefox, wenn man einen benutzerfreundlichen Browser mit Tabs verwenden wollte (nun, und Opera, welches aber andere Probleme hatte). Safari und Internet Explorer 7 haben das geändert.

War an GNU/Linux an sich viel innovativ? An Firefox? An Apache? Mein Eindruck ist: nein. Diese Software war technisch gut umgesetzt, und auf diese technische Überlegenheit hat man vertraut. Firefox war der bessere Internet Explorer. Linux war das bessere Windows. Heutzutage heißt es, Mac OS X sei das bessere Linux. Damit hat Linux das eine Herausstellungsmerkmal verloren, das für den gemeinen Anwender die Attraktivität des Systems ausmachte.

Nicht, daß es nicht Versuche gegeben hätte, Akzente zu setzen. Leider nur sind alle Projekte, die sich trauten, neue Wege zu gehen, reihenweise grandios gescheitert. Man denke an Berlin/Fresco. Man denke an GNOME.

GNOME? Genau dieses. Erinnert sich noch jemand daran, wofür die Abkürzung eigentlich steht? GNOME — das GNU Network Object Model Environment. Der Name kündet von einer sagenhaften Welt, in der Dateien und Anwendungen keine Rolle mehr spielen, in der es nur Objekte gibt, in der die Smalltalk-Sicht der Dinge regiert — und das auch noch netztransparent. Viel ist von dieser großen Vision nicht übriggeblieben, und heute wird jede versuchte radikale Paradigmenwende von der endlosen Angstschürerei der Pragmatiker zutodediskutiert. „Wenn wir uns dermaßen anders verhalten als Windows, dann werden wir viele Anwender verlieren!“ Nein, schrecklich! Wir werden Anwender verlieren! Aber werden wir dafür nicht etwas gewinnen, das langfristig viel wertvoller ist — nämlich ein Profil? Etwas, das uns auszeichnet? Etwas, das diejenigen Anwender möglicherweise in Zukunft in Scharen zu uns treiben könnte, die mit dem ancien regime doch nicht so zufrieden sind, wie sie vielleicht jetzt glauben? Ist es wirklich so klug, sich gerade an den Menschen zu orientieren, die von unserer Philosophie eigentlich gar nichts wissen wollen und sich nur ein besseres Windows wünschen?

Apple hat für dieses Dilemma eine einfache und effektive Lösung: Sie ziehen ihre Vision stückchenweise durch — gut verdaulich, aber doch konsequent, und nicht in ganz so kleinen Häppchen, daß vom Wandel nichts mehr zu erkennen wäre. Die etablierten freie Systeme aber bekommen das nicht hin, weil sie sich nicht einmal auf irgendeine Vision einigen geschweige denn so effektiv ihre Kräfte bündeln können, wie eine diktatorisch organisierte Firma das kann. Der einzige Ausweg aus dem Teufelskreis des krankhaft status-quo-orientierten Microsoft- und Apple-Kopierens wäre, denjenigen Anwendern, die sich von Windows nicht weggewöhnen wollen, ihr geliebtes Windows eben zu gönnen.

Das wäre doch a priori eigentlich der große Vorteil von freien Projekten: Ihr Erfolg hängt gar nicht im selben Maße von der Zahl der Anwender ab wie das eines kommerziell betriebenen Projekts. Sie können rein ergebnis- statt kundenorientiert arbeiten, ohne großen Schaden dabei zu nehmen. Anstatt aber eine Revolution einzuleiten, beugen sie sich dem Establishment und den von ihm geschriebenen Gesetzen, indem sie in das Geschrei nach „Marktanteilen“ einstimmen — ein Spiel, das sie angesichts der anarchischen Struktur der Community nicht gewinnen können. Man riskiert mit diesem Handeln im besten Fall ohne Grund die völlige Irrelevanz freier Software und ihrer Philosophie außerhalb einer eingeschworenen Community. Im schlimmsten Fall läßt man sowohl die Community selbst als auch ihre Produkte sich so weit von ihren ursprünglichen Idealen entfernen, daß ihre Mitglieder keinen Sinn mehr in ihr sehen und sie sich selbst auflöst.

Wer weiß: Mit etwas Glück entwickelt die Community bald ein Bewußtsein für ihre eigene Rolle auf der Suche nach Unabhängigkeit und Freiheit. Möglicherweise hat ein Schwinden der Windows-Flüchtlinge durch die (gerüchteweise) hohe technische Qualität von Windows 7 sogar eine heilende Wirkung auf sie. Falls nicht — falls man sich vom Buhlen nach „Switchern“ trotz allem nicht befreien kann — wird Desktop-Linux die kommende Dekade vielleicht nicht überleben.

Syntaxhervorhebung von Lisp-Code im Web

Wie dem einen oder anderen sicher aufgefallen ist, bietet dieses Journal neuerdings Syntaxhervorhebung, unter anderem für Code in Lisp. Zu diesem Zweck verwende ich den Google Code Prettifier. Die Syntaxbeschreibung für Lisp allerdings habe ich ein wenig angepaßt, da sie mir nicht von einem praktizierenden Lisper geschrieben zu sein schien und entsprechende Probleme aufwies. Man darf mir dies bei Bedarf gerne gleichtun und den Inhalt der Datei lang-lisp.js durch folgenden Code ersetzen:

// Copyright (C) 2008 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

PR.registerLangHandler(
    PR.createSimpleLexer(
        [
         ['opn',             /^\(|\[/, null, ''],
         ['clo',             /^\)|\]/, null, ''],
         // A line comment that starts with ;
         [PR.PR_COMMENT,     /^;[^\r\n]*/, null, ';'],
         // Whitespace
         [PR.PR_PLAIN,       /^[\t\n\r \xA0]+/, null, '\t\n\r \xA0'],
         // A double quoted, possibly multi-line, string.
         [PR.PR_STRING,      /^\"(?:[^\"\\]|\\[\s\S])*(?:\"|$)/, null, '"']
        ],
        [
         [PR.PR_KEYWORD,     /^(?:block|c[ad]+r|catch|cons|defun|do|eq|eql|equal|equalp|eval-when|flet|format|go|if|labels|lambda|let|load-time-value|locally|macrolet|multiple-value-call|nil|progn|progv|quote|require|return-from|setq|symbol-macrolet|t|tagbody|the|throw|unwind|loop|with--*(?:[a-z_]|\\[\x21-\x7e])(?:[\w-]*|\\[\x21-\x7e])[=!?]?|define--*(?:[a-z_]|\\[\x21-\x7e])(?:[\w-]*|\\[\x21-\x7e])[=!?]?|for|in|from|to|fn|defn|def)\b/, null],
         [PR.PR_LITERAL,
          /^[+\-]?(?:0x[0-9a-f]+|\d+\/\d+|(?:\.\d+|\d+(?:\.\d*)?)(?:[ed][+\-]?\d+)?)/i],
         // A single quote or a colon possibly followed by a word that optionally ends with
         // = ! or ?.
         [PR.PR_LITERAL,
          /^(\'|\:)(?:-*(?:\w|\\[\x21-\x7e])(?:[\w-]*|\\[\x21-\x7e])[=!?]?)?/],
         // A word that optionally ends with = ! or ?.
         [PR.PR_PLAIN,
          /^-*(?:[a-z_]|\\[\x21-\x7e])(?:[\w-]*|\\[\x21-\x7e])[=!?]?/i],
         // A printable non-space non-special character
         [PR.PR_PUNCTUATION, /^[^\w\t\n\r \xA0\"\\\';]+/]
        ]),
    ['cl', 'el', 'lisp', 'scm']);

Stilistische Unterschiede zwischen Clojure und Common Lisp anhand von HTML-Generierung

Wenn man davon absieht, daß Clojure für die JVM entwickelt wurde, sehen es und Common Lisp einander sehr ähnlich. Das Makrosystem ist dasselbe, beide unterstützen interaktive Programmierung mit SLIME, und wenn man sich Module (in diesem Fall Webanwendungsframeworks) wie Hunchentoot auf der einen Seite und Compojure auf der anderen ansieht, kommt man nicht umhin, die Parallelen zwischen den beiden Sprachen anzuerkennen. Schließlich sind die Unterschiede zwischen einer Hallo-Welt-Webanwendung in der einen Umgebung und in einer in der anderen höchstens oberflächlicher Natur, und die grobe Programmstruktur ist in den beiden Fällen nicht wesentlich unterschiedlich.

Ein bißchen anders sieht es aus, wenn man in die Details geht. Hier äußert sich nämlich der große stilistische Unterschied, den man im Groben nicht sehen kann: Während auf der einen Seite in Clojure ein funktionalen Stil sowohl im Großen als auch im Kleinen üblich ist, hat man auf der anderen Seite mit Common Lisp eine Sprache, die zwar funktionale Schnittstellen, gleichwohl aber imperative Implementierungen bevorzugt.

Um ein bißchen klarer zu machen, wie sich diese subtile Unterschiedung konkret äußert, kann man als Beispiel das Generieren von HTML-Code betrachten, welches in Webanwendungen häufig vorkommt.

Nehmen wir den typischen imperativen Common-Lisp-Code, der eine kleine HTML-Seite mit einer Tabelle von Studenten mitsamt Hausaufgabenpunkten erzeugt. Hier seien students eine Liste von Studenten, die jeweils einen Namen (name) und eine Sequenz von Punkteeinträgen (score) besitzen. Wir verwenden im folgenden die Yaclml-Bibliothek, welche im Stil der Verwendung sehr Common-Lisp-typisch ist.

(defun list-students (students)
  (with-yaclml-output-to-string
    (<:html
      (<:head (<:title "Punkteliste"))
      (<:body
        (<:h1 "Punkteliste")
        (<:table
          (<:tr (<:th "Name") (<:th "Punkte"))
          (loop for student in students
                do (<:tr
                     (<:td (<:as-html (student-name student)))
                     (<:td (<:as-html (format nil "~A" (student-score student)))))))))))

Wie wir sehen, handelt es sich um ein imperatives Programm. Die HTML-Tags werden durch Makros erzeugt, die, da sie Lisp-Code sind, beliebig mit anderem Lisp-Code gemischt werden können. So macht es keinen Unterschied, ob wir die <:TR-Anweisung oder die LOOP-Schleife innerhalb des <:TABLE-Ausdrucks betrachten -- die Verarbeitung erfolgt stets gleich, nämlich als Programmcode. Genauso können wir ein <:TR als Anweisung innerhalb der Schleife ausführen.

In anderen Worten: Yaclml stellt uns Anweisungen zur Verfügung, die HTML-Tags ausgeben. Das ist ein zutiefst imperatives Konzept. Unsere list-students-Funktion allerdings hat eine funktionale Schnittstelle: Wenn man sie aufruft, erhält man als Ergebnis einen String mit dem HTML-Code (dafür sorgt das Makro with-yaclml-output-to-string, das den entsprechenden Kontext für die Codeausführung auf- und am Ende wieder abbaut); Nebenwirkungen hat die Funktion keine.

Man beachte, daß im imperativen Code oben zu keinem Zeitpunkt eine Baumstruktur des zu generierenden HTML-Codes existiert. Der Code wird ausgeführt und gibt nebenbei Zeichenketten aus, aber das geschieht linear. Die HTML-Baumstruktur ist nur durch die Schachtelung des Codes gegeben und wird nie explizit gemacht.

Betrachten wir nun den entsprechenden Code in Clojure unter Verwendung der HTML-Generierungsfunktionalität des Compojure-Frameworks.

(defn list-students [students]
  (html
   [:html
    [:head [:title "Punkteliste"]
    [:body
     [:h1 "Punkteliste"]
     [:table
      [:tr [:th "Name"] [:th "Punkte"]]
      (map (fn [student]
             [:tr
              [:td (:name student)))
              [:td (str (:score student))]])
           students)]]]))

Was passiert in diesem Codeschnipsel? Erst einmal wird ein Baum in Form einer verschachtelten Liste aufgebaut. Der map-Aufruf erzeugt eine Sequenz von Listen, die in die :table-Liste eingefügt werden. Am Ende wird alles einer Funktion mit dem Namen html gegeben, welche aus der Baumstruktur den fertigen HTML-Code konstruiert.

Auch der Clojure-Code mischt Code mit HTML-Struktur, aber auf eine andere Weise. Anstatt einen großen Haufen Code zu schreiben, der sich zufälligerweise zum Teil wie die Daten liest, die am Ende herauskommen sollen, macht Clojure die Trennung von Code und Daten expliziter. Code sind hier nur der map- und der str-Aufruf sowie der Aufruf der html-Funktion ganz außen. Alles andere besteht aus Listen, die nicht als Programme ausgewertet werden.

Es ist zu bemerken, daß die Syntax von Clojure hier eine wesentliche Rolle spielt. Gäbe es die Vektor-Syntax mit den eckigen Klammern nicht, so wäre der obige Code wesentlich umständlicher zu schreiben, da man Auswertung und Nichtauswertung der Baumstruktur explizit abwechseln müßte.

Genau diesen Ansatz verfolgt eine andere in der Common-Lisp-Welt beliebte HTML-Generierungsbibliothek namens CL-WHO. Der obige Code würde basierend auf ihr wie folgt aussehen:

(defun list-students (students)
  (with-html-output-to-string
   (:html
    (:head (:title "Punkteliste")
    (:body
     (:h1 "Punkteliste")
     (:table
      (:tr (:th "Name") (:th "Punkte"))
      (loop for student in students
            do (htm (:tr
                     (:td (esc (student-name student))))
                     (:td (esc (format nil "~A" (student-score student)))))))))))

Hier wird also die Listenstruktur wieder explizit gemacht, aber nicht so, daß lokal syntaktisch klar ist, wo und wo nicht. Die einzige Möglichkeit, Code von Nichtcode zu unterscheiden, ist hier, in die jeweiligen Strukturen hineinzusehen und das Symbol zu betrachten, das die Liste einleitet (ist es ein Schlüsselwort, d.h. ein Symbol mit Doppelpunkt vorne dran, handelt es sich um Daten; außerdem handelt es sich um Daten, wenn das ganze Ding ein String ist; ansonsten handelt es sich um Code); zusätzlich muß man ein paar Ebenen nach oben gehen, um zu sehen, ob man überhaupt in einem CL-WHO-Kontext ist (d.h. innerhalb von htm oder with-html-output) -- denn sonst muß man alles als Code interpretieren und nichts als Daten.

Mag sein, daß ich hier mit meiner Meinung allein auf weiter Flur stehe, aber man erlaube mir die Bemerkung, daß mir diese undurchsichtige Vermischung von Code und Daten nicht gerade erstrebenswert anmutet.

Unter demselben Problem leiden aber weder die rein imperative noch die funktionale Variante. Beiden ist eine gewisse elegante Einfachheit und Eindeutigkeit gemein -- obschon sie prinzipiell entgegengesetzte Ansätze verfolgen. Konsequentheit wiegt eben schwerer als Philosophie.

Backups auf einen Dateiserver

Mit Time Machine hat Apple etwas Kurioses bewerkstelligt: Da gab es plötzlich ein System, das vielen tausenden von Anwendern nicht nur die Möglichkeit gab, Backups zu machen, sondern sie sogar dazu brachte, das auch zu tun. Leider hat es zwei große Schwachstellen: Erstens existiert es nur für Mac OS X, und zweitens ist es nicht in der Lage, Backups über ein Rechnernetz auf einen Server zu legen (außer freilich dieser Server ist eine von Apple verkaufte Time Capsule oder ein Macintosh).

Wenn man im Haushalt mehrere Computer am Laufen hat, ist der Gedanke naheliegend, einen zentralen Server für alle Backups einzurichten. Auch ist es leichter, auf einen Server zu backuppen, wenn man zwischen verschiedenen Wohnorten hin- und herwechselt (wie viele Studenten das regelmäßig zum Beginn und zum Ende der Vorlesungszeit tun) oder häufig unterwegs ist, da es bei entsprechender Konfiguration (z.B. über ein VPN) möglich ist, Backups auch über das Internet durchzuführen.

Wenn Time Machine nun für diesen Zweck also nicht zu gebrauchen ist, was sind dann gute Alternativen?

rsync

Da wäre erst einmal das gute, alte rsync. Eine eher unbekannte Funktion dieses mächtigen Programms besteht darin, beim Synchronisieren ein bestehendes Verzeichnis (ein früheres Backup) als Ausgangspunkt zu nehmen und ein neues Verzeichnis mit dem neuen Inhalt anzulegen, wobei unveränderte Dateien mithilfe von Hardlinks nur ein Mal auf der Platte abgelegt werden. Das Resultat ist, daß man mehrere vollständige Backups vorhalten kann, die aber vom Platzverbrauch her inkrementellen Backups entsprechen.

rsync allein macht freilich noch kein automatisches Backupsystem. Mit ein wenig Skripterei sollte es jedoch machbar sein, sich ein vollständiges Backupsystem damit zu bauen, das sich im Großen und Ganzen genau so verhält wie Apples Time Machine.

Ein Nachteil von rsync ist, daß erweiterte Metadaten zu Dateien, wie sie zum Beispiel wichtigerweise auf Macs in Form von Resource Forks vorkommen, nur dann gespeichert werden können, wenn das Zieldateisystem sie auch unterstützt. Speziell für die Sicherung von Mac-Metadaten auf ein inkompatibles Dateisystem gibt es jedoch einen Patch namens rsync+hfsmode.

rsnapshot

Einen Schritt weiter geht rsnapshot. Es ist bereits als Time-Machine-ähnliches Backupsystem konzipiert und nimmt dem Anwender daher weit mehr ab als rsync allein. Die Einrichtung von rsnapshot ist sehr einfach und schnell erledigt. Leider hakt es an einer Kleinigkeit: Übers Netzwerk kann man zwar Backups anfertigen, aber leider nur vom Server aus. Dieser muß sich dazu durch paßwortlosen Login an dem Rechner anmelden, für den ein Backup gemacht werden soll — und die entsprechenden Zugriffsrechte haben, um auf alles zuzugreifen, was in das Backup hineingehört. Eine solche Konfiguration kann ein nicht zu verachtendes Sicherheitsrisiko darstellen. Schließlich könnte so jemand, der den Backupserver knackt, jeden Rechner infiltrieren, der für Backups vorbereitet ist. Viele weitere Backupsysteme, die sich am Prinzip des inkrementellen Backups durch Hardlinks orientieren, weisen dieselbe Schwäche auf.

rdiff-backup

Einen etwas anderen Ansatz verfolgt rdiff-backup. Im Gegensatz zu den hardlinkbasierten Systemen behält es nur das jüngste Backup als vollständigen Verzeichnisbaum, während zu älteren Datenbeständen die Unterschiede zum jeweils nächsten Backup gespeichert werden. Diese Vorgehensweise ermöglicht einfachen Zugriff auf das neueste Backup und eine platzsparende Sicherung älterer Daten. Es ist allerdings nicht so einfach möglich wie in den hardlinkbasierten Systemen, ältere Datenbestände zu löschen, ohne auch alle früheren unbrauchbar zu machen.

Um mit rdiff-backup Backups über ein Rechnernetz erstellen zu können, muß auf dem Server die passende Version von rdiff-backup installiert sein. Da es sich dabei um ein Python-Programm mit zusätzlichen Abhängigkeiten handelt, das nicht sehr weit verbreitet ist, kann das unter Umständen zu Problemen führen.

gibak

Übers Netzwerk ist es manchmal schwierig, sich das verwendete Dateisystem auszusuchen. Je nach Situation kann es sein, daß man mit Zugriffsmethoden wie sshfs, FTP, WebDAV oder CIFS auskommen muß, die unter anderem keine Hardlinks unterstützen. Ein System, das über das Dateisystem eine Ebene legt, die konzeptionell auf Hardlinks basiert, diese aber in den unteren Ebenen nicht voraussetzt, ist git.

git ist am besten als Versionsverwaltungssystem für Programmcode bekannt, eigentlich aber ist es im Kern nicht mehr und nicht weniger als ein Verwaltungssystem für Datenpakete (lies: Dateien), die in Bäumen (lies: Verzeichnissen) organisiert sind. Ein offensichtlicher Kandidat für ein Backupsystem also, besonders in Verbindung mit den Möglichkeiten der Versionsverwaltung, die git mitbringt.

gibak macht aus git ein solches Backupsystem. Man kann Backups lokal anfertigen und sie dann mit dem in git eingebauten push-Befehl auf beliebig viele Server verteilen. Alternativ dazu kann man die git-Datenbank auch gleich in ein Verzeichnis auf einem Server legen. Das Schöne an dem System ist, daß es einerseits transparent und leicht zu durchschauen ist, andererseits Daten aber sehr wirkungsvoll komprimiert werden, um Platz zu sparen und das Netz zu schonen. Außerdem ist es bei von git verwalteten Datensätzen stets möglich, die Geschichte umzuschreiben, und zwar insbesondere auch, einzelne Versionen aus der Versionsgeschichte zu entfernen, ohne Gefahr zu laufen, andere Versionen destruktiv zu beeinflussen.

Der Nachteil von gibak gegenüber Time-Machine-ähnlichen Systemen ist natürlich, daß Letztere Backups erzeugen, die man ohne jegliche Hilfsmittel in vollem Umfang lesen kann (soweit man in der Lage ist, das Dateisystem zu verstehen). Möchte man von einem Backup vielleicht in 10 Jahren noch etwas haben, ist das ein wichtiger Punkt, den man bei der Wahl eines Backupsystems auf jeden Fall berücksichtigen sollte.

ecto — Sinn und Unsinn eines Desktop-Blogging-Clients

Nachdem ich das Atom-API für mein Journal implementiert hatte, konnte ich endlich einen Blick auf ecto werfen, ein angesehenes Blogging-Programm für Mac OS X, das mit knapp € 15 recht erschwinglich ist.

Ich lud mir die 21-Tage-Testversion auf den Rechner. Daß ecto beim ersten Start meinte, ich hätte nicht mehr 21, sondern nur noch 20 Tage zum Testen übrig, nahm ich ihm nicht übel. Was ist schon ein Tag?

Das Programm begrüßt mich mit einer angenehm minimalistischen, aufgeräumten Oberfläche. Was mir etwas zu denken gibt, ist, daß in der Liste der Blogeinträge die Umlaute in den Artikelnamen ausgelassen werden. In den Vollansichten der Artikel scheint es mit ihnen aber zumindest im Fließtext selbst keine Probleme zu geben.

Ich verwende ecto also zum Testen meiner Atom-Implementierung, bessere hier und dort etwas in dieser nach — daß ecto ein hübsch eingefärbtes Protokoll der Kommunikation mit dem Server anbietet, ist dabei sehr hilfreich — und nebenbei schreibe ich einen Blogeintrag über das Atom-API. Alles funktioniert ganz wunderbar, bis ecto plötzlich meint, sein kleines Gehirn von den Früchten meiner Arbeit befreien zu müssen. Ganz spontan löscht es den gesamten geschriebenen Text und läßt nur die Überschrift übrig. Alle Versuche, mit Tastenkürzeln und Menüklicks das Geschehene rückgängig zu machen, scheitern, der Punkt „Widerrufen“ im Menü hat keine Wirkung.

Gereizt schließe ich das Fenster, in dem eben noch mein sorgfältig ausformulierter Artikel zu sehen war. Doch das scheint ecto nicht zu gefallen. Seinerseits gekränkt, stürzt es ab.

Ich hätte zu dem Zeitpunkt aufgegeben, wenn ich eine gute Alternative zu dem Programm gekannt hätte. Leider war das nicht der Fall. Ich starte ecto also neu.

Denkste. ecto will nicht mehr starten. Ich möge doch bitte die Vollversion kaufen, heißt es. Bitte? Nach nicht einmal einem Tag des Testens hört die 21-Tage-Testversion auf, zu funktionieren? Nachdem ich keine verwertbaren Daten mehr von ecto verwaltet weiß (auch grep konnte keine verschollenen Textfragmente mehr ausfindig machen), lösche ich kurzerhand alles in meinem Benutzerordner, was mit dem Programm zu tun haben und es irgendwie verwirren könnte, und siehe da: Es läßt sich doch wieder zum Arbeiten bewegen.

Ich beginne, meinen Artikel neu zu schreiben. Zur Sicherheit kopiere ich ihn hin und wieder nach TextEdit. Immerhin: Drag'n'Drop von Text inklusive Formatierungen und Verknüpfungen funktioniert tadellos. Schließlich klicke ich auf „Veröffentlichen“, und der Artikel erscheint — welche Freude! — auf dieser Website.

Leider muß ich feststellen, daß der dortige Link in die Wikipedia nicht korrekt gespeichert wurde. Ein unmaskiertes „und“-Zeichen im Verweis — das Problem kenne ich. Auch, wenn es mich wundert, daß sich ecto darum nicht selbst kümmert, stört es mich nicht besonders. Muß ich beim Hereinkopieren von Links eben achtgeben. Ich versuche also, den bestehenden Text wieder zu öffnen—

Puff! Ein Fenster öffnet sich. ecto hat den Fehler in dem Link entdeckt und bietet mir an, ihn auszubessern. Ich akzeptiere und überprüfe das Ergebnis, doch von der Korrektur sehe ich keine Spur. Der Link sieht aus, wie ich ihn eingegeben habe. Ich setze den Cursor an die Stelle des Fehlers und möchte den Link ausbessern, aber ecto funkt mir mit einer seltsamen Autovervollständigungsfunktion dazwischen, die mir den Text im Verweisbearbeitungsfenster völlig ruiniert. Nach mühevoller Handarbeit bekomme ich den Link wieder so hin, wie er sein soll, diesmal mit Maskierung des problematischen Zeichens (genauer gesagt: mit dem Zeichen umschrieben als HTML-Entität). Leider hat ecto auch noch irgend etwas anderes an meinem Text fehlinterpretiert und vor jedem Link die Leerzeichen gelöscht. Ich korrigiere das schnell und klicke erneut auf „Veröffentlichen“.

Spaces are missing in front of links.

Das Ergebnis kann sich sehen lassen. Auf der Website paßt alles, der Link ist ausgebessert. Was ich allerdings übersehen habe, ist ein Tippfehler im Beschreibungstext desselben Links. Also noch einmal zu ecto gewechselt und den Artikel angeklickt...

ecto displays an error message... again.

ecto zeigt mir das Fehlerfenster von vorhin ein weiteres Mal an. Was? Der Fehler ist doch ausgebessert, und sonst kommt nirgends mehr eines der gemeinen „und“-Zeichen vor. Was will ecto von mir? Ich sage ecto, es solle das mit der Korrektur lieber bleiben lassen. Ich klicke auf den Link, um den Tippfehler zu beseitigen, und was muß ich sehen? Der Fehler von zuvor ist zurückgekehrt! Ich schließe das Fenster, öffne es noch einmal, lasse ecto diesmal die Korrektur übernehmen — kein Unterschied! ecto hat den Fehler erneut in den Link eingebaut, nur um sich sogleich darüber zu beschweren und wirkungslose Autokorrekturhilfen anzubieten. Was in Hesindes Namen..?! Ganz zu schweigen davon, daß ecto schon wieder die Leerzeichen vor all meinen Links entfernt hat.

In contrast to what ecto is claiming, what needs to be escaped, is escaped.

Langsam sinkt meine Stimmung in ungeahnte Tiefen. Auf den ersten Blick sieht ecto so schön, einfach und elegant aus. Warum muß es nur voller Programmierfehler sein?

Ich weiß nicht, was beim Hersteller von ecto schiefläuft. Sicher ist allerdings, daß das Programm nicht das wunderbare Werk von Eleganz und Produktivität ist, als das es in einigen Testberichten dargestellt wird. Ob ich es mir angesichts fehlender Konkurrenz trotzdem kaufen werde? Ich weiß es noch nicht. Die Sache mit den verschwindenden Leerzeichen läßt sich umgehen, indem man in den Feed nicht XHTML direkt einbettet, sondern stattdessen maskierten HTML-Code verwendet. Nicht unbedingt schön, dazu ohne Grund gezwungen zu werden, aber keine Katastrophe. Die anderen Probleme bleiben aber auch in diesem Fall bestehen. In einem Programm meine Texte zu schreiben, das deren wesentliche Teile spontan vergißt, ist jedenfalls kein besonders angenehmes Gefühl. (Ich bin übrigens nicht der einzige, der Opfer dieses Bugs wurde.)

Implementierung eines Atom-basierten Webdienstes

Es ist schon praktisch, ein eigenes Desktop-Blogging-Programm wie ecto zur Verfügung zu haben, um sein Webjournal mit Inhalt zu füllen. Man kann verschiedene Entwürfe speichern, hat mit einem schnellen Klick eine Korrektur an einem älteren Artikel durchgeführt und kann all seine gewohnten Tastenkürzel zum Schreiben verwenden. Gar nicht sprechen will ich von der Einfachheit, mit einem solchen Programm ein Bild oder andere Dinge in einen Artikel einzubinden und sie ohne Zutun mit auf den Server laden zu lassen.

Leider steht dem ganzen Komfort möglicherweise eines im Wege: die Weblog-Software auf dem Server. Wenn sie nicht darauf ausgelegt ist, Daten von externen Programmen zu empfangen, muß man es ihr erst einmal beibringen. Das ist aus mehreren Gründen nichttrivial.

Zum einen muß man sich vor allem anderen durch mehrere Spezifikationen wühlen, die mehr oder weniger gut zu verdauen sind. Da wäre erst einmal — natürlich — das Atom-API, das den Kern der ganzen Angelegenheit darstellt. Über das Atom-API kommunizieren Client und Server. Sieht man sich die Protokoll-Spezifikation einmal an, wird man womöglich völlig von ihr überwältigt. Nicht, daß sie besonders lang wäre, aber an so gut wie jeder Stelle werden eigens für Atom erfundene und häufig nicht gerade intuitiv begreifbare Begriffe verwendet, deren Definitionen man sich aus diversen anderen Dokumenten zusammenklamüsern muß. Das macht nicht gerade Freude, wenn das eigentliche Ziel, das man vor Augen hat, lediglich ist, sich das Schreiben etwas leichter zu machen.

Glücklicherweise trügt der erste Anschein. Wendet man sich nämlich von der Spezifikation ab und schaut sich im Six Apart Atom Resource Center um, erscheint plötzlich alles ganz einfach — und die freudige Nachricht ist, daß dieser neue Eindruck im großen und ganzen auch tatsächlich stimmt. Das Atom-API selbst ist schnell implementiert, und so hat man auch nach kurzer Zeit einen Server implementiert, der es versteht. Nicht einmal besondere Bibliotheken sind erforderlich. Die einfachst denkbaren XML-Parser wie z.B. xmls tun es. Im Gegensatz zu ähnlichen Protokollen basiert Atom nämlich nicht auf komplexeren Techniken wie XML-RPC.

Leider steckt der Teufel im Detail. Da wäre einmal das Problem mit dem ungültigen XML. Ein einzelner Blogeintrag mit einem ungeschlossenen Tag kann die ganze Aktion zum Stehen bringen. Ein Glück, daß es Werkzeuge wie nXML gibt, mit denen sich Fehler in XML-Dokumenten schnell finden lassen.

Und dann die Geschichte mit der Anmeldung: Man möchte ja meinen, daß man sich mit einem Apache mit Digest-Anmeldung oder einer SSL-Verbindung keine großen Gedanken über die Anmeldungsprozedur machen muß. Dem ist leider nicht so. Aus guten Gründen haben sich die Atom-Entwickler dazu entschieden, ein eigenes Anmeldeverfahren namens WSSE zu verwenden, welches die wenigsten Webserver beherrschen dürften. Da es als kryptographisches Verfahren nicht ohne einige einfache Kodierungs- und Wertstreuungsverfahren auskommt, muß man für seine Implementierung dann möglicherweise doch die ein oder andere kleine Bibliothek einbinden.

Alles in allem bleibt Atom aber doch ein verhältnismäßig einfach zu implementierendes Prokotoll. Und wenn man sowieso schon einen Atom-Feed für Newsreader-Software anbietet, kann man den dafür geschriebenen Code denn auch für die Kommunikation mit anderer Software wiederverwenden.

Deutsche Kleinparteien: Ökologisch-Demokratische Partei

Dieser Artikel ist Teil 2 meiner Serie über deutsche Kleinparteien. Heute werde ich die größte der Kleinparteien vorstellen, die ich auf dem Plan stehen habe: die ödp.

Die ** Ökologisch-Demokratische Partei ** ist eine Art wertkonservatives Pendant zu den Grünen. Sie entstand zu einer Zeit, als die Grünen noch recht weit links im politischen Spektrum angesiedelt waren.

Anhand des Namens fällt sicherlich zuerst einmal auf, daß es sich um eine Partei handelt, die sich den Umweltschutz auf die Fahne geschrieben hat. Gentechnik, Atomkraft und „physisch oder psychisch quälerisch[e] und leidvoll[e] Experimente an und mit Tieren“ [Parteiprogramm der ödp] sind ihr zuwider, und den Verbrauch von Ressourcen sowie die Verschmutzung der Umwelt möchte sie hoch versteuern, um Anreize zu schaffen, effizientere Arbeitsweisen zu finden, und erneuerbare Energiequellen zu fördern.

Die Umwelt ist indes nicht das einzige, was die ödp gerne sauber halten möchte. In ihrem Parteiprogramm fordert sie neben einer Stärkung der direkten Demokratie die Entflechtung von Wirtschaft und Politik.

Parteispenden von Firmen und Großorganisationen an politische Parteien und Wählervereinigungen sind zu verbieten, um die Käuflichkeit von Parteien zu verhindern. [Bundespolitisches Programm der ödp]

Eine unkonventionelle Haltung nimmt die ödp auch in der Familienpolitik ein — ein Teil des Parteiprogramms, in dem sich der kulturelle Konservatismus der Partei zeigt. Sie möchte Eltern für ihre Arbeit direkt mit einem sozialversicherungspflichtigen Erziehungsgehalt entlohnen, anstatt lediglich Krippenplätze zu sponsern.

Hand in Hand mit einer Neuordnung des Rentenrechts muss eine sachgerechte, d.h. dem Wert für die Gesellschaft entsprechende finanzielle Bewertung der Betreuungs- und Erziehungsarbeit gehen. Das fördert nicht nur die gesellschaftliche Anerkennung sondern auch die Attraktivität und Qualität der Kindererziehung und ist damit eine Investition in die Zukunft. Kinder werden dann nicht mehr von durch Beruf und Kinder überforderten Eltern vernachlässigt ... Die von der Bundesregierung eingeleitete einseitige Förderung der Krippenerziehung von Kindern unter drei Jahren lehnen wir ab, da sie das im Grundgesetz verankerte Elternrecht auf Wahlfreiheit der Erziehungsform untergräbt. Das bedeutet nicht, dass wir Krippen an sich ablehnen, wenn ein ausreichender Betreuungsschlüssel besteht. Aber anstatt einer Finanzierung der Kinderkrippen hat eine Finanzierung der Kinderbetreuung zu erfolgen, und zwar unabhängig davon, ob die Betreuung durch die Eltern oder auf andere Weise erfolgt. [Bundespolitisches Programm der ödp]

Ähnlich wie die Liberalen Demokraten fordert die ödp außerdem die seit langem überfällige Arbeitszeitverkürzung, „auch ohne vollen Lohnausgleich“. [Op. Cit.]

Vieles deutet darauf hin, dass Vollbeschäftigung im Sinne der ersten Nachkriegsjahrzehnte nicht mehr zu erreichen sein wird. Darin liegt auch eine Chance für ein sinnerfüllteres Leben. Eine generelle Verkürzung der Erwerbsarbeitszeit hat auf jeden Fall positive Züge, weil so mehr Zeit für Familie, Weiterbildung, Kultur sowie soziales und kulturelles Engagement zur Verfügung steht. Die Fortsetzung des heutigen Zustandes - Stress und Überlastung auf der einen Seite der Gesellschaft und Arbeitslosigkeit auf der anderen - ist hingegen nicht hinnehmbar. Gerade auf diesem zentralen Gebiet erfordert die Gerechtigkeit die Bereitschaft zum Teilen. [Bundespolitisches Programm der ödp]

Interessant ist, was die Wirtschaftspolitik angeht, auch die Forderung nach der Einführung der Tobin-Steuer sowie einer Abgabe auf grenzüberschreitenden Handel („Terra-Abgabe“), mithilfe derer Steuerverluste ausgeglichen werden sollen, die dadurch entstehen, daß Firmen sich durch Ausnutzung der Globalisierung zunehmend nationalen Regelungen entziehen.

Die ödp tritt zur Bundestagswahl 2009 in Baden-Württemberg, Bayern, Berlin, Hamburg, Niedersachsen, Nordrhein-Westfalen, Rheinland-Pfalz und Thüringen an. [Pressemitteilung]

Deutsche Kleinparteien: Liberale Demokraten

Ich werde gleich heute die angekündigte Serie über interessante deutsche Kleinparteien starten. Den Anfang macht die LD, eine linksliberale Partei.

Die Liberalen Demokraten (LD) verstehen sich als der geistige Nachfolger des linken Flügels der FDP, der zur Zeit der sozialliberalen Koalition auf Bundesebene die inzwischen nur noch wirtschaftsliberale Partei bedeutend prägte.

Nach dem Eintrag in der Wikipedia ist die LD nicht so blind marktgläubig wie die etablierten nichtsozialistischen Parteien.

Im Mittelpunkt der politischen Arbeit stehen heute Menschenrechte, Umweltschutz und soziale Gerechtigkeit. Marktwirtschaft und Eigentum betrachten sie als Mittel zum „Zweck der Wahrung und Mehrung menschlicher Freiheit“ und nicht als Selbstzweck. Des weiteren fordern die LD die Abschaffung der Fünf-Prozent-Hürde. Gegenwärtig diskutieren sie in verschiedenen Zusammenhängen das Thema Bedingungsloses Grundeinkommen. [Wikipedia]

In der Tat scheint die LD im Gegensatz zu allen bekannten Parteien (Die Linke eingeschlossen) der Erwerbsarbeit als Allheilmittel gesellschaftlicher Probleme kritisch gegenüberzustehen. Der Bundesvorsitzende der LD schreibt in einem offenen Brief an den SPD-Kanzlerkandidaten 2009, Frank Walter Steinmeier:

Der einzig richtige Ansatz bei der Betrachtung von sogenannter Arbeitslosigkeit muss die Nicht-Erwerbsarbeit und ihren gesellschaftlichen Nutzen mit einbeziehen. Die LD hat seit 1987, damals veröffentlicht unter dem Titel „Arbeit für Alle“, die Förderung und Belohnung gesellschaftlich notwendiger Nicht-Erwerbsarbeit in den Mittelpunkt ihrer Überlegungen gestellt und diese Betrachtung seitdem ständig weiterentwickelt. Diese Ansätze vermissen wir leider bei allen in den Parlamenten vertretenen Parteien. [Website der LD, LD enttäuscht von SPD-Kanzlerkandidat Steinmeier]

Im hier genannten Papier Arbeit für alle schreibt die LD indes:

Der Ausgleich zwischen Angebot von und Nachfrage nach Arbeitsleistungen muss in der Hauptsache durch eine Verminderung des Angebots erzielt werden. Denn eine Erhöhung der Nachfrage nach Arbeitsleistungen ist wegen der erreichten Grenzen des Wachstums weder in dem erforderlichen Umfang möglich noch wünschenswert. Den Ansatz, das Angebot von Arbeitsleistungen durch Fernhalten oder Ausscheiden von Personengruppen aus dem Arbeitsleben zu vermindern, lehnen die Liberalen Demokraten ab. Es würde in der Praxis überwiegend die Frauen treffen und den Emanzipationsprozess beeinträchtigen, wenn nicht sogar teilweise rückgängig machen.

Es bleibt der Weg der Verkürzung der Tages-, Wochen-, Jahres- und Lebensarbeitszeit. [Website der LD, Arbeit für alle]

Ein ganz netter Absatz findet sich im selben Text auch zum Thema Freizeit, wenn er auch etwas unkonkret daherkommt:

Begriff und Problem der Freizeit sind ein Produkt der modernen Industriegesellschaft. Fehlentwicklungen in der Arbeit (z.B. übermäßige Belastung, Sinnentleerung durch Arbeitszergliederung und Automatisierung) haben zu Fehlentwicklungen auch außerhalb der Arbeit geführt (z.B. Flucht in die Freizeit, Einrichtung von Freizeitparks, Medienkonsum).

Die Liberalen Demokraten streben an, die ganzheitlichen Perspektiven im menschlichen Leben zu verstärken. Deswegen darf Freizeit sich nicht verselbständigen und nur den Zwecken der Erholung und Entspannung zugeordnet werden. Freizeit gewinnt vielmehr ihren Sinn durch die Aspekte Muße und Genuss, die in Arbeitsprozessen (ob bezahlt oder unbezahlt) regelmäßig weniger bedeutsam sind. Zur erfüllenden Gestaltung ihres Lebens fehlt vielen Menschen jedoch noch die Fähigkeit, mit freier Zeit eigenbestimmt so umzugehen, dass sie Ihre geistigen und körperlichen, ihre ästhetischen und musischen, ihre kreativen und sozialen, ihre sportlichen und handwerklichen Wünsche und Möglichkeiten dabei entdecken und befriedigen. Insofern sind Bildung und Weiterbildung stark gefordert. Im Übrigen kann unmittelbar auf Freizeit bezogene Politik nur ausgerichtet sein auf Beispiel, Anregung und Angebot, nicht dagegen auf Bevormundung, Gängelung und Verplanung (auch in kommerzieller Form). [Op. Cit.]

Zur Familienpolitik schließlich wird ebenfalls eine Zurückstellung der Lohnarbeit als Lösung für viele Probleme angesehen:

Anstelle eines Elternurlaubs soll beiden Eltern unter Verbesserung des Familienlastenausgleichs der Anspruch auf Teilzeitarbeit von der Geburt eines Jeden Kindes an eingeräumt werden. Nach sechsjähriger Tätigkeit beim gleichen Arbeitgeber soll ein Anspruch auf unbezahlten Urlaub von einem Jahr ohne weitere Voraussetzungen gegeben sein. [Op. Cit.]

Serie: Deutsche Kleinparteien

Ich habe mir vorgenommen, eine Serie über interessante deutsche (und vielleicht auch nichtdeutsche) Kleinparteien zu starten, die den wenigsten Bundesbürgern bekannt sein dürften. Die Serie wird nicht auf einen bestimmten Zeitraum beschränkt sein. Wann immer ich über interessante Parteien stolpere, werde ich sie hier vorstellen.

Inhaltsverzeichnis

  1. Liberale Demokraten — die Sozialliberalen

  2. Ökologisch-Demokratische Partei — die „wertkonservativen Grünen“

Befehls- als partielle Metataste in iTerm

An der Konsole eines unixartigen Systems kann man, die passende Shell vorausgesetzt, für seine Befehlszeilenjonglierereien einige nützliche Tastenkombinationen verwenden, die Emacs entlehnt sind. Darunter gibt es zum Beispiel forward-word (Meta-F), backward-word (Meta-B) und delete-word (Meta-D). Man kann kaum gut genug betonen, wie sehr allein diese Tastenkombinationen bereits den Komfort an der Konsole steigern.

Kaum ein Computer hat heutzutage noch eine eigene Metataste. Gut, kein Problem, sagt der GNU/Linux-Anwender. Verwende ich halt Alt.

Nun verhält es sich leider mit Macs bekanntermaßen so, daß diesen nicht nur eine Meta-, sondern auch eine Alt-Taste fehlt. Stattdessen haben sie zwei andere Arten von Tasten, nämlich die Befehls- und die Wahltasten. Dabei sind die Befehlstasten (abgekürzt Cmd für Command, als Zeichen: ⌘) diejenigen Tasten, mit denen man Befehle ausführt, die man auch in der Menüleiste findet, wie z.B. das Kopieren, Ausschneiden und Einsetzen von Objekten (⌘C, ⌘X und ⌘V) oder das Öffnen und Speichern einer Datei (⌘O und ⌘S), aber auch wichtige Funktionen der Fensterverwaltung wie das Verstecken der aktiven Anwendung oder das Wechseln zu einer anderen (⌘H und ⌘⇥). Die Wahltaste (abgekürzt Opt für Option, als Zeichen: ⌥) übernimmt indes die Aufgabe, die auf vielen nichtamerikanischen PC-Tastaturen von AltGr wahrgenommen wird, d.h. die Auswahl von Sonderzeichen, die man nicht allein mit der Umschalttaste erreicht.

Dieser Umstand führt den befehlszeilenaffinen Mac-Anwender mit Nicht-US-Tastatur schnurstracks in ein Dilemma: Wenn er die Befehlstaste als Metataste verwenden würde, könnte er den Terminalemulator nicht mehr vernünftig bedienen. Aus diesem Grund bieten weder iTerm noch das Mac-OS-eigene Terminal.app diese Option überhaupt an. Andererseits kann er auch die Wahltaste nicht als Metataste verwenden, da er dafür entweder auf das US-Tastaturlayout umsteigen oder aber auf die Eingabe von Sonderzeichen verzichten müßte. Die erstere Möglichkeit ist für jemanden, der nicht nur programmiert, sondern auch hin und wieder mal einen Text in seiner Muttersprache verfaßt -- und wer tut das nicht? -- viel zu disruptiv. Die andere Möglichkeit ist genauso eine Katastrophe, da sie die Arbeit mit der Shell fast unmöglich macht. Was ist also zu tun?

Ich habe für mich eine Zwischenlösung gewählt: Da die Wahltaste für mich wegen meiner Verwendung eines bestimmten Nichtstandardtastaturlayouts an der Konsole völlig unabdingbar ist, verwende ich die Befehlstaste als Metataste, aber nur partiell. Genau gesagt: Ich lasse iTerm die Tastenkombinationen ⌘F, ⌘B und ⌘D in die jeweiligen Pendants mit Metataste übersetzen und lasse den Rest unberührt. Das geht wie folgt:

  1. Man öffnet in iTerm den Menüpunkt Bookmarks -> Manage Profiles.

  2. Man klickt auf das Dreieck neben Keyboard Profiles.

  3. Man klickt auf Global.

  4. Man klickt auf +.

  5. Unter Key: wählt man hex code und gibt daneben 62 ein.

  6. Unter Modifier: wählt man Command an.

  7. Unter Action: wählt man send escape sequence und gibt darunter b ein.

  8. Man aktiviert das Kästchen High interception priority.

  9. Man klickt auf OK.

  10. Man wiederholt die Schritte 4 bis 9 mit den Werten 66 für den Hexcode und f für die Escape-Sequenz.

  11. Man tut dasselbe für die Werte 64 als Hexcode und d als Escape-Sequenz.

  12. Man schließt das Fenster.

Wie man weitere Befehlstasten- zu Metatastenübersetzungen bastelt, sollte man sich jetzt denken können.

Wie dem auch sei, mir hilft diese Konfiguration ungemein.

Lisp im Chaosradio Express

Ich habe gerade entdeckt, daß es eine Folge vom Chaosradio Express über Lisp gibt. Wer sich mit Lisp nicht auskennt und gerne einen kleinen Eindruck gewinnen möchte, ohne gleich tief einzusteigen, ist damit gar nicht mal so schlecht bedient.

(Laßt Euch aber nicht beirren: LOOP ist toll!)

Update: Es gibt auch eine Folge über Dylan. In die kann man, wenn man schon dabei ist, natürlich auch gleich reinhören. Dank an Christoph für den Hinweis!

Transfinite Wahrscheinlichkeitslogik online

Die Arbeitsgruppe für transfinite Wahrscheinlichkeitslogik unter Führung von Prof. Dr. Maximum Likelihood und Prof. Dr. Ignatius Schottenschneider hat neuerdings eine Webpräsenz. Dort finden sich auch die Informationen zu angebotenen Vorlesungen und Seminaren, unter anderem das ganz frisch angekündigte Seminar über Methoden der Differentialstochastik in der Differentialstochastik.

Jeder, der sich für Differentialstochastik oder Wahrscheinlichkeitslogik interessiert (und das tun wir doch alle), sollte einen Blick riskieren.

Man munkelt übrigens, daß es kommendes Semester auch wieder eine Vorlesung geben soll. Informationen finden sich dazu allerdings noch keine.

Monaden in Scala

Ein nichtdeterministisches Programm, zum Beispiel zum Bestimmen aller Faktoren einer Zahl, ist in Haskell schnell geschrieben:

-- A non-deterministic programme that finds all factor pairs of a given number.  
factors :: Integral a => a -> [(a, a)]  
factors = do  
  x <- [1..num]  
  guard $ num `mod` x == 0  
  let y = num `div` x  
  return (x, y) 

In Scala geht das ganz ähnlich:

// A non-deterministic programme that finds all factor pairs of a given number.  
def factors(num: Int) = {  
  for {  
    x <- 1 to num  
    if num % x == 0  
    val y = num / x  
  } yield (x, y);  
} 

Klar, Obiges ist einfach eine Listenkomprehension, aber in der Tat geht das in beiden Sprachen auch über ganz anderen Datentypen, die die jeweils geforderten Schnittstellen implementieren. Auch, wenn die Scala-Schnittstelle rein namensmäßig auf Listen zugeschnitten ist (flatMap für bind, filter für fail), ist die Funktionalität identisch. Nur rein nebenwirkungsorientierte Anweisungen ohne Variablenbindung scheinen in Scala-Komprehensionen nicht möglich zu sein.

Bearbeiten von Tabellen in Numbers mit AppleScript

Neulich habe ich ein Numbers-Dokument angelegt, um eine Partie eines Kartenspiels zu erfassen und hübsche Graphiken erstellen zu können, die zum eigentlichen Spielspaß auch noch die Erfüllung dieses seltsamen Statistiktriebs hinzubringen, dem ich hin und wieder verfalle. Erfreulicherweise scheinen die Graphiken auch den anderen Mitspielern eine gewisse Freude zu bereiten.

Nun hatte ich die Punktestände allerdings auf eine, wie sich herausstellte, etwas ungeschickte Weise eingetragen (Minuspunkte von 0 bis 5 statt Punkte von -2 bis 2). Naturgemäß wollte ich nicht jede Zelle einzeln umrechnen und bearbeiten müssen, um die Daten in eine bessere Form zu bringen. Warum also nicht mal AppleScript ausprobieren?

tell application "Numbers"  
 
    repeat with x in cells of selection range of table "Einzelergebnisse" of first sheet of document "Karrierepoker"  
 
        set value of x to -(value of x) + 2  
 
    end repeat  
 
end tell  
 

Nun, AppleScript mag kein Lisp sein, aber der Programmtext liest sich trotzdem recht angenehm, und dank der vom Skripteditor aus schnell erreichbaren Funktionsreferenz kann man schön explorativ arbeiten.

Ad-hoc-Polymorphie in Scala: besser als Haskell?

Wie würdet ihr die Fakultätsfunktion, das Hallo-Welt-Programm der funktionalen Programmierung, naiv in Scala implementieren? Ungefähr so?

def fac(n) = if (n <= 1) 1 else n*fac(n-1) 

Leider reicht das nicht. Die Typinferenz von Scala ist ziemlich schwach. Man muß deshalb den Typen des Arguments sowie den Rückgabetypen der Funktion angeben (Letzterer wird normalerweise automatisch festgestellt, bei rekursiven Definitionen geht das aber nicht):

def fac(n: Int):Int = if (n <= 1) 1 else n*fac(n-1) 

Damit ist fac zwar definiert, aber nicht so allgemein wie möglich. Für BigInts beispielsweise wird die Funktion nicht funktionieren.

Man könnte nun auf die Idee kommen, etwas ähnliches wie das Folgende zu schreiben:

def fac(n: Number):Number = if (n <= 1) 1 else n*fac(n-1) 

Das schlägt aber leider gleich doppelt fehl, weil einerseits nicht alle Zahlen von Number erben, und Number andererseits nicht die üblichen arithmetischen Operatoren und Größenvergleiche bereitstellt. Es wäre auch unflexibler als in Haskell, da man dort a posteriori eine sogenannte Typklasse implementieren kann, womit der Typ der Funktion in Haskell wie folgt aussieht:

fac :: Number a => a -> a 

Das heißt: Wir erhalten ein Objekt vom Typen a und geben eines vom Typen a zurück, und zwar für jeden Typen, der die Number-Operationen zur Verfügung stellt. Man beachte, daß der Typ genauer ist als der in der obigen Scala-Funktion, welche uns nur garantiert, daß sie uns irgendeine Zahl zurückgibt, aber nicht, von welchem Typ. Das liegt daran, daß die Haskell-Version von fac über den Typen a parametrisiert ist.

Nun gibt es solche Typparameter auch in Scala. Wir würden gerne etwas hinbekommen wie:

def (Numeric[T]) fac[T](n: T):T = if (n <= 1) 1 else n*fac(n-1) 

Leider können wir das so nicht schreiben, weil Scala keine Typklassen kennt, aber etwas anders bekommen wir es hin:

def fac[T](n: T)(implicit num: Numeric[T]):T = {  
  import num.{mkNumericOps, mkOrderingOps}
  if (n <= num.one)  
    num.one  
  else  
    n * fac(n - num.one)  
} 

Was hierbei geschieht, grenzt an dunkle Magie. Es wird ein impliziter Parameter num eingeführt, der vom Compiler auf der Seite des Aufrufenden automatisch eingesetzt wird, falls eine zu den Typen passende Implementierung im jeweiligen Kontext sichtbar ist. Zusätzlich bedienen wir uns innerhalb von fac der Tatsache, daß mkNumericOps und mkOrderingOps in der Klasse Numeric[T] als implizite Umwandlungen definiert sind. Sie erlauben es uns, unseren Wert n, über dessen Typen wir gar nichts wissen, in etwas umzuwandeln, das Rechenoperationen bzw. Größenvergleiche mit der gängigen mathematischen Schreibweise unterstützt — und das, ohne die Umwandlungen explizit zu machen. Nebenbei bietet Numeric[T] uns Konstanten für die Werte 0 und 1, die wir leider ausschreiben müssen.

(Bemerkung: Man kann die import-Zeile auch durch eine import num._-Zeile ersetzen, die alle Methoden aus num zugleich importiert.)

Daß unsere Definition tut, was sie soll, können wir nachprüfen:

scala> fac(5.0)  
res17: Double = 120.0  
 
scala> fac(5)  
res18: Int = 120  
 
scala> fac(BigInt(5))  
res19: BigInt = 120 

Das ist nun sowohl schlechter als auch besser als Haskells Typklassen. Der Hauptnachteil ist, daß der Mechanismus wesentlich weniger transparent ist und mehr auf dem Verstecken von Fakten beruht, was man grundsätzlich kritisch sehen sollte. Als Korollar dazu läßt auch die Typinferenz arg zu wünschen übrig (das ist allerdings im Falle von Scala ganz allgemein so).

Andererseits ist der Mechanismus flexibler, denn er gibt dem aufrufenden Kontext die Möglichkeit, jede beliebige Implementierung für die Rechenoperationen bereitzustellen. Ich habe bereits in einem früheren Eintrag festgestellt, daß Typklassen in dieser Hinsicht eine Barriere zur Wiederverwendung von Code darstellen. Scalas Funktionen werden mithilfe von Umwandlungsfunktionen polymorph, die je nach Kontext unterschiedlich aussehen können und einfach als Parameter übergeben werden. Haskells Funktionen werden hingegen direkt über statische Eigenschaften von Typen parametrisiert, was die Polymorphie einschränkt.

Letztlich bietet Scala in etwa das, was ich am Ende meines früheren, oben referenzierten Eintrags vorgeschlagen habe. Offenkundig lassen allerdings sowohl die Transparenz als auch die syntaktische Unterstützung noch zu wünschen übrig, womit die Frage nach einer optimalen Lösung des Problems weiter unbeantwortet bleibt.

Kompromisse sind wohl nicht zu vermeiden, wenn man eng mit Java zusammenarbeiten will.

Funktionale Programmierung ist algebraische Programmierung

Ich habe mich schon häufig in der Situation wiedergefunden, einem an funktionaler Programmierung interessierten Gesprächspartner zu erklären, daß gerade die Mutter aller funktionalen Sprachen, Lisp, selbst gar nicht viel mit funktionaler Programmierung zu tun hat. Auf die dann meistens folgende, leicht irritierte Frage, was es denn für eine Sprache sei, wenn nicht eine funktionale, habe ich bislang immer verhalten „irgendwas zwischen objektorientiert und funktional“ geantwortet — eine etwas unzufriedenstellende Antwort und vor allen Dingen nichts, womit man wirklich etwas anfangen könnte, wenn man die Sprache noch nicht kennt.

Vor einer Weile habe ich an der Uni eine Vorlesungsstunde über „zirkuläre Programme“ im Rahmen der Vorlesung „Funktionale Programmierung“ gehört — oder besser: ich habe das Skript dazu gelesen, weil jene Vorlesungsstunde an einen unpassenden Termin verschoben worden war (ironischerweise eine Überschneidung mit Funktionentheorie, welche später noch einmal einen Auftritt in diesem Artikel haben wird). Ich blätterte also so dahin, und als ich auf das erste Beispiel stieß, ließ es mich stutzen. Es ging um die Definition einer Datenstruktur durch eine Art Verknotung des Codes — und nur darum. Zugegeben, ich hätte nicht gewußt, was unter einem „zirkülären Programm“ zu verstehen hätte sein können, aber irgendetwas sagte mir, daß das doch keines sein konnte.

Seitdem ist mir klarer geworden, worum es eigentlich geht.

Die Lisp-Denkweise: Ich schreibe die Funktionen, der Rest ergibt sich so nebenbei

Wenn ein Lisper ein Programm schreibt, dann baut er es häufig erst einmal von unten her auf. Er erweitert dabei die Sprache so lange, bis er seine Probleme adäquat darin ausdrücken kann. Diese Spracherweiterungen bestehen primär aus Funktionen und Makros. Datenstrukturen definiert er sich dann, wenn sie sich als nützlich oder notwendig herausstellen.

Ein Lisper programmiert also funktionsorientiert. Er schreibt Makros und Funktionen. Sie bilden die Bausteine seines Programms, und sie liegen im Zentrum seines Denkens. Deshalb interessiert er sich auch nur am Rande für solche Dinge wie Musterangleich (pattern matching) und algebraische Datentypen — einfache structs tun es meist auch. Ein statisches Typensystem würde den Lisper nur bei seiner Arbeit stören, die ja schließlich darin besteht, erst einmal viel Allgemeines, Grundlegendes zu schreiben, das sowieso mit allen oder zahlreichen nicht unbedingt viel gemeinsam habenden Datentypen gleichermaßen zu verwenden ist — Spracherweiterungen eben.

Die funktionale Denkweise: Datentypen und Kinder zuerst!

Der funktionale Programmierer sieht die Welt ganz anders. Für ihn stehen die Datenstrukturen im Mittelpunkt des Interesses. Was ist schon ein Programm ohne Datenstrukturen? Erst einmal definieren wir... natürlich, einen algebraischen Datentypen. Der erfüllt vielleicht irgendwelche Gesetze, die man schön in einer Typklasse ausdrücken kann. Wir fangen also an, Code zu schreiben, der unsere Datenstrukturen transformiert. Dabei befinden wir uns auf einer hohen Abstraktionsebene und haben ständig vor Augen, auf welcher Art von Daten wir gerade arbeiten.

Da die Datenstrukturen so wichtig sind, ist auch statische Typprüfung von ungeheurem Wert und findet in unserem Programm fast alle Fehler, die wir überhaupt machen können. Wir schreiben den Code so allgemein wie möglich, aber wir schreiben keine Spracherweiterungen, denn die würden den Typprüfer verwirren, und überhaupt — wenn wir alles in Datenstrukturen ausdrücken, wieso sollten wir uns dann die Mühe machen, die Sprache für Code zu erweitern? Soviel Code schreiben wir ja gar nicht. Außerdem ist die Syntax der Sprache für die Arbeit mit algebraischen Datentypen optimiert und eignet sich nicht besonders für Erweiterungen.

Auch pattern matching ist ein wesentliches Merkmal von funktionalen Sprachen. Wie sollte es anders sein, wenn man ständig Datenstrukturen destrukturiert und wieder zusammensetzt? Ohne pattern matching könnte man gar nicht leben.

Aber warum heißt diese Programmierweise eigentlich gleich nochmal funktional?

Funktionale Programmierung ist algebraische Programmierung

Wenn man sich die obigen (zweifelsohne stereotypen) Beschreibungen durch den Kopf gehen läßt, fällt einem früher oder später vielleicht auf, daß der Begriff „Funktion“ im Abschnitt über die funktionale Programmierung nirgends vorkommt. In der Tat erscheinen Funktionen als solche in der funktionalen Programmierung eigentlich nebensächlich. Worum es geht, sind die Datenstrukturen, üblicherweise algebraische Datentypen. Diese werden erzeugt, transformiert und konsumiert. Das ist der primäre Sinn und Zweck eines Großteils des Codes eines üblichen funktionalen Programms. Daß dafür gern Funktionen verwendet werden, liegt nur daran, daß sie dafür nützlich sind. Die algebraischen Datenstrukturen sind es, die eigentlich im Mittelpunkt des Geschehens stehen.

In Lisp hat man hingegen in erster Linie die Funktionen und Makros im Kopf, die man geschrieben hat oder noch schreiben möchte. Datenstrukturen sind nur ein nützliches Beiwerk, nichts weiter, zuweilen sogar eher ein notwendiges Übel. Die Funktionalität der einzelnen Codeteile ist es, was in der Hauptsache zählt.

Es gab eine Zeit, in dem man mit dem Begriff „funktional“ die Lisp-Sichtweise meinte. In der Tat: Letztlich arbeitet man in Lisp viel mehr funktionenorientiert als in „funktionalen“ Sprachen im heutigen Sinne. Die Zeiten haben sich geändert. Heutzutage bezeichnet man als funktionale Programmierung eine Programmierweise, die eigentlich algebraische Programmierung heißen sollte.

Bleibt die Frage, wie man die Lisp-Sicht der Welt nennen soll. „Irgendwas zwischen objektorientiert und funktional“ ist vielleicht nicht ganz falsch, aber auch nicht ganz richtig. Sprachorientierte Programmierung vielleicht?

Nebenbei bemerkt: In der Mathematik gibt es ein Gebiet namens Funktionentheorie. Wider jegliche Erwartung geht es darin nicht um Funktionen im allgemeinen Sinne, nicht um Funktionen als Objekte oder Strukturen, sondern schlicht um komplexe Analysis. Der Begriff der Funktion hat offenbar viele Gesichter.

Scala

Nachdem man immer mal wieder (manche sagen: jedes Jahr) eine neue Programmiersprache lernen soll und Clojure sich nicht hinreichend von Common Lisp unterscheidet, um wirklich sagen zu können, daß ich dem in letzter Zeit nachgekommen sei, habe ich beschlossen, mir die Sprache Scala ein wenig anzusehen.

Mein bisheriger Eindruck ist recht gemischt, aber im Vergleich zum Mainstream schneidet Scala zweifelsohne gut ab.

Die Sprache wirkt insgesamt bemerkenswert mächtig, wenn man bedenkt, daß Scala sich von der Integration mit der JVM her viel stärker an Java orientiert, als beispielsweise Clojure das tut.

Aus diversen Quellen hört und liest man, daß Scala ein relativ mächtiges Typensystem besitze. In der Tat scheint die Typisierung ein paar interessante Merkmale aufzuweisen; jedoch muß ich mir das Typensystem erst genauer ansehen und ein bißchen damit herumspielen, um Konkreteres sagen zu können. Daß Traits unterstützt werden, ist aber auf jeden Fall schon mal ein Pluspunkt, und auch die impliziten Typenumwandlungen, die einem ganz unerwartete Dinge erlauben, passen gut in das Gesamtkonzept hinein.

Interessant ist auch, daß Scala ein Feature hat, das ich bislang nur aus diversen Lisps kenne und sonst noch nirgends entdecken konnte: dynamische Variablen. Ein Beispiel:

import scala.util.DynamicVariable  
 
val x = new DynamicVariable(0)  
 
def printx {  
  println(x.value)  
}  
 
printx               //=> 0  
x.withValue(100) {  
  printx             //=> 100  
  x.value = 50  
  printx             //=> 50  
}  
printx               //=> 0 

Die dynamischen Bindungen sind threadlokal und werden den Kindthreads vererbt, genau wie es sich gehört.

Natürlich stört mich das Fehlen eines Makrosystems. Die Scala-Anhänger glauben offenbar, ihre Sprache sei so flexibel, daß sie kein Makrosystem benötige. Das ist ein ziemlich weit verbreitetes Mißverständnis von Makrosystemen im allgemeinen, dem viele Anwender funktionaler Sprachen aufliegen. Man kann durchaus viele nützliche Muster funktional abstrahieren, für die man in weniger funktionalen Sprachen Makros benötigen würde, aber bei weitem nicht alle — ganz besonders nicht in einer funktional-objektorientierten Hybridsprache! —, und die Gefahr eines unbeschränkten, Code transformierenden Makrosystems wird gemeinhin auch maßlos überschätzt.

Andererseits sind funktionale Programmierung und programmierergesteuerte Spracherweiterungen eben Teile verschiedener Kulturen. Dazu werde ich in einem späteren Eintrag noch ein wenig mehr schreiben.

Entwurf einer auf natürliche Weise homoikonischen objektbasierten Sprache

Zurzeit überlege ich hin und wieder, wie eine Programmiersprache aussehen könnte, die einerseits objektbasiert ist und andererseits auf so offensichtliche Weise selbstdarstellend wie Lisp. Mein Ziel ist eine Sprache, die gut in einen vornehmlich oder rein objektorientierten Kontext paßt und von derselben Flexibilität profitiert, die Lisp durch Makros erhält.

Momentan hinkt mein Sprachdesign noch. Einige Stellen müssen von haarsträubenden inneren Widersprüchen bereinigt werden. Im großen und ganzen scheint mir das Konzept aber durchführbar zu sein -- die Praktikabilität muß sich letzten Endes jedoch natürlich in einem Praxistest herausstellen.

Als kleinen Vorgeschmack stelle ich hier kurz den derzeitigen Entwurf vor. Man bedenke, daß der Entwurf einer von vielen ist, die größtenteils bereits ihren Weg in den Papierkorb gefunden haben, und es nicht ganz und gar unwahrscheinlich ist, daß ihm selbst dasselbe Schicksal widerfahren wird. Man wird sehen.

Idee

Die grobe Idee ist es, eine prototypenbasierte, objektorientierte Sprache mit einer möglichst regulären Syntax zu basteln, welche auf Nachrichtensendungen, d.h. auf Methodenaufrufen basiert. Im Prinzip ähnelt die Sprache der Sprache Self, außer daß die Syntax regulärer ist und Blöcke durch Makros ausgedrückt werden.

Syntax

Im Gegensatz zu den meisten anderen Sprachen ist in meinem Sprachentwurf die abstrakte Syntax von entscheidender Bedeutung. Die Sprache kennt vier Arten von zusammengesetzten Konstrukten:

  1. Sequenzen: a. b. c.

  2. n-tupel: a : b : c

  3. Listen: a, b, c

  4. Vektoren: a b c

Der Operatorvorrang geht hierbei von unten nach oben, d.h. a: b, c. d: e. bezeichnet eine Sequenz von zwei 2-tupeln (Paaren), von denen das erste als zweite Komponente eine Liste aus zwei Elementen enthält.

Semantik

Interpretiert wird das Ganze so:

  1. Ein Programm ist eine Sequenz von Anweisungen.
  2. Eine Anweisung ist ein n-tupel oder ein Literal.
  3. Wie genau ein gegebenes Anweisungstupel interpretiert wird, hängt vom ersten Element ab.
    1. Ist dieses ein Makroname oder ein Vektor, der als zweite Komponente einen Makronamen enthält, dann wird das Makro aufgerufen und das Ergebnis erneut betrachtet.
    2. Anderenfalls gelten folgende Regeln: Ein Tupel der Form a b: c, d, e, f wird als Aufruf der Methode b auf dem Objekt a interpretiert. c bis f bezeichnen die Argumente des Methodenaufrufs. Diese dürfen wiederum Anweisungen sein (allerdings muß häufig geklammert werden, wie man leicht an einem beliebigen hinreichend komplexen Beispiel sehen kann). Es müssen auch keine Argumente da sein, und in diesem Fall kann man auch den Doppelpunkt weglassen, d.h. der ganze Ausdruck ist dann gar kein Tupel sondern eigentlich ein Vektor. Fehlt a (die erste Komponente des Tupels ist dann kein Vektor), so wird implizit self als Empfänger der Nachricht angenommen. (Dem geneigten Leser wird auffallen, daß self selbst auch lediglich eine Nachrichtensendung an self ist, d.h. self ist äquivalent zu self self, dieses wiederum zu (self self) self usw.. Das erscheint etwas zirkulär, wird aber vom Compiler einfach abgekürzt.)
  4. Ein Literal ist ein Numeral (z.B. 9.5 oder -42), ein String oder ein zusammengesetztes Konstrukt mit geschweiften Klammern außen herum, z.B. {x: 10. y: 20.} oder {(1 + 3) print.}. Literale werden direkt als solche in den Code übernommen und ihr Inhalt wird nicht selbst als Code interpretiert, es sei denn ein Makro transformiert sie entsprechend.

Makros

Makros transformieren ihren Aufruf zur Kompilierzeit in eine beliebige andere Anweisung. Es sind noch einige Fragen offen im Hinblick auf das Problem, zwischen Makro- und Methodenaufrufen zu unterscheiden. Besonders die Interaktionen mit einem etwaigen Modulsystem bereiten mir noch Kopfzerbrechen.

Beispielprogramme

Fibonacci-Zahlen (einfach)

Ein einfaches Beispielprogramm, das die zehnte Fibonaccizahl berechnet und ausgibt, sähe vielleicht wie folgt aus:

Integer defMethod: fib {  
 
  (< 2) if: {  
 
    self.  
 
  }, {  
 
    ((self - 1) fib) + ((self - 2) fib).  
 
  }.  
 
}.  
 
 
 
"Die zehnte Fibonaccizahl ist " print.  
 
(10 fib) printLn.  
 

Fibonacci-Zahlen (Dispatch-basiert)

Obiges Programm könnte man auch wie folgt formulieren:

0 defMethod: fib { 0. }.  
 
1 defMethod: fib { 1. }.  
 
Integer defMethod: fib { ((self - 1) fib) + ((self - 2) fib). }.  
 
 
 
"Die zehnte Fibonaccizahl ist " print.  
 
(10 fib) printLn. 

.tar.bz2/.tar.gz in .lzma.io (cpio/afio) umwandeln

Mir ist heute klar geworden, daß bzip2- oder gzip-komprimierte Tarballs auf DVDs eine äußerst schlechte Verpackung für Backups sind, da die Komprimierung dafür sorgt, daß ein kleiner Bitfehler an irgendeiner Stelle den ganzen Rest des Archivs unlesbar machen kann.

Ein wenig Recherche ließ mich schnell damit liebäugeln, meine Tarballs in afio-Archive umzuwandeln. afio komprimiert jede Datei einzeln und erzeugt auf diese Weise wesentlich fehlertolerantere Archive als tar -j. Leider gibt es nur eine wirklich universelle Möglichkeit, Archive verschiedener Formate ineinander umzuwandeln: altes Archiv vollständig entpacken, neues Archiv zusammenpacken. Dieser Weg ist nun allerdings für viele meiner Backup-Tarballs inakzeptabel, weil ich genügend Platz auf der Festplatte selten habe. Effizient (d.h. ohne ständiges Durchsuchen des Ursprungstarballs von Anfang an) kriegt man es anders auch sehr wahrscheinlich nicht hin, außer man schreibt sich ein eigenes Programm dafür, das den Tarball selbst Stück für Stück verarbeitet.

Ein Glück, daß Python solcherlei Dinge von Haus aus kann. Der folgende Code macht aus einem Tarball (erstes Befehlszeilenargument) ein cpio-Archiv, in dem die Dateien jeweils mit lzma komprimiert wurden. afio kann solche Archive direkt entpacken (afio -iZP lzma).

#! /usr/bin/env python  
 
import os  
import subprocess  
import sys  
import tarfile  
 
tar = tarfile.open(sys.argv[1], "r|*")  
afio_name = sys.argv[2]  
 
cpio = subprocess.Popen(["cpio", "-o", "-c0", "--file=%s" % afio_name])  
 
for info in tar:  
    tar.extract(info)  
    file_name = info.name  
    if info.isfile():  
        os.spawnlp(os.P_WAIT, "lzma", "lzma", "-S", ".z", info.name)  
        file_name += ".z"  
    cpio = subprocess.Popen(["cpio", "-o", "-c0A", "--file=%s" % afio_name], stdin=subprocess.PIPE)  
    cpio.communicate(file_name + "\x00")  
    cpio.stdin.close()  
    cpio.wait()  
    if not info.isdir():  
        os.unlink(file_name)  
 
tar.close()

Interaktive GUI-Programmierung mit SLIME, Clojure, Qt und Swing

Interaktivität ist bekanntlich eines der wesentlichen Merkmale von Programmierung in Lisp. Sicherlich ist das einer der Gründe dafür, daß sich das Entwickeln von GUI-Anwendungen in Lisp immer etwas unnatürlich anfühlt. Wenn man nicht gerade ein besonders lispiges Framework wie den Common Lisp Interface Manager oder ein smalltalkesques wie Morphic zur Verfügung hat — und im Falle von Clojure hat man zu meinem großen Bedauern keines von beiden — dann ist die einzige Möglichkeit, die noch bleibt, ein traditionelles GUI-Toolkit zu verwenden, das sich halbwegs interaktiv programmieren läßt.

Nun ist das mit der Interaktivität so eine Sache. Die meisten Programmiersprachen lassen sich in dieser Weise gar nicht nutzen, sondern setzen einen umständlichen Editieren-Kompilieren-Starten-Zyklus voraus. Das gilt insbesondere für die populären Sprachen mit C-artiger Syntax. So erstaunt es auch nicht, daß verbreitete Toolkits wie Gtk+ und Qt den Programmierer zwingen, Dinge zu tun, die in einem interaktiven Kontext völlig inakzeptabel wären. Eines davon ist folgendes Codemuster (hier beispielhaft für Qt-Jambi in Java, anderswo sieht es ähnlich aus):

gdk_threads_enter();  
// GUI-Code hier.  
gdk_threads_leave(); 

Code, der auf das Benutzerinterface zugreifen will, beispielsweise um es zu verändern, muß in einem Kontext stehen wie der Kommentar „GUI-Code hier“ oben im Codebeispiel. Der Grund dafür ist, daß die verbreiteten Toolkits stets nur einem einzelnen, bestimmten Thread den Zugriff auf das Interface erlauben.

Für interaktive Programmierung ist die Erzwingung dieses Codemusters katastrophal. Es ist nicht möglich, in der Clojure-REPL mal schnell einen Button zu erzeugen und ihn auf das bestehende Interface zu kleben, um auszuprobieren, wie das aussieht. Schließlich will wirklich niemand ernsthaft Dinge wie (try (threads-enter) (.show button) (finally (threads-leave))) anstelle von (.show button) eintippen wollen — jedenfalls definitiv nicht an einer Befehlszeile!

Genausowenig kann man schnell in einem geöffneten Editorfenster C-M-x drücken, um eine Label-Beschriftung zu aktualisieren, die man gerade angepaßt hat.

Vielleicht kann jemand, der interaktives Programmieren nicht gewohnt ist, nicht gleich verstehen, warum das alles ein riesiges Problem ist. Nichtsdestotrotz ist es eine Tatsache, daß sich in der Welt von Common Lisp trotz der Verfügbarkeit von Gtk+- und wxWidgets-Anbindungen immer noch Tk als Quasistandard für GUI-Programmierung hält. Ich bezweifle, daß es Zufall ist, daß gerade dasjenige Toolkit am meisten Zuspruch erhält, das das Problem des abgetrennten GUI-Threads nicht aufweist.

Lassen wir nun aber Tk einmal außen vor. Abgesehen davon, daß es, wie es scheint, keine akzeptable Java-Anbindung dafür gibt, mutet es doch zumindest unter X11-basierten Systemen etwas prähistorisch an. Was gibt es für moderne Clojure-Programmierer für Alternativen?

Da wäre freilich einmal JFC/Swing. Genügend Leute klagen über das barocke API. Doch: Entgegen jeder rationalen Erwartung, kennt Swing das Problem des GUI-Threads nicht! Es eignet sich somit ganz hervorragend zum interaktiven Zusammenbasteln von GUIs.

Nun, vielleicht nicht ganz hervorragend. Wie gesagt, ist das API barock und etwas unhandlich zum interaktiven Programmieren. Hinzu kommt, daß es keine realistische Möglichkeit gibt, zur Laufzeit ein mit einem GUI-Designer entworfenes Interface zu laden. Matisse zum Beispiel, der für Swing-Verhältnisse exzellente GUI-Designer in Netbeans, erzeugt Java-Code. Um den zur Laufzeit neu zu laden, müßte man erst die alte Instanz irgendwie aus dem Speicher kriegen, und dann— nein. Viel zu aufwendig.

Vielleicht ist Qt die Lösung? Der Qt Designer erzeugt XML-Beschreibungen des entworfenen Interfaces, die man zur Laufzeit laden kann. Doch — oh Schreck! — wie muß man GUI-Code bei Qt aufrufen?

QCoreApplication.invokeAndWait(new Runnable() {  
  public void run() {  
    // GUI-Code hier.  
  }  
} 

Nein, nein und nochmal nein! So wird das nichts mit der interaktiven Programmierung. Es sei denn, jemand würde SLIME hacken und alle interaktiven Befehle automatisch in diesen Boilerplate einpacken, bevor sie ausgeführt werden...

Gesagt, getan. Zwar schreibe ich im Moment keine GUI-Anwendungen, aber mit diesen kleinen SLIME-Hacks bewaffnet, werde ich beim nächsten Mal Qt in Erwägung ziehen, wenn ich Bedarf an Desktopgraphik habe.

Lektionen aus dem Erfolg von Clojure und dessen Bedeutung für Lisp

Some people give me the impression that they have a religious

relationship to CL. Now when a new Lisp dialect shows up, it just has to suck (like basically all other Lisps announced here in the past 6 years). — André Thieme, comp.lang.lisp, 20. Feb 2009

Was geschieht heutzutage in der Lispwelt? Seit der Fertigstellung von ANSI Common Lisp Anfang der 90er Jahre scheint die Entwicklung von Lisp als Sprachfamilie zu stagnieren. Wo man hinblickt, nur zwei Namen: Scheme und Common Lisp.

Nun entwickelt sich Scheme immerhin fort, wenn auch in den Augen vieler in eine völlig absurde Richtung, die die raison d'être der Sprache selbst geradezu in Frage stellt. Man scheint nicht zu wissen, was das Kind eigentlich mal werden soll: eine sehr einfache Sprache und ein Grundgerüst für den Bau von höheren Werkzeugen, oder doch eine als solche praktikable, aber entsprechend weniger hübsche Sprache, die sich mit unschönen Dingen wie Modulen und Syntax herumschlagen muß?

Darüber ist man sich im Lande Common Lisp indes ganz und gar einig: Lisp sollte eine praktisch einsetzbare Sprache sein, die alles für den Programmierer Nötige mitliefert. Mag das Resultat auch wie ein von Kriegen gezeichneter Veteranfrankenstein aussehen — wen kümmert's? Solange die Verwendung Spaß macht, kann einem doch egal sein, ob Theoretiker die Sprache nun besonders elegant finden oder nicht. Da nimmt man auch gern in Kauf, daß viele Dinge über ein systemnahes C-Interface bereitgestellt werden, Frevel hin oder her.

APL is like a diamond. It has a beautiful crystal structure; all of its parts are related in a uniform and elegant way. But if you try to extend this structure in any way — even by adding another diamond — you get an ugly kludge. LISP, on the other hand, is like a ball of mud. You can add any amount of mud to it and it still looks like a ball of mud. — Joel Moses

Naturgemäß passen sich in das Frankensteinmonster Common Lisp alle denkbaren Arten von Erweiterungen ein. Auch größere Paradigmenwechsel können eingebettet werden: Neben den unzähligen Prolog-in-Lisp-Varianten gibt es unter anderem die an Haskell erinnernde funktionale Sprache Qi und Kenny Tilton's Dataflow-Erweiterung Cells.

Einerseits ist das ein schönes Feature von Lisp. Andererseits macht es dies aber gerade für den typischen idealistischen Lisp-Jüngling zu einem harten Brocken, Verbesserungen in der Sprache selbst anzuleiern. Daß welche sinnvoll wären, bestreiten auch die eingefleischtesten Lispveteranen nicht, aber den ANSI-Prozeß neu aufrollen? Die vielen Implementierungen alle auf einen neuen Standard einschwören? Zeit in einen langwierigen Community-Prozeß investieren, dessen Ausgang wahrscheinlich eh nicht sonderlich strahlend sein wird? Nein danke. Schreib doch lieber eine Bibliothek. Schau, Lisp ist so erweiterbar, da geht das.

So kommt es denn auch, daß manch einer versucht, sein eigenes „Common Lisp 2.0“ zu entwerfen — und kläglich am Desinteresse der Gemeinschaft scheitert. Praktisch jeder Verbesserungsversuch wird kurzerhand mit der knappen Feststellung abgetan, die paar Neuerungen seien den mühevollen Umstieg nicht wert, und überhaupt habe man das alles schon vor dreißig Jahren ausprobiert und keiner habe etwas davon wissen wollen. Warum schreibst Du nicht einfach eine Bibliothek?

Man mag versucht sein, diese scheinbar überhebliche Grundeinstellung der Lispgemeinde als reine Bornhiertheit abzutun. In Wahrheit aber handelt es sich um puren Pragmatismus. Wenn eine Sprachfamilie so beschaffen ist, daß man einen Konzept-Interpreter für seinen eigenen Dialekt an einem Wochenende schreiben kann, dann passiert dies nunmal auch jeden Monat mindestens einmal. Würde man jedem der so entstehenden Minidialekte seine volle Aufmerksamkeit widmen, hätte man eine Menge Arbeit, von der man höchstwahrscheinlich nichts hat. Und mal im Ernst: Würde der Gemeinde eine weitere Bibliothek nicht tatsächlich mehr bringen als drei neue Interpreter für inkompatible neue Dialekte, die sich nur oberflächlich voneinander und den schon existierenden unterscheiden?

Geschieht in der Lisp-Welt, um die Frage vom Anfang endlich aufzugreifen, also zwangsweise gar nichts? Auch das wäre eine Fehleinschätzung. Die Wahrheit ist, daß sich auch Lisper nach einem hell leuchtenden Stern am Horizont sehnen, der ihnen die Hoffnung gibt, in etwas Zukunftssicheres zu investieren. Machen wir uns nichts vor: Auch, wenn die Lispgemeinde am Wachsen ist, den Industrieliebling Java wird eine stagnierende Sprache wie Common Lisp nie einholen. Was also tun, um in den Genuß einer größeren Marktakzeptanz zu kommen? Was, um es mit der Fülle von Bibliotheken aufzunehmen, die die Großen zu bieten haben?

Die Antwort scheint heute klar und deutlich sichtbar zu sein: Wir schlagen Java mit seinen eigenen Waffen, und zwar durch — Clojure. Nicht JFLI und nicht ABCL, nicht eine Brücke von Common Lisp nach Java und nicht ein CL-System auf der JVM, sondern ein neues Kind der Lispfamilie. Wie kommt es, daß selbst die scheinbar so sture (man könnte auch sagen: traditionsbewußte) Lispgemeinde diesen frech in modernem Gewand umhersausenden Neuling mit Freude umarmt, wo sie doch bislang jeden anderen, der es versuchte, in einem achtlos gebastelten, wackeligen Korb aufs Meer hinaustreiben hat lassen?

Die Gründe lassen sich nur schwer allesamt erfassen. Was Clojure gegenüber früheren Versuchen der Lispmodernisierung schon einmal klar auszeichnet, ist, daß ihr Papa Rich Hickey mit Common Lisp bereits zum Beginn seines Projekts gut vertraut war und selbst zur Lispgemeinde gehörte. Ihm war bewußt, was an Common Lisp so großartig ist, und er schaffte es, dieses Wissen bei seinem eigenen Sprachentwurf gekonnt zu nutzen. Herr Hickey traf jede seiner Designentscheidungen mit der Kenntnis der Lisptradition im Hinterkopf. Auf diese Weise gelang es ihm, ein Lisp zu entwickeln, das nicht nur neue Märkte anspricht, sondern auch den Lisper, der seine liebgewonnenen kleinen sprachlichen „Lispigkeiten“ nicht ohne einen Kampf hergibt.

Andererseits ist Clojure eben gerade kein Common Lisp 2.0. Papa Hickey ließ sich nicht davon beirren, daß Common Lisp an sich bereits eine sehr gute Sprache war. Er hatte eine Vision, und die zog er durch, ohne mit der Wimper zu zucken. Eine funktionale Sprache sollte es sein. Nebenläufigkeitsfreundlich. Java-kompatibel. Alle schönen Erinnerungen an Common Lisp mußten hinter diesen Grundzielen zurückstecken. Nie die Lispigkeit außer Acht lassen — aber Kompatibilität mit Common Lisp ist kein Kriterium! So zieht folglich auch das typische „Wozu-das-alles-Du-hättest-nur-eine-Bibliothek-schreiben-müssen“-Argument nicht, und auch der erfahrene Lisper wird genötigt, sich Clojure doch ein bißchen genauer anzusehen, um ein Urteil zu fällen.

Wahrung der Lisptradition auf der einen Seite und Mut zu einem Neubeginn auf der anderen: Rich Hickey wäre mit seiner Kreation sehr wahrscheinlich nicht so weit gekommen, hätte er einen dieser beiden Aspekte außer Acht gelassen. Es drängt sich die Frage auf, ob eine weitreichendere Lektion hinter dieser Erkenntnis steckt als bloß eine softwarebezogene.

Wie dem auch sei, Clojure ist hier, und sie wird uns eine Weile lang begleiten. Heißen wir unseren frechen, aber klugen kleinen Neuling willkommen! Möge sie glücklich aufwachsen und zusammen mit ihren Geschwistern die Zukunft prägen.

Öffentliche Petition zum bedingungslosen Grundeinkommen

Auf der Online-Petitionsplattform des Petitionsausschusses des deutschen Bundestages läuft derzeit eine Petition für die Diskussion über ein bedingungsloses Grundeinkommen. Ich habe von ihr leider erst gerade eben durch den Spiegelfechter erfahren. Das ist bedauerlich, weil sie nur noch bis heute mitternacht läuft und ich daher nicht mehr wesentlich zur Rekrutierung von Mitzeichnern beitragen kann.

Immerhin scheint die Petiton mit über 50.000 Mitzeichnern mit immensem Abstand die bislang meistgezeichnete Online-Petition zu sein. Das ist doch mal eine erfreuliche Nachricht.

Freitag

Der Freitag ist laut dem Spiegelfechter die „letzte ‚links-intellektuelle‘ Wochenzeitung“. Ist er ein Printmedium, das es sich zu abonnieren lohnt? Vielleicht gar das einzige?

Kryptofaschistische Blut- und Bodenideologie des Sportfanatismus

Aus dem Kapitel „Risiken des Sports“ des Buches „Sport und Apotheke — Möglichkeiten der Beratung und des Sortiments“ (Schlemmer, Schmitt):

M. Kröher [25] berichtet in seinem Beitrag „Der Mensch im Laufrad“ ... über einen Sportphysiologen eines renommierten deutschen Forschungsinstituts, der folgende Aussagen machte: „Wer glaubt, das Leben könne nicht nur länger dauern, sondern auch länger Spaß machen, wenn man nur oft genug joggt, schwimmt oder schwitzt, der sitzt der kryptofaschistischen Blut- und Bodenideologie auf, wie sie Luis Trenker und andere verknarzte Wurzelsepps vertreten. Gegenbeispiele wie Marlene Dietrich oder Geheimrat Goethe, die zeitlebens nie eine Hantel in die Hand genommen haben und trotz ihres ausschweifenden Lebenswandels steinalt geworden sind, passen einfach nicht ins gesellschaftspolitische Bild, das man dem modernen Freizeitmenschen von sich zeichnen will.“

Die Referenz [25] bezieht sich hier laut Buch auf: „M. Kröher, Südd. Zeitung, Feuilletonbeilage vom 19.3.1988“.

Außerdem wird Hans Magnus Enzensberger zitiert:

Menschen mit einem einigermaßen intakten Triebhaushalt nehmen Wörter wie „Leibeserziehungen“ oder „körperliche Ertüchtigung“ überhaupt nicht in den Mund.

Inkscape

Dr. Mulk ist guter Dinge.

Ich habe endlich eine angemessene Hauptseite für diese Website angefertigt. Was mir dabei immens geholfen hat, waren das Graphiktablett meiner Schwester und Inkscape. Das Programm ist wahrlich ein Wunderding.

Dovecot, launchd und die Leopard-Firewall

Mac OS X 10.5 enthält eine anwendungsorientierte Firewall, in die man nicht Ports zum Öffnen einträgt, sondern Anwendungen, deren Dienste von außen zugänglich gemacht werden sollen. An sich ist das keine schlechte Idee. Schließlich ist es angenehmer, den verwendeten Port nur einmal einstellen zu müssen, und zwar in der Anwendung selbst.

Allerdings hat die Sache einen Haken: Die Firewall öffnet nur solche Ports nach außen, die von Anwendungen geöffnet werden, die nach ihr gestartet wurden. Läßt man, wie in meinem Falle, zum Beispiel Dovecot von launchd beim Systemstart gleich mitstarten, bemerkt die Firewall das noch nicht und läßt die Ports zu.

Man könnte nun denken, daß man das nach wie vor vorhandene Befehlszeilentool ipfw verwenden könne, um die betroffenen Ports pauschal nach traditioneller Art zu öffnen. Mitnichten! In Wahrheit ist ipfw standardmäßig derart eingestellt, daß es alle Verbindungen durchläßt. Die Anwendungsfirewall sitzt erst dahinter und filtert das, was von ipfw nicht bereits herausgenommen wird.

Es scheint, als hülfe es in der Tat nur, Dovecot nach dem Einloggen manuell noch einmal zu starten.

F-Spot und ipernity

F-Spot ist ein wahrlich wunderbares Photoverwaltungsprogramm. Neuerdings unterstützt es sogar von Haus aus Export zum Photo-Sharing-Dienst Zooomr (neben Flickr, SmugMug, Google Picasa, 23HQ und anderen, die schon länger unterstützt werden). Lediglich Unterstützung für ipernity fehlt bislang.

Das erst kürzlich veröffentlichte API von ipernity unterscheidet sich leider maßgeblich von dem von Flickr. Daneben scheint es zwar ein undokumentiertes Flickr-kompatibles API zu geben sowie einige Patches für F-Spot, die Unterstützung hierfür einbauen, doch fanden diese nie offiziell in F-Spot Einzug. Ich habe mir gestern das veröffentlichte ipernity-API angesehen und seitdem einen Patch zusammengestellt (zu Subversion-Revision 4681), der F-Spot die Verständigung mit ipernity ermöglicht.

Der Patch ist häßlich. Ich glaube kaum, daß er in die Vanilleversion von F-Spot übernommen werden wird. Vielleicht fließen aber zumindest Teile davon ein oder jemand fühlt sich dadurch inspiriert, einen guten Patch zu schreiben.

Diskret-topologische Wahrscheinlichkeitslogik und die transfinite Behandlung endlicher Gruppen

Bekanntlich bietet unser aller Lieblingsdozent Prof. Ignatius Schottenschneider dieses Semester die beliebte Vorlesung Diskret-topologische Wahrscheinlichkeitslogik in mehreren Veränderlichen II für Differentialinformatiker und -statistiker an, die sich unter anderem mit solch aufregenden Strukturen wie dreifachen amalgamierten Zählmaßen beschäftigt sowie damit, wie diese in diskreten und transfiniten Wahrscheinlichkeitsringen wirken. Für Leute, die an diesem spannenden Thema interessiert sind, wird es jetzt voraussichtlich auch ein Seminar zu transfiniten Methoden über endlichen Gruppen von Prof. Maximum Likelihood geben, der ebenfalls der Arbeitsgruppe transfinite Wahrscheinlichkeitslogik angehört. Wärmstens zu empfehlen!

Wörter zählen mit Common Lisp

Fefe hat auf seinem Blog vor einer guten Woche folgenden Aufruf veröffentlicht:

Ich mache gerade einen kleinen Test, bei dem ihr mir helfen könnt, wenn ihr Lust habt. Es geht darum, eine vergleichsweise triviale Aufgabenstellung in Skriptsprachen zu implementieren. Die Aufgabenstellung ist: stdin lesen, in Wörter splitten, für die Wörter dann jeweils die Häufigkeit zählen, am Ende die Wörter mit Häufigkeiten nach letzteren sortiert ausgeben. Einfach genug, in perl ist das in unter 10 Zeilen ordentlich zu machen ...

Daher wäre es mir lieb, wenn mir jemand für die fehlenden Sprachen Sourcen zuschickt. Ich erwarte da jetzt nicht AppleScript oder so, aber C# wäre z.B. nett, und Tcl. Und vielleicht noch sowas wie LISP oder Scheme.

Mindestens Christoph und ich haben jeweils Lösungen in Common Lisp eingeschickt. Leider warten wir beide noch auf eine Antwort. Christoph hat seinen Code auf seinem Blog veröffentlicht. Ich tue hiermit dasselbe. Vielleicht interessiert sich ja jemand dafür.

(defun count-words (stream)  
  (let ((table (make-hash-table :test 'equal))  
        (word (make-array '(100)  
                          :element-type 'character  
                          :fill-pointer 0  
                          :adjustable t)))  
    (loop for char = (read-char stream nil nil)  
          while char  
          when (not (member char '(#\Newline #\Space)))  
            do (setf (fill-pointer word) 1  
                     (elt word 0) char)  
               (loop for char = (read-char stream nil nil)  
                     until (member char '(nil #\Newline #\Space))  
                     do (vector-push-extend char word))  
               (incf (gethash (copy-seq word) table 0)))  
    table))  
 
(defun main (&optional (stream *standard-input*))  
  (let ((words (loop for v being the hash-values of (count-words stream)  
                           using (hash-key k)  
                     collect (list v k))))  
      (format t "~:{~&~D~T~A~}"  
              (sort words #'< :key #'first)))  
  0) 

Genossenschaften vs. öffentliche vs. Privatunternehmen

Unsere regionale Sparkasse sowie die entsprechende Raiffeisenbank (d.h. die jeweiligen Regionalbanken im Landkreis Weilheim-Schongau) haben am 9.10. eine gemeinsame Pressekonferenz (Artikel aus dem Weilheimer Tagblatt vom 10.10.2008) gegeben, in der sie betonten, daß im Angesicht der Bankenkrise alle Einlagen bei ihnen sicher seien, da speziell die Sparkassen und Volks- und Raiffeisenbanken ein robustes Sicherungssystem hätten und außerdem regional ausgerichtet sowie nicht auf „risikoreich[e] Geschäfte“ angewiesen seien.

Schon irgendwie interessant, das alles. Die Sparkassen als öffentlich-rechtliche Einrichtungen mit gesetzlich vorgeschriebenem Auftrag einerseits, die genossenschaftlich organisierten, mitgliederverantwortlichen Volks- und Raiffeisenbanken andererseits, Seite an Seite -- und das ziemlich erfolgreich. Laut dem Artikel beträgt ihr gemeinsamer Marktanteil in der Region 80 %.

Feministische Doppelmoral

Es schreibt Dr. Bruno Köhler im Artikel „Jungen und Geschlechterpolitik“ (als Abschnitt enthalten im „Handbuch Jungen-Pädagogik“):

Die ehemalige Schulministerin von NRW, Ute Schäfer (SPD), behauptete angesichts von Mädchenzukunftstag und beruflicher Frauenförderung allen Ernstes, die geschützte Freiheit der Berufswahl verbiete es, Maßnahmen zur Erhöhung des Anteils männlicher Erzieher und Lehrer durchzuführen (Rheinische Zeitung 2003).

Irgendwie amüsant.

Dresden for the win!

Das Handbuch für Frau und Mann 2007 der Landeshauptstadt Dresden äußert sich zu Gleichstellungsfragen erfrischend uneinseitig. Die Gleichstellungsbeauftragte dort scheint im Gegensatz zu vielen ihrer Kolleginnen nicht der blinden Misandrie verfallen zu sein, sondern sich wirklich der Gleichberechtigung der Geschlechter zu widmen. Löblich!

Verschiedene Beweisstrategien in der Mathematik und ihre Anwendungen

Betmodelle

Das Betmodell ist ein in der Logik weit verbreitetes Mittel, um Sätze zu beweisen, von denen niemand weiß, wie man sie angehen soll, zum Beispiel komplizierte logische Aussagen wie „aus A folgt A“. Mehrere Logiker versammeln sich dabei um ein Betmodell und praktizieren geheime Riten, die dazu führen sollen, daß sich ein Knoten in eine zukünftige sogenannte „mögliche Welt“ aufmacht, aus dem der Beweis für den betreffenden Satz sauber aufgeschrieben herausfällt.

Ob das Verfahren funktioniert, ist nicht bekannt, da jeder Orden der Logiker, der diese Riten durchführen darf, strikte Geheimhaltung der Ergebnisse gegenüber Uneingeweihten praktiziert.

Kripke kritisierte die traditionelle Sektiererei um die Betmodelle herum und entwickelte seine eigene Variante, das sogenannte Kripkemodell. Die komplizierten Tänze, die man um ein Kripkemodell herum tätigen muß, um ein Ergebnis zu erhalten, konnte jedoch noch niemand durchführen, ohne sich diverse Gliedmaßen zu brechen, weshalb es bislang keine weite Verbreitung finden konnte.

Beweis aus Zeitgründen

Der Beweis aus Zeitgründen (auch: argumentum ad tempus) ist ein integraler Bestandteil der abstrakten Algebra. Er gewinnt seine Gültigkeit zehn Minuten vor dem Ende des Vortrags, den der Algebraiker hält, und kann dann genutzt werden, um Aussagen mit der Angabe „klar“ oder „trivial“ zu beweisen, für die ihm keine andere Erklärung einfällt.

Berühmte Ergebnisse wie der Satz von Frobenius oder der Fundamentalsatz der Algebra wären ohne diese fortgeschrittene Beweistechnik undenkbar, weshalb sie zum Standardhandwerkszeug gehört, das einem Mathematikstudenten in den Anfängervorlesungen mitgegeben wird.

Beweis durch Widerspruch

Der Beweis durch Widerspruch (auch: reductio ad absurdum) ist ausschließlich Korrektoren von Übungs- und Klausuraufgaben vorbehalten. Die Technik besteht darin, eine Behauptung des Prüflings zu widerlegen, indem man ihm mit einem einfachen „Nein!“ widerspricht. Eine weitere Angabe von Begründungen erübrigt sich in diesem Fall, da allgemein angenommen wird, daß der Prüfling mit genügend Zeit selbst zur Kenntnis gelangen wird, daß seine Behauptung falsch war.

Strukturelle Intuition

Variante der vollständigen Intuition (siehe unten), die vor allem in der Logik und der Informatik Anwendung findet, um Algorithmen und Softwaresysteme zusammenzubauen. Ihre Herkunft gilt nur bruchstückhaft als gesichert. Laut einer Legende soll Alonzo Church sie entwickelt haben, als er unter einem Baum sitzend von einem herabfallenden Stück Vogelkot getroffen wurde.

Die Korrektheit der strukturellen Intuition ist die Kernaussage der Churchschen These, die innermathematisch nicht bewiesen werden kann, ohne die die Informationstechnologie allerdings in sich zusammenfallen würde wie ein Kartenhaus, da ein sinnvolles Arbeiten ohne strukturelle Intuition in der Informatik nicht denkbar wäre. Experten merken regelmäßig an, daß die Qualität der existierenden Computertechnik der eindeutige Beweis dafür sei, daß dies schon lang geschehen sei.

Transfinite Intuition

(Auch: Beweis durch Hinschauen) Eine Weiterentwicklung der vollständigen Intuition (siehe unten). Während Algebraiker und Logiker ihre Zeit damit verschwenden, Beweise formal sauber aufzuschreiben, was genauso gut ein Computer könnte, verbringen Analytiker die durch das konsequente Ignorieren von Formalia freigewordene Zeit damit, ihre übernatürlichen Sinne zu schärfen.

Aus dieser Tradition heraus entstand das Beweisprinzip der transfiniten Intuition, die heute zum tragenden Fundament der Analysis gezählt wird. Dabei produziert der Analytiker Aussagen aufgrund seiner überlegenen Fähigkeit zur Herbeiführung göttlicher Eingebungen, und beweist sie dann in einem Formalismus, der speziell auf ihre Psyche zugeschnitten ist und die folgerichtig niemand sonst (insbesondere auch kein anderer Analytiker) nachvollziehen kann. Dies äußert sich zumeist in scheinbar abstrusen Zeichnungen z.B. von grünen Strichmännchen (sogenannten „Affen“) oder inneren sowie äußeren Körperteilen, die durch mit für einen Außenstehenden sinnfrei wirkenden Beschriftungen versehene Pfeile verbunden sind.

Böse Zungen behaupten, die Fähigkeit zur transfiniten Intuition werde dem Analytiker durch Mephisto für den Verkauf seiner Seele verliehen. Die Tatsache, daß nur wenige Analytiker später gut bezahlte Stellen in der Wirtschaft erhalten, macht diese Hypothese jedoch unglaubwürdig.

Vollständige Intuition

Die traditionelle Beweistechnik der Algebra, die vom römischen Philosophen Seneca eingeführt wurde. Um maximale Effektivität bei minimalem Aufwand zu erreichen, legte sich Seneca zum Beweis eines philosophischen Standpunkts in seine Hängematte und schlief tief und fest. Den Gedanken, der ihm nach dem Aufwachen im Sinn war (zumeist „ich bin hungrig“ oder „die Hängematte hängt schief“), schrieb er auf, und wann immer er genügend Aussagen gesammelt hatte, veröffentlichte er sie in Form eines Werkes in mehreren Kapiteln.

Heute wird die vollständige Intuition nicht mehr sehr häufig angewandt, weil festgestellt wurde, daß sie im wesentlichen nur noch selten neue Erkenntnisse hervorbringt. Lediglich unter Studenten im Grundstudium ist die Beweistechnik nach wie vor beliebt, wird allerdings von ihren Korrektoren nur allzu oft nicht gewürdigt.

Die universelle Eigenschaft der Klumpentopologie

Satz (universelle Eigenschaft der Klumpentopologie)

Sei X eine Menge. Dann gibt es genau eine Topologie T(X) auf X, die kein Werk Satans ist. T(X) heißt Klumpentopologie auf X.

Beweis

Existenz: Setze T(X) = {∅, X}. Die Topologie ist dann offenbar kein Werk Satans, weil sie nur Trivialitäten enthält.

Eindeutigkeit: Klar durch Hinschauen. (Betrachte einen beliebigen topologischen Beweis, in dem die vorkommenden Topologien von der Klumpentopologie verschieden sein können. Man findet dann leicht einen Homöomorphismus zwischen dem Beweis und dem Raum der Pfeile im ℜ². Da dieser ein Werk Satans ist, ist auch der Beweis ein Werk Satans. Da die Wahl des Beweises beliebig war, folgt die Behauptung.)

QED.

Ein weiterer Klassiker

user> (let [sieve (fn sieve [[x & xs]]  
                    (lazy-cons x (sieve (filter #(pos? (rem % x)) xs))))]  
        (def primes (sieve (iterate inc 2))))  
#'user/primes  
user> (take 20 primes)  
(2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71)  
user> (nth primes 1000)  
7927 

Diese Definition ist sicherlich nichts für die praktische Anwendung, aber dafür ist sie unterhaltsam.

Vorträge über Clojure

Bei blip.tv gibt es eine Reihe von Aufzeichnungen von Vorträgen über Clojure. Momentan findet man:

Rich Hickey ist ein guter Redner, und das Thema ist interessant.

Backquote in Clojure und die Referenztransparenz

Folgende Interaktion mit der Clojure-REPL ist für einen Common Lisper wie mich einigermaßen verblüffend:

user> `fn  
clojure/fn  
user> (macroexpand '(defn mulk [] fn))  
(def mulk (clojure/fn ([] fn)))  
user> (let [fn 100]  
        (defn mulk [] fn))  
#'user/mulk  
user> (mulk)  
100 

Wenn fn doch augenscheinlich dasselbe bezeichnet wie clojure/fn, wie kann es dann sein, daß lokales Binden desselben nicht dazu führt, daß das Makro in etwas expandiert, das versucht, die Zahl 100 aufzurufen (was freilich ein Fehler wäre)?

Vielleicht liegt es hieran:

user> (identical? 'fn 'fn)  
false  
user> `fn  
clojure/fn  
user> 'fn  
fn 

Der Leser von Clojure interniert Symbole nicht, und backquote und quote verhalten sich unterschiedlich, je nach dem, welche Variablen gerade in den aktuellen Namensraum importiert sind. (Ich sage hier bewußt „Variablen“ und nicht „Symbole“, denn wenn das Namensraumsystem Symbolen einen Namensraum gäbe und nicht den Bindungen, die an diesen Symbolen hängen, würde das Ergebnis höchstwahrscheinlich anders aussehen, weshalb ich vermute, daß das nicht der Fall ist. Andererseits sage ich auch nicht „Bindungen“, weil ich mir nicht sicher bin, ob wirklich Bindungen importiert werden oder doch irgendwas anderes. „Variablen“ ist hingegen ein hinreichend schwammiger Ausdruck, um so zu tun, als wüßte ich, wovon ich spreche.)

Ich weiß noch nicht, wie Clojures Namensraumsystem funktioniert, aber es scheint beim Vermeiden von Unhygiene und Referenzintransparenz irgend eine Rolle zu spielen. Nur eines kann ich sagen: Das Gesamtsystem scheint seine Arbeit gut zu machen.

Clojure

Clojure ist ein vollständig mit der JVM integrierter, funktionaler Lispdialekt mit einem besonderem Fokus auf Nebenläufigkeit.

Man spürt, daß der Designer der Sprache, Rich Hickey, Ahnung von Lisp, insbesondere Common Lisp, hat. Dennoch, oder vielleicht gerade deshalb (wofür sonst eine neue Sprache?) geht Clojure vom Konzept her völlig andere Wege als Common Lisp, und der Programmierstil in den beiden Sprachen ist kaum miteinander zu vergleichen.

Clojure ist interaktiv. Es hat Unterstützung für SLIME, ein großer Pluspunkt, wenn es nach mir geht. Für ein Nicht-Common-Lisp ist das ziemlich ungewöhnlich. Überhaupt ist die Mischung aus dynamischen und statisch-funktionalen Aspekten verblüffend. Einerseits bietet Clojure echte, threadgebundene dynamische Variablen, andererseits sind alle lexikalischen Bindungen unveränderlich wie in ML-artigen Sprachen. Funktionsbindungen sind dynamisch (nicht global lexikalisch wie in anderen Lisps!) und können jederzeit um- oder neu gebunden werden. Die Sprache ist dynamisch typisiert, erlaubt aber optionale Typangaben; mehr noch: Beliebige Metadaten lassen sich mit Daten und damit auch mit Code verknüpfen, auf die Makros und der Compiler ebenso wie Anwendungscode zugreifen können.

Makros sind einfache prozedurale, DEFMACRO-artige. Zugleich trennt Clojure Funktions- und Variablennamen nicht. Irgendwie soll der kuriose, nicht internierende, nebenwirkungsfreie Leser das Problem der Unhygiene, die durch die Kombination von Lisp-1 mit prozeduralen Makros auftreten kann, umgehen. Damit muß ich mich bei Gelegenheit beschäftigen.

Die Integration mit der JVM ist so vollständig, wie man sich nur wünschen kann. Jede Clojure-Datenstruktur ist eine Instanz einer Klasse mit Methoden, die man auch von Java aus sinnvoll aufrufen kann. Umgekehrt fühlen sich Java-Objekte in Clojure-Code genauso wohl. Clojure kann fröhlich alle Methoden aufrufen und die normalen Java-Collections mit den eingebauten Funktionen für Folgen verarbeiten. nth zum Beispiel funktioniert für alles, was aufzählbar ist.

Und dann ist da noch die Unterstützung für Streams, als wären sie normale Listen oder Vektoren. Folgende Definition funktioniert und liefert die unendliche Folge aller Fibonaccizahlen:

(def fibs (lazy-cat [0 1] (map + fibs (drop 1 fibs))))  
 

(take 10 fibs) liefert nach dieser Definition die ersten 10 Fibonaccizahlen. Sieht aus wie Haskell, nicht? Und das, obwohl Clojure eine streng ausgewertete Sprache ist. Wie eigentümlich!

Die Unterstützung für Nebenläufigkeit basiert lose auf einer Variation des Aktormodells und Software Transactional Memory, eine Kombination, von der ich schon länger denke, daß sie extrem sinnvoll ist. Wer sich dafür interessiert, könnte den Vortrag von Rich Hickey darüber interessant finden.

Zuguterletzt dasjenige Detail, das mir beim ersten Ausprobieren einer neuen Sprache immer gleich auffällt:

user> (/ 10 6)  
 
5/3  
 

Korrekte Arithmetik mit Brüchen und ohne Überläufe! Der Tag ist gerettet.

Alles in allem bin ich von Clojure beeindruckt. Endlich ein neuer Lispdialekt, der Zukunft haben könnte!

Das deprimierende Thema der Familienpolitik

Zur österreichischen Nationalratswahl

Das liberale Forum:

„Fair wäre[n] ... flächendeckende, ganztägige hochqualitative Betreuungsangebote ab dem 1. Lebensjahr des Kindes ...“

Die Grünen:

  • „Erhöhung der Verfügbarkeit von Kinderbetreuungseinrichtungen“

  • „Ausdehnung und Flexibilisierung der Öffnungszeiten in Kinderbetreuungseinrichtungen“

  • „Stärkere Subventionierung der Kosten der Kinderbetreuung“

Die KPÖ:

„Flächendeckendes Netz kostenloser Kinderbetreuungseinrichtungen von der Krippe bis zum Hort ...“

Ist denn wirklich überhaupt niemand wählbar?

Zur bayerischen Landtagswahl

Die ödp:

„Die Leistung von Eltern in der Kinderbetreuung und –erziehung wollen wir endlich als Arbeit anerkennen und bezahlen.“

Die Bayernpartei:

„Eine einseitige Bevorzugung von Kitas, Ganztagsschulen u.Ä. lehnen wir definitiv ab; als Angebot sind sie zu begrüßen, allerdings soll hier nach Möglichkeit privaten Trägern der Vorzug gegeben werden. Eltern, die ihre Kinder selbst erziehen wollen, dürfen finanziell nicht benachteiligt werden.“

Warum gibt es keine österreichische ödp? Und: Warum müssen alle Sozialisten neofeministisch festgebissene Familienfeinde sein? Bin ich wirklich der einzige, der gerne die KPÖ wählen würde bis auf diesen einen Punkt?

SOLID, ein SLIME für O'Caml

SOLID ist ein Emacs-Modul, das für O'Caml eine Entwicklungsumgebung bieten soll, die an SLIME angelehnt ist, komplett mit REPL-Integration, Debugger, Onlinehilfe zu Bezeichnern im Quelltext und Wortvervollständigung.

The aim is to be what SLIME is for Lisp -- the ultimate interactive, integrated, development environment (in Emacs of course).

Da SLIME eines der Argumente für Common Lisp schlechthin (insbesondere gegenüber anderen Lisps) ist, interessiert mich SOLID sehr. SLIME hat meine Ansprüche an IDEs ziemlich hochgeschraubt. Wenn SOLID gut ist, kann ich mich vielleicht mehr mit Caml anfreunden.

Das schöne C-Interface von Objective Caml

Ich habe mir gerade das C-Interface von Objective Caml angesehen. Ich muß zugeben, daß es sehr überzeugend wirkt. Man kann damit:

  1. C-Funktionen direkt von Caml aus aufrufen. (O.K., das ist wohl das Minimum, das ein ernstzunehmender Fremdaufrufsmechanismus bieten muß. Andererseits gibt es auch welche, die nicht einmal das unterstützen.) Die Deklarationen sehen denen von CFFI dem Grunde nach sehr ähnlich.

  2. Caml-Funktionen von C aus aufrufen. Man muß dafür durch eine spezielle C-Trampolinfunktion hüpfen. Argumente und Rückgabewerte werden nicht automatisch zwischen C- und Caml-Typen umgewandelt.

  3. Von C aus Caml-Datentypen dekonstruieren.

  4. Caml-Programme in C-Programme einbetten.

Punkt 4 ist derjenige, in dem O'Caml allen freien Lispcompilern außer ECL voraus ist. (Ich zähle Toilet Lisp mal noch nicht. ;)) Er ist außerdem derjenige, der es mir erlauben würde, O'Caml statt C++ für den Toilet-Lisp-Compiler zu verwenden, wenn ich das wollte.

Vielleicht sollte ich das. Caml ist die einzige Sprache neben C und C++, die offizielle LLVM-Bindings hat. Allerdings würde das wahrscheinlich bedeuten, daß ich einiges an der Struktur sowohl des Interpreters als auch des Compilers ändern müßte. Außerdem würde das Kompilieren des Compilers schwieriger werden. Weder Xcode noch GNUstep-Make haben Unterstützung für O'Caml.

Im Moment habe ich nicht viel Zeit fürs Programmieren. Wenn die Prüfungen vorbei sind, werde ich mich dieser Frage ernsthafter widmen können.

Survey: Mehrsprachendokumentationsgeneratoren

Da ich zur Zeit eine gewisse Motivationsflaute habe, was das Debuggen des Toilet-Lisp-Compilers angeht, habe ich heute damit Zeit totgeschlagen, nach Dokumentationssystemen zu suchen, die ich verwenden könnte, um meinen Code ein bißchen zu illustrieren.

Nicht, daß ich nicht schon solche Systeme verwendet hätte. Den öffentlichen Teil von Objective-CL zum Beispiel habe ich recht gründlich dokumentiert, wofür ich mir selbst ein kleines Dokumentationsgenerationsskriptchen in Lisp geschrieben hatte. Leider ist dieses aber sehr stark auf Common Lisp zugeschnitten und könnte, so fürchte ich, nicht einmal ansatzweise für andere Sprachen herhalten (nun, vielleicht noch für Emacs-Lisp... ansatzweise).

Im Falle von Toilet Lisp gestaltet sich die Sache schwieriger. Zum einen besteht es aus viel mehr API, worauf mein Homebrew-Dokumentationssystem nun nicht gerade optimiert ist. Zum anderen ist Toilet Lisp in einer Kombination aus ganz verschiedenen Sprachen implementiert: Objective-C im Kern, Lisp in der Bibliothek und (seufz) Objective-C++ im Compiler. Sogar ein Perlskript war eine zeitlang dabei.

Ich könnte natürlich jeden Teil für sich dokumentieren, aber dann müßte ich mir verschiedene Dokumentierungskonventionen merken, und soviel Hirn habe ich nicht. Deshalb entschloß ich mich heute, ein wenig Zeit mit der Recherche über Dokumentationssysteme totzuschlagen, die mehrere Sprachen unterstützen.

Das Ergebnis ist, daß es so viele universelle Dokumentationssysteme gar nicht gibt, wie man annehmen könnte. Gefunden habe ich eigentlich nur zwei, die flexibel genug sind, um exotischere Sprachen wie Lisp und Objective-C zu unterstützen (ja, wirklich! Mit Objective-C sieht's da unerwartet düster aus): Robodoc und Natural Docs. Robodoc erzeugt leider optisch nicht so arg ansprechende Ergebnisse.

Apple bietet für Objective-C und einige andere Sprachen sein eigenes (ebenfalls freies) Tool namens HeaderDoc an. Ich habe es mir nicht genau genug angesehen, aber ich vermute, daß es auch einigermaßen gut möglich sein sollte, es für neue Sprachen anzupassen, da es bereits sehr verschiedenartige unterstützt.

Schließlich habe ich mich vorläufig für Natural Docs entschieden. Objective-C unterstützt es nicht von Haus aus, aber es war nicht schwer, ein kleines Perlmodul zusammenzuhacken, das ihm diese Fähigkeit verleiht.

Für den Fall, daß es jemand brauchen kann:

Download: Modul, Installationsanleitung.

Die Objective-C-Runtime als Compilertarget

Spätestens seit Worse is Better ist uns bewußt, daß unschöne Dinge für das Überleben von Software wohl oder übel von entscheidender Bedeutung sein können. Bei Programmiersprachen ist eines dieser Dinge die nahtlose Zusammenarbeit mit dem Betriebssystem, und das bedeutet in der Regel Integration mit C. Viele interaktiven Programmiersprachen wie Common Lisp leiden unter Schwächen in diesem Bereich.

Nun ist Interaktivität schon etwas, das sich mit C vom Prinzip her ein wenig beißt. C ist eine statisch kompilierte Sprache, noch dazu ohne jegliche Introspektion geschweige denn Reflexion. Daher muß man eine mehr oder weniger dicke Schicht darüberlegen, wenn man etwas Dynamik haben möchte. So eine Schicht nennt man Runtime, und verschiedene Runtimes können sich in Bereichen wie C-Nähe, Effizienz und Dynamik sehr unterscheiden.

Sehen wir uns als Beispiel bestehende freie Common-Lisp-Systeme mit ihren Runtimes an:

  1. CMUCL und SBCL haben effiziente, robuste Runtimes mit einer eigenen Speicherverwaltung, die mehr an eine Lispmaschine erinnert als an ein C-Programm, und die auch sonst eher betriebssystemfeindlich als -freundlich ausgelegt sind.

  2. ECL ist sehr C-nah und kompiliert Code mithilfe von GCC. Dafür leidet die Interaktivität unter der langen Kompilierzeit und der Abhängigkeit von externen Programmen, und die Geschwindigkeit des Systems ist insgesamt eher weniger berauschend.

  3. CLISP ist ein Zwischending. Es ist in C implementiert und sehr interaktiv. Ein nativer Codegenerator fehlt zwar, es wird aber an einem basierend auf libjit gearbeitet. Dennoch ist es nicht viel einfacher, mit C von CLISP aus zu interagieren, als es bei CMUCL und SBCL der Fall ist, weil auch die CLISP-Runtime relativ abgeschottet vom Rest der Welt existiert.

Man kann noch eine Menge anderer Runtimes aufzählen, die außerhalb der Lispgemeinde entstanden sind. NekoVM, HLVM, Squeak, die JVM und die CLR sind allesamt ganz interessante Runtimes (besonders NekoVM*), aber jede einzelne von ihnen leidet unter schlechter C-Integration (Squeak ist hierbei wohl das extremste Beispiel -- kein Wunder, denn Smalltalk war schon immer eine radikal dynamische Sprache und hatte eine entsprechend separatistische Umgebung).

Und dann gibt es da noch Objective-C.

Kompakt, dynamisch, ungeheuer C-nah, nicht aussterbensgefährdet -- und immerhin ist fast der ganze graphische Teil eines beliebten Betriebssystems darauf aufgesetzt, also kann es auch nicht sehr ineffizient sein. Die Objective-C-Runtime bietet sicherlich weniger als z.B. NekoVM. Sie ist low-level, keine Frage. Sie erfüllt jedoch jedes Kriterium, das eine lebensfähige VM in einer C-orientierten Welt erfüllen muß. Sogar Methoden, welche man in der Objective-C-Runtime zur Laufzeit reibungslos auswechseln kann, sind ganz normale C-Funktionen und können als solche aufgerufen werden.

Objective-C ist vielleicht nicht die ultimative, allumfassende Runtime für jede dynamische Programmiersprache der Welt. Aber sie ist näher dran als alles andere, was eine ähnliche Zukunftssicherheit hat.


* O.K., ich geb's ja zu: Der Name hat's mir angetan. Wer kann beim Gedanken an ein süßes Kätzchen schon widerstehen?

FIXNUMs für Toilet Lisp

Toilet Lisp hat jetzt echte FIXNUMs. Sie werden intern von Zeigern dadurch unterschieden, daß ihr niedrigstes Bit eine 1 ist. Natürlich funktioniert das nur, solange „echte“ Objekte nicht an ungeraden Stellen im Speicher anfangen. Offensichtlich hängt die Erfüllung dieser Bedingung von der verwendeten Objective-C-Runtime ab. Bislang scheint die Situation jedenfalls auf meinem System konsistent passend zu sein. Sollte sich die Annahme auf einem gegebenen System als problematisch erweisen, kann man die FIXNUMs immer noch mit einem Compilerflag (kompilieren mit make ADDITIONAL_OBJCFLAGS=-DNO_FIXNUMS) deaktivieren.

Toilet Lisp: MACROLET als COMPILER-LET-Ersatz

Eigentlich sind BLOCK und RETURN-FROM ja Sonderoperatoren. Da ich meinen Interpreter aber nicht zu sehr aufblähen möchte (schon erst recht nicht, weil er irgendwann einen Compiler zur Seite bekommen wird, der dieselben Sonderoperatoren auch alle nochmal verstehen muß), versuche ich, die Anzahl dieser zu minimieren, weshalb ich BLOCK und RETURN-FROM als Makros über CATCH und THROW implementiert habe.

Es gibt ein Paper darüber, wie das geht, doch hat der dort angegebene Code leider einen fatalen Fehler: Er setzt die Existenz eines Operators BLOCK-TO-TAGNAME voraus, der aus einem Symbol ein anderes Symbol macht, das erstens (wie ein GENSYM) nirgendwo sonst vorkommen und andererseits für jede Eingabe eindeutig sein soll (d.h. BLOCK-TO-TAGNAME ist eine injektive Funktion im mathematischen Sinne von SYMBOL nach SYMBOL). Das Schreiben einer solchen Funktion ist nun dummerweise nicht möglich*, wenn man sich nicht Nebenwirkungen zunutze macht, wie z.B. das Auffüllen einer Hashtable. Letzteres wäre wiederum speicherverwaltungstechnisch ein furchtbares Problem, weil die Einträge, jedenfalls in einem einigermaßen einfachen Ansatz, niemals wieder entfernt werden könnten und immer mehr würden.

Also einen Schritt zurück. Wofür ist BLOCK-TO-TAGNAME eigentlich da? Der Grund für seine Existenz liegt darin, daß man gerne das Mapping zwischen Blocknamen und CATCH-Tagnamen vom Makro BLOCK, in dem der CATCH-Tag eingeführt wird, zum Makro RETURN-FROM übermitteln möchte, in dem es verwendet werden muß. Diese Zuordnung muß lexikalisch eindeutig sein, d.h. folgendes liefert 3 zurück, nicht 2:

(block mulk  
  (let ((fn (lambda () (return-from mulk 3))))  
    (block mulk (funcall fn))  
    2) 

Folglich muß der äußere MULK-Block einen anderen CATCH-Tag verwenden als der innere. Ich löse das zur Kompilierzeit, indem ich in der Auswertungsumgebung der Makros eine Zuordnungstabelle anlege, die nach dem Kompilieren verschwindet:

(defmacro block (block-name &body body)  
  (let ((catch-tag (gensym)))  
    `(compiler-let ((%block-mapping% (acons ',block-name ',catch-tag %block-mapping%))))  
       (catch ',catch-tag  
         ,@body))))  
 
(defmacro return-from (block-name &optional value &environment env)  
  `(throw #,(cdr (assoc block-name %block-mapping% :test #'eq))  
     ,value)) 

Dem geneigten Leser wird aufgefallen sein, daß ich hier zwei Features verwendet habe, die es in ANSI Common Lisp nicht mehr gibt, obgleich es sie früher einmal gab: #, und COMPILER-LET. Den Platz von #, hat heute LOAD-TIME-VALUE eingenommen. In der Tat läßt sich LOAD-TIME-VALUE in SBCL ähnlich wie #, verwenden und respektiert sogar die Auswertungsumgebung des Compilers:

CL-USER> (sb-cltl2:compiler-let ((x 10))  
           (load-time-value x))  
10 

Was ist aber mit COMPILER-LET? Nicht, daß mich jemand daran hindern würde, es in Toilet Lisp zu implementieren -- vielleicht mache ich das auch noch. Doch wollte ich nicht möglichst wenige Sonderoperatoren definieren? Wenn es doch schon einen gäbe, der diesen Zweck erfüllte...

Und den gibt es: MACROLET läßt sich effektiv für dieselben Sachen einsetzen wie einstmals COMPILER-LET, auch wenn sein Existenzsinn eigentlich woanders liegt. Ein bißchen Hacking hier und da, et voilà:

(defmacro block (block-name &body body)  
  (let ((catch-tag (gensym)))  
    `(macrolet ((%block-mapping% () `(quote ,(acons ',block-name ',catch-tag (%block-mapping%)))))  
       (catch ',catch-tag  
         ,@body))))  
 
(defmacro return-from (block-name &optional value &environment env)  
  `(throw ',(cdr (assoc block-name (cadr (macroexpand `(%block-mapping%)  
                                                      env))  
                        :test 'eq))  
     ,value)) 

Häßlicher als zuvor mit #, und COMPILER-LET, soviel ist sicher. Dafür funktionieren die Definitionen auch in CMU CL, und das ist doch auch ganz schön. Es schadet dem Debuggingaufwand jedenfalls sicherlich nicht.


* Nun, jedenfalls nicht möglich, solange man die Abstraktionsbarriere nicht bricht (insbesondere solange man die Speicheradresse des Symbols nicht kennt). O.K., als anständiger Informatiker kann ich so eine Aussage natürlich nicht unbewiesen lassen. Seien A und B also frische, verschiedene, uninternierte Symbole mit demselben Namen. (Solche kann man offensichtlich mithilfe von MAKE-SYMBOL konstruieren.) Angenommen, es gäbe eine injektive Funktion f von SYMBOL nach SYMBOL, die nicht die Abstraktionsbarriere bricht, d.h. sie darf nur Namen und Paket des Eingabesymbols betrachten und die Identität mit anderen Symbolen vergleichen. Dann sind A und B für f aber ununterscheidbar aufgrund des Namens und des Pakets. Die einzige Unterscheidungsmöglichkeit wäre Identitätsvergleich mit einer der Funktion zur Definitionszeit (Achtung: Hier nehmen wir an, daß die Funktion konstruiert wurde, denn sonst muß es einen solchen Zeitpunkt nicht geben!) bekannten Symbol, aber auch dies wird für A und B gleichermaßen stets das Ergebnis falsch liefern, weil A und B frisch sind. Also sind A und B für f nicht unterscheidbar, d.h. f(A) = f(B). Das ist ein Widerspruch, also kann f zu keinem bestimmten Zeitpunkt definiert worden sein. Folglich ist f nicht konstruierbar.
Ob die Nichtkonstruierbarkeit jetzt bedeutet, daß f auch nicht existiert, nun, darüber kann man sich bekanntlich trefflich streiten. :)

OpenJDK und libmawt.so

Ich hatte folgendes in meinem Shellprofil stehen:

# Für Java.  Swing/JFC arbeitet sonst nicht mit AIGLX zusammen.  
 
AWT_TOOLKIT=MToolkit  
 
export AWT_TOOLKIT  
 

Mit Suns Java 6 war dieser Workaround für AIGLX-Kompatibilitätsprobleme noch okay. OpenJDK hingegen klagt über die Absenz des Motif-basierten Backends:

Exception in thread "main" java.lang.UnsatisfiedLinkError: Can't load library: /usr/lib/jvm/java-6-openjdk/jre/lib/i386/motif21/libmawt.so  
 

Es empfiehlt sich also, die AWT_TOOLKIT-Variable zu leeren, wenn man auf OpenJDK umsteigen möchte. Ob die Notwendigkeit auch nicht mehr gegeben ist, kann ich nicht sagen. AIGLX habe ich derzeit nicht aktiviert.

Adé, Bill

Bill Gates hat Microsoft verlassen. Ich frage mich, ob Microsoft ohne ihn überhaupt noch eine Chance gegen die neuen Mächte Apple und Google hat. Microsofts beste Zeit könnte vorüber sein.

Vielleicht war Bill Gates so etwas wie der Steve Jobs von Microsoft. Ein Visionär, ein charismatischer Diktator, der der Firma den Weg wies, und der sich letztlich zurückzog, als sie groß und bürokratisch wurde und sich nicht mehr alles um ihn drehte. Auch, wenn die Kreationen von Microsoft im Vergleich zu denen von beispielsweise Apple regelrecht kinderhaft aussehen und die Marktpolitik der Firma oft alles andere als zimperlich war, so war Gates vielleicht noch das letzte Überbleibsel des alten Garagenunternehmens, dem man einen gewissen Charakter nicht ganz absprechen konnte.

Nachdem Vista auf dem Markt ein solcher Mißerfolg wurde, soll das nächste Windows ja alles wieder ins Lot bringen. Ich glaube kaum, daß das gelingen wird. Microsoft ist defensiv und feige geworden auf seine alten Tage, wie das wohl mit allem geschieht, was durch eine schwerfällige Bürokratie regiert wird. Sollte nicht schon Vista revolutionär werden? WinFS, eine Avalon-basierte Oberfläche, ein vollkommen überdachtes Sicherheitskonzept -- letztendlich wurden alle Versprechen nur halbherzig eingelöst, und man blieb stattdessen bei Bewährtem; außer, daß das Bewährte von der Kundschaft nicht als solches wahrgenommen wird, sondern als das barocke, undurchsichtige und vertrauensunwürdige System, das es ist.

Die letzte Hoffnung für Microsoft wäre vielleicht noch gewesen, daß Bill Gates alles wieder an sich reißt und mit harter Hand den nächsten großen Coup durchzieht, mit dem keiner gerechnet hätte (wie wäre es hiermit: Verabschiedung aus dem Betriebssystemgeschäft und Konzentration auf die Office-Suite und die Entwicklerwerkzeuge -- vollständig plattformübergreifend, versteht sich, um sowohl Google als auch Apple etwas entgegensetzen zu können). Zumal diese Möglichkeit nun ausscheidet, könnte es gut sein, daß Microsofts Tage in marktbeherrschender Stellung endgültig gezählt sind.

Nun, wie dem auch sei. Ich denke, Herr Gates hat es sich schon lange verdient, sich zurückzuziehen. Möge er mit der Entscheidung glücklich werden.

Toilet Lisp: der Code

Wie es scheint, habe ich es in meinem früheren Eintrag von heute versäumt, anzugeben, wie man an den Quelltext von Toilet Lisp kommt. Dies sei hiermit nachgeholt:

$ git clone http://matthias.benkard.de/code/toilet.git 

Viel Spaß! :)

Toilet Lisp

Ich habe mal wieder ein neues Hobbyprojekt.

Nachdem Objective-CL sich ja nunmehr sehen lassen und prinzipiell produktiv verwendet werden kann, habe ich mich an den nächsten Schritt gewagt: ein Lispcompiler für Étoilé bzw. GNUstep und Mac OS X, der vollständig auf der Objective-C-Runtime aufbaut und sich mit ihr integriert. Derzeitiger Projektname: Toilet Lisp.

Nun, um ehrlich zu sein, habe ich keine Ahnung von Übersetzerbau (welcher sicherlich nicht einfach ist), weshalb Toilet Lisp in der ersten Phase nur einen Interpreter enthalten wird und keinen Compiler. Ein Zwischenschritt könnte ein Compiler nach Objective-C-Code sein, der via clang oder GCC weiterkompiliert wird, und das Endziel ist ein Compiler, der LLVM-Code erzeugt.

So oder so werde ich für Toilet Lisp wahrscheinlich länger brauchen als für Objective-CL, bis ich etwas in der Praxis Nützliches produziert habe. Ob überhaupt etwas daraus wird, steht in den Sternen. Aber es ist sicher ganz lehrreich. Auch, wenn sonst niemand etwas davon haben sollte, versuche ich es deshalb trotzdem.

Vielleicht sollte ich noch eine Frage beantworten, die sich unmittelbar aufdrängt: Warum schon wieder ein neuer Lispcompiler, wenn es schon so viele gibt? Vor allem: Gibt es nicht schon Lisps auf der Objective-C-Runtime?

Die gibt es in der Tat. Sie sind aber allesamt semantische Neuerfindungen. Ich halte mich hingegen soweit wie nur irgend möglich an ANSI Common Lisp. Die Kombination einer vollständig mit Objective-C integrierten Umgebung einerseits und traditionellem Lisp andererseits existiert in dieser Form noch nicht. (Pragmatisch betrachtet, kommt Clozure CL mit der gut integrierten Objective-C-Bridge zwar an die Idee einigermaßen nahe heran, aber leider ist es sehr unportabel. Eines von Toilet Lisps Zielen ist es, unter allen Betriebssystemen mit einer Objective-C-Runtime zu laufen und kompakte, klickbare Binaries zu erzeugen.)

Die großen Ziele zusammengefaßt:

  1. Portabilität. Zur Not muß man je nach Plattform eben ohne Compiler auskommen, aber laufen soll das System überall.

  2. Codelesbarkeit. Im Zweifel auch zu Lasten der Performance.

  3. Integration mit Objective-C und Ausnutzung der Runtimefunktionen statt Neuerfindung (z.B. die Implementierung von CATCH und THROW über das bestehende Exception-System).

  4. Kompatibilität mit bestehendem ANSI-CL-Code, insbesondere Bibliotheken.

  5. Unterstützung sowohl eines lispigen, interaktiven als auch eines unixfreundlichen uninteraktiven Entwicklungsmodus, d.h. z.B. Erzeugung von Bibliotheken und Programmen mit Dingen wie make statt aus dem laufenden Lispsystem heraus. Insbesondere: keine Kernbilder (solche wären auf der Objective-C-Runtime auch gar nicht portabel zu realisieren).

Die Avantgarde schlägt zurück

Bernhard Frauendienst hat in einem Beitrag zu unserem Antifaschismus-Diskussionsthread im Informatikerforum der LMU München auf einen Artikel von Luzi-M hingewiesen, der sich auf ebendiesen unseren Thread bezieht und uns folgenden Vorwurf macht:

»Im Wesentlichen geht es den Herrn um die Verabsolutierung der Mitte und des `Unpolitischen', sowie die Gleichsetzung von Links und Rechts, die Akzeptanz der extremen Rechten und die Affirmation einiger ihrer Inhalte.«

Schon lustig. Im Gegensatz zu dem, was einige Möchtegernlinke gerne als »Diskussion« bezeichnen, war die unsrige tatsächlich von politischer Natur und basierte gerade nicht auf einem Grundsatzkonsens mit nur symbolischen oder oberflächlichen Meinungsverschiedenheiten, sondern die Argumente gingen recht heiß (wenngleich zivilisiert) zwischen mehreren ganz grundverschiedenen politischen Lagern hin und her.

Luzi-M versäumt es leider, zu erwähnen, daß gerade ich -- als Linkslibertärer! -- den Antrag aufs schärfste kritisiert habe. Auf meine (im Forum verlinkte) Stellungnahme gegen die Einrichtung eines unabhängigen Antifaschismusreferats geht sie leider nicht ein. Das mag ja nun daran liegen, daß ihr Artikel älter ist, aber gerade das ist bedenklich: Mitten in der Hitze einer Diskussion, die eben erst begonnen hat, überhaupt an Identität zu gewinnen; die noch gar nicht weiß, wo sie hinläuft; in der die Beteiligten ihren Standpunkt gerade erst entwickeln; in dieser Situation maßt sich die Autorin des Artikels an, zu urteilen, daß am Ende ja doch nur bürgerliche Selbstbestätigung dabei herauskommen wird und sowieso die ganze Diskussion reaktionär ist. Sie verfolgt die Sache nach ihrem Urteil offenbar auch nicht weiter, und auch eine konstruktive Beteiligung an der jungen Diskussion scheint ihr nicht in den Sinn zu kommen.

Nein, sich zu beteiligen oder gar differenziert zu berichten, dafür müßte man erst einmal Argumente finden und sich die Mühe machen, diese zu formulieren, ja sogar die Gefahr eingehen, durch eine unglückliche Formulierung mißverstanden zu werden, wie es in der Politik unvermeidlicherweise hin und wieder passiert. Stattdessen zieht man lieber als Außenstehender über Diskussionen und ebensolche unter Umständen unglücklichen Formulierungen -- am allerbesten in Form von aus dem Kontext gerissenen Zitaten, versteht sich -- her und kann sein Gewissen darüber beruhigen, daß routinemäßig durchgeführte Protestmärsche gegen G8-Gipfel und eisern praktizierte diszplinierende Sprachrituale bedauerlicherweise immer noch keine soziale Revolution hervorrufen konnten.

(Übrigens: Es ist bezeichnend, wie Luzi-M im Negativen immer von »Studenten« und »den Herrn« spricht, obwohl aus den Pseudonymen der Diskutanten teilweise das Geschlecht gar nicht ersichtlich ist -- während sie zugleich eine der Äußerungen zugunsten des generischen Maskulinums aus dem Thread in ihre Liste von Negativzitaten (?) aufnimmt. Aber ach! wie dumm von mir. Das Böse ist immer weißhäutig, gehört der Ober- oder Mittelschicht an und ist ein Mann. Das weiß man doch.)

Étoilé nähert sich Smalltalk an

Quentin Mathé vom Étoilé-Projekt berichtet von den neuesten Entwicklungen in Richtung eines vollständigen Benutzeroberlächenframeworks.

Die Vortragsfolien für den Swansea-Hackathon lassen einige wichtige Annäherungen an Smalltalk erahnen. Mein Lieblingspunkt? Live development.

Systemupdates mit NetBSDs pkgsrc-System

Zum Updaten aller von pkgsrc verwalteten Pakete verwende ich normalerweise pkg_rolling-replace.

Doch egal, ob man jedes Paket einzeln via bmake replace aktualisiert oder pkg_rolling-replace -u aufruft, um alle Pakete zugleich auf den neuesten Stand zu bringen -- zwei Pakete lassen sich auf diese Weise grundsätzlich nicht behandeln: bmake und bootstrap-mk-files. Um das Problem zu umgehen, gibt es einige mehr oder weniger zweifelhafte Methoden. In der pkgsrc-Anwendermailingliste erklärt Tobias Nygren, wie man es richtig macht:

cd pkgtools/bootstrap-mk-files  
 
bmake USE_DESTDIR=full package  
 
pkg_add -uu /usr/pkgsrc/packages/All/bootstrap-mk-files...tgz  
 
cd ../../devel/bmake  
 
bmake USE_DESTDIR=full package  
 
pkg_add -uu /usr/pkgsrc/packages/All/bmake...tgz  
 

Eine Antwort von Jörg Sonnenberger auf die E-Mail weist darauf an, daß hierbei Zustandsinformationen verloren gehen können -- ich weiß allerdings nicht, welche. Meiner Paketdatenbank scheint die Prozedur jedenfalls keinen Schaden zugefügt zu haben.

Warum ich gegen den Antifaschismus-Vorschlag gestimmt habe

Im Informatikerforum der LMU München tobt momentan eine Schlacht zwischen mehreren (definitiv mehr als zwei) verschiedenen politischen Lagern über den Vorschlag, Folgendes in das Grundsatzprogramm der neu formierten Studentenvertretung aufzunehmen:

  • Der Konvent möge beschließen, dass sich die Studierendenvertretung als Organ ausdrücklich gegen Faschismus, Rassismus und Sexismus wendet, über solche Tendenzen informiert und gegen sie vorgeht.
  • Der Konvent möge beschließen, dass das Antifaschismusreferat dem Konvent zuarbeitet und im Rahmen seiner Möglichkeit Veranstaltungen (Informationsveranstaltungen, Filmabende etc.) organisiert, dies diene der politischen Bildung und der Schaffung eines Diskussionsrahmens.
  • Der Konvent möge beschließen, dass zum Zwecke der antifaschistischen Arbeit Veranstaltungen außeruniversitärer Gruppen unterstützt werden können und die Zusammenarbeit mit ihnen möglich sei.

Es dürfte wohlbekannt sein, daß ich mich eher zur politischen Linken bekenne als zur Rechten oder der Mitte. Der Antrag klingt auch eher nach dem Werk eines Linken. Trotzdem habe ich gegen den Antrag gestimmt.

Aus meiner Sicht ist der Antrag inhaltsleer: Es wird ein vages Prinzip des Antifaschismus gefordert, das nirgends definiert oder erläutert wird, und dem keine konkreten Forderungen zugrundezuliegen scheinen. Mich erinnert das an die sogenannte Globalisierungskritik, in der sich heutzutage als eine Art Modeerscheinung viele möchtegernlinken Gutmenschen zusammenfinden, um sich das Gefühl geben zu können, kritisch zu denken und gegen das Establishment zu sein, die aber durch keine gemeinsamen politischen Forderungen vereint sind. In der Globalisierungskritik findet sich alles Mögliche wieder: Rechte, Linke und Mittlere, Autoritäre und Libertäre, Grüne, Schwarze, Rote und Braune. Das Fehlen gemeinsamer Forderungen macht die Globalisierungskritik zu nicht viel mehr als einem esoterischen Kreis, der über den ach-so-schlimmen Zustand der Welt philosophiert und sich wahnsinnig gut dabei vorkommt. Eine Bewegung wäre etwas anderes.

Nicht, daß wir uns falsch verstehen: attac war beispielsweise anfangs eine Organisation mit echten, politischen Forderungen. Tatsächlich weist ja der Name darauf hin, daß es eine Organisation war, die ganz konkret die Einführung der Tobin-Steuer forderte, was nun eine gute oder eine schlechte Idee gewesen sein mochte, aber doch jedenfalls etwas, für oder gegen das man Stellung beziehen konnte. Das Bedauerliche an der Sache ist lediglich, daß aus der ursprünglich politischen Bewegung um attac herum ein esoterischer Modeklub wurde, »gegen« den man nicht sein kann, weil das darauf hinausliefe, grundsätzlich für Ausbeutung und gegen Menschlichkeit zu sein, denn mehr fordert die ehemalige Bewegung ja gar nicht mehr.

Ein ähnliches Problem sehe ich, um auf den Studentenvertetungsantrag zurückzukommen, beim Punkt des Sexismus: Was genau würde es bedeuten, sich »gegen ... Sexismus« zu wenden? Sicherlich wende ich mich entschieden gegen Sexismus. Ich wende mich aber insbesondere auch z.B. gegen Frauenquoten, die ich, grundsätzlich und von einzelnen Ausnahmen abgesehen, ihrerseits als sexistisch betrachte. So, wie die Dinge stehen, müßte ich leider annehmen, daß eine Zustimmung zu diesem Antrag auch eine Bekennung zu solcher sogenannter positiver Diskriminierung bedeuten würde, die ja von den heutzutage dominanten Varianten des Feminismus gern angewandt wird, um den Frauen eine privilegierte Stellung in der Gesellschaft einzuräumen -- eine Entwicklung, die ich genauso entschieden ablehne wie andere Formen der Ungleichberechtigung.

Das ist schlußendlich nämlich das Problem: Wir geben mit dem Durchwinken eines solchen Antrags den zuständigen Beauftragten (d.h. in diesem Falle den Referaten, die für solche Fragen eingerichtet wurden bzw. werden) freie Hand in der Interpretation des Grundsatzprogramms, die sie einsetzen können, um unangenehmen Diskussionen von vornherein aus dem Wege zu gehen und ihre persönliche Meinung über die Köpfe derjenigen Studenten hinweg, die sie repräsentieren sollen, durchzusetzen.

Wären in dem Antrag konkrete Forderungen genannt, so wüßte ich, ob ich ihm zustimmen soll oder nicht. Solange Vorschläge aber dermaßen vage formuliert sind, kann ich sie, so leid es mir um die noblen Ziele des Antifaschismus und der Gleichberechtigung tut, aus den oben genannten Gründen nur kategorisch ablehnen.

Die USA, erklärt

USA Erklärt. Eine sehr interessante Ressource. Die Einträge über free speech lassen mich manche Aspekte der USA bewundern.

Smalltalk als Standardsprache für Étoilé

David Chisnall werkelt am C/Objective-C-Compilerfrontend clang für LLVM herum und stellt in Aussicht, daß in Zukunft die bevorzugte Sprache für Étoilé-Entwicklung Smalltalk sein könnte.

Das wäre cool.

Paketmanagement unter Mac OS X

Um freie Software unter Mac OS X auf Unix-Manier zu installieren und zu verwalten, gibt es eine Reihe von Möglichkeiten. Ich bin über alle davon frustriert.

MacPorts

Die Paketkollektion von MacPorts wäre ja noch halbwegs akzeptabel. Leider ist MacPorts nicht in der Lage, bei Abhängigkeiten zwischen benötige libfoo und benötige libfoo zwischen den Versionen 1.2.0 und 1.3.99 zu unterscheiden, was die Möglichkeit, mehrere Versionen nebeneinander zu installieren, völlig sinnlos macht. Sie stört sogar, weil sie einen dazu zwingt, bei jedem Upgrade sowohl -f (force) als auch -u (uninstall inactive versions) anzugeben, da sonst die alten Versionen nicht gelöscht werden können, wenn von den zu aktualisierenden Paketen irgendwelche anderen Pakete abhängig sind. Was aber dafür die Fehlerbehandlung bei Abhängigkeitsproblemen deaktiviert. Was bescheuert ist.

Und wenn man dann ein einziges Mal vergessen hat, bei einem Upgrade -u anzugeben, hat man eine Menge alter Versionen installiert, die Platz verschwenden und zu nichts zu gebrauchen sind. Dafür wird man sie dann aber auch nicht so einfach wieder los, außer man geht manuell durch die Liste aller installierten Pakete, schreibt sich die Doppelinstallationen auf und löscht sie nachher (natürlich mit -f, denn man kann ja keine Paketversionen deinstallieren, die noch gebraucht werden könnten -- auch wenn noch zwanzig weitere Versionen vom selben Paket installiert sind).

Es ist ein Kompromiß! It may be slow, but it's hard to use!

Fink

Fink besteht aus dpkg und einer Sammlung von veralteten Softwarepaketen. Es wäre ja toll, wenn die Software wenigstens als Binärpakete zur Verfügung stünde, aber nicht einmal das ist bei den meisten Paketen der Fall. Man muß also doch wieder alles selbst kompilieren, und dies schlägt ziemlich oft fehl, weil die Quellpakete irgendwelche Fehler haben.

Wundervoll.

Gentoo/Alt

Prefixed Portage ist eigentlich ja ein sehr angenehmes System. Im Gegensatz zu Fink und MacPorts hat es einen Abhängigkeitenauflösungsmechanismus, der nicht völlig hirnrissig ist oder in der Hälfte aller Fälle die Hände über den Kopf wirft und um Hilfe schreit. Dafür kann man kaum ein Paket damit installieren, weil ungefähr 99,7% aller Ports in Portage nicht auf Prefixed Portage ausgelegt sind und bei der Installation die Hände über den Kopf werfen und um Hilfe schreien.

pkgsrc

NetBSDs pkgsrc-System ist das einzige der genannten Systeme, mit dem man halbwegs vernünftig arbeiten kann. Es hat kein Klicki-Bunti-Interface wie MacPorts und Fink, aber wenigstens stürzt es nicht alle zehn Minuten ab oder wirft Teile von Paketen quer durchs Dateisystem, von wo man sie nicht mehr wegbekommt. Außerdem sind die Ports in der WIP-Variante des Portbaumes durchaus akzeptabel aktuell. Dafür weiß man nie, worauf man sich bei WIP-Ports einläßt. Manchmal lassen sie sich nicht kompilieren. Die Quote ist allerdings besser als bei allen anderen Systemen außer MacPorts, und im Gegensatz zu Letzterem funktioniert pkgsrc wenigstens grundsätzlich.

Worauf man bei pkgsrc verzichten muß, sind Mac-OS-spezifische Pakete. Doch wer steigt schon auf Mac OS um, um dann die speziellen Features von Mac OS auszunutzen? Jedenfalls niemand, der auf die Idee kommt, pkgsrc zu verwenden, richtig?

CamlP4 und CamlP5

CamlP5 ist eine Weiterentwicklung von CamlP4 3.09 oder jedenfalls einer damit kompatiblen Version von CamlP4.

CamlP4 selbst wird parallel weiterentwickelt, und die jüngste Version 3.10 macht anscheinend einige inkompatible Veränderungen, die dem ursprünglichen Designer von CamlP4 nicht so recht gefallen wollen, weshalb er sich dazu entschloß, seine eigene, traditionellere Version CamlP5 zu nennen.

So verstehe ich die Situation jedenfalls momentan. Vielleicht ist sie auch ganz anders.

Etwas verwirrend ist sie auf jeden Fall.

Lesbare Syntax für OCaml

minimulk:~ mulk$ ocaml -I +camlp5 camlp5r.cma pa_lisp.cmo  
        Objective Caml version 3.10.2  
 
        Camlp5 Parsing version 5.08  
 
# +  
- : int -> int -> int = <fun>  
# *  
- : int -> int -> int = <fun>  
# **  
- : float -> float -> float = <fun>  
# (* 2 3)  
- : int = 6  
# (* 2 (+ 3 4))  
- : int = 14  
# (list 1 2 3)  
- : list int = [1; 2; 3]  
# "abc"  
- : string = "abc"  
# (let* ((a 10)  
         (b (* a 3)))  
    (+ b (* 3 4)))  
- : int = 42  
# (lambda (x y) (+ x (* 3 y)))  
- : int -> int -> int = <fun> 

Beschrieben im CamlP5-Handbuch. Es gibt auch eine revidierte Syntax, die näher am Caml-Original ist, aber wenigstens ein paar seiner größeren Probleme behebt.

Ad-hoc-Polymorphie: Gut? Schlecht? Keines von beiden?

Bislang fand ich es außerordentlich befremdlich, daß Caml keine Ad-hoc-Polymorphie unterstützt. Für jeden Zahlentypen einen anderen Operator für Addition verwenden zu müssen, ist beispielsweise eine Pein.

Doch kann das Fehlen von Overloading gar ein Vorteil sein? Abgesehen von der automatischen Entscheidbarkeit des Typensystems, welche von Overloading behindert wird, gibt es vielleicht etwas, das Overloading gefährlich werden läßt.

Vergleichen wir doch einmal die Funktionen in CL und Haskell zum Testen, ob ein Element in einer Liste enthalten ist. Die Signatur von CLs member lautet:

(member item list &key (test #'eql) (test-not #'eql))

Das heißt, man kann den Gleichheitstest, der normalerweise durch eql gegeben ist, explizit für jede Verwendung der Funktion angeben. Wie sieht die Funktion in Haskell aus?

elem :: forall a. (Eq a) => a -> [a] -> Bool

Hier wird die Gleichheitsrelation durch die Eq-Typklasse bestimmt. Offenbar ist elem weniger mächtig als member!

Jeder Haskellprogrammierer kennt freilich die Funktion any als Alternative, und anstelle von elem x list kann man sicherlich stets any (= x) list schreiben, wobei die Äquivalenzrelation explizit angegeben wird. Dennoch: Eigentlich ist es doch eine Elementsuche, die man macht, nicht eine Suche über ein Prädikat. Verwendung und Intention liegen hier weiter auseinander, als wenn elem eine Möglichkeit böte, die gewünschte Äquivalenzrelation in Form eines optionalen Schlüssels anzugeben. Man beachte, daß in diesem Falle das Element, nach dem man suchte, gar nicht unbedingt Eq implementieren müßte, weshalb die Signatur plötzlich nicht mehr stimmen würde. Sie würde dann in etwa so aussehen (mit einer von mir aus dem Stegreif erfundenen Notation):

elem :: forall a. (test: a -> a = (=)) -> a -> [a] -> Bool

Doch was ist das? Die Typklasse ist verschwunden.

Wenn wir optionale Argumente einführen, werden Typklassen dann überflüssig? (Vielleicht nicht: Man beachte, daß (=) nach wie vor vom Typen Eq a => a -> a wäre, d.h. überladbar; es wäre lediglich weniger interessant oder wichtig, es zu überladen.) Behindern sie gar unsere Motivation, Funktionen mächtiger zu machen, indem wir sie gebrauch von Typklassen machen lassen, anstatt sie über die relevanten Operationen zu parametrisieren?

Wäre es möglich, Typklassen eine Art pseudodynamische Bindung (womit ich etwas zu impliziten Argumenten Analoges meine) zu geben, so daß man so etwas schreiben könnte wie:

elem x list where instance Eq Integer { a == b  =  eqv a b }

Es ist vielleicht kein Zufall, daß Haskell keine optionalen Schlüsselargumente kennt -- und Caml schon.

Leseprobe: Der gebrauchte Mann

Auf ihrer Website bietet die Autorin Karin Jäckel eine Leseprobe zu ihrem Buch »Der gebrauchte Mann: Abgeliebt und abgezockt -- Väter nach der Trennung« an. Eine der dort erzählten Geschichten (Suchwort: Svenja) trieb mir beim Lesen Tränen in die Augen.

Qualität in der Wikipedia, continued

Die Wikipedia schreibt: » Common Lisp ist keine Programmiersprache, sondern ein Standard der Programmiersprache LISP und bezeichnet oftmals eine Implementierung von LISP, die dem Standard Common LISP entspricht. «

Kommt uns das irgendwie bekannt vor?

All meine Beiträge, inklusive ganzer Abschnitte über die besonderen Merkmale von Common Lisp gegenüber anderen Sprachen, wurden vor ungefähr einer Woche von irgendwem (wohl von diesem Thomas Stauß, der auch auf der Diskussionsseite auftaucht) vollständig gelöscht. Warum? Oh richtig, ich vergaß. Common Lisp ist ja keine Sprache, sondern nur ein Standard, und hat daher auch keine Merkmale gegenüber anderen Sprachen, schon gar nicht gegenüber anderen Lisps. Klar.

Viele weitere Beiträge von anderen Menschen hat es ebenfalls getroffen, in völligem Disrespekt ihrer Mühen. Immerhin: Die von mir selbst geschriebenen Abschnitte kann ich für einen persönlichen Überblick über Common Lisp wiederverwenden; viele der weniger isolierten Veränderungen anderer, weit wichtigerer Mitwirkender werden hingegen wahrscheinlich einfach in der Versenkung verschwinden.

Mein Tip: Am besten größere Beiträge nur noch in der lateinischen Vicipaedia verfassen. Dort gibt es wenigstens nicht so viele Leute, die einem dagegeneditieren können.

Pico Lisp, Fenster in eine andere Welt?

Pico Lisp ist ein Lispdialekt ohne lexikalische Variablen, der grundsätzlich nicht kompiliert, sondern interpretiert wird, und der sich durch seine Minimalität auszeichnet.

Eigentlich nicht weiter interessant, möchte man meinen, und sicherlich für die meisten Einsatzzwecke praxisuntauglich. Überhaupt hört sich die Idee verdächtig ähnlich zu der von newLISP an, und das kann ja wohl keine gute Sache sein.

Wenn man sich aber die Dokumentation durchliest, erhält man den Eindruck, daß sich da jemand ziemlich genaue Gedanken darüber gemacht hat, warum alles in Pico Lisp so zu sein hat, wie es ist. Es ist von dynamischen Closures die Rede und von erzwungener Starrheit durch den Einsatz von Compilern anstelle von leichtgewichtigen, effizienten Interpretern. Als Alternative zu lexikalischen Variablen wird ein neuartiger Umgang mit Symbolen propagiert. Es wird vorgeschlagen, daß die Auslagerung von effizienzkritischem Code in C-Module womöglich ein recht geringer Preis für die Dynamik und Einfachheit des Systems ist.

In meinem Kopf entwickelt sich gerade ein Bild von einer kahlen Lichtung, die von Steinwänden umgeben ist. Hinter uns liegt ein Pfad, auf dem verschiedene Metallklumpen, Steinbrocken und andererseits auch Schleimpfützen herumliegen, mit Beschriftungen wie »MACLISP«, »InterLISP«, »BBN LISP«, »T« und »NIL«. Neben uns liegt ein wohlgeformter, harter, rundlich flacher Stein, der klein genug ist, um ihn in die Hand zu nehmen und in die Tasche zu stecken. Er ist mit der Aufschrift »Scheme« versehen.

Vor uns ragt ein gewaltiger Lehmklumpen in die Höhe. Man kann Lehm vom Boden aufnehmen, formen und auf dem Lehmklumpen anbringen, und dieser assimiliert den hinzugefügten Lehm, so daß er nicht vom Rest des riesigen Klumpens zu unterscheiden ist. Wir stehen vor dem mächtigen Common Lisp, das Verteidigungsanlagen zerschmettern kann, wenn es einmal anfängt, loszurollen, und das ohne zu murren jedes Feature in sich aufnimmt, das ihm der Anwender auf den Leib klebt.

Doch was ist das? Unter dem Klumpen kriecht ein kleiner Käfer hervor. Der Käfer ist häßlich und hinkt, und er scheint harte und weiche Stellen an völlig unpassenden Stellen zu haben, aber ihm folgt ein weiterer Käfer, der noch etwas kleiner ist, schöner aussieht und einen biegsamen Körper zu besitzen scheint. Sind diese Käfer etwa ein unscheinbares Indiz dafür, daß hinter dem Lehmklumpen, der uns aufgrund seiner Größe die Sicht versperrt, eine Welt existiert, die wir noch nicht kennen? Oder kommen die beiden Käfer lediglich aus dem Weg hinter uns und nur zum Schein von jenseits des Klumpens?

Womöglich liegt jenseits des Klumpens ein noch gewaltigerer Klumpen mit noch größerer Macht und einer anderen Beschaffenheit. Vielleicht eröffnet sich auch der Blick auf eine grüne Wiese mit lauter kleinen Käfern und vielleicht Schmetterlingen und anderen Lebewesen. Oder aber hinter dem Klumpen ist nur die Steinwand.

Wir wissen es nicht. Der Klumpen liegt im Weg, und er rührt sich nicht.

Lisp-Einführung

Ich habe einen Versuch gestartet, ein bißchen etwas für Leute zusammenzuschreiben*, die sich für Lisp interessieren, aber nicht wissen, wo sie anfangen sollen, sich zu informieren.

Vielleicht ist es ja ein kläglicher Versuch, aber wer weiß. Ich lasse es einfach darauf ankommen und veröffentliche meinen aktuellen Entwurf.

Daher, lo and behold: mein persönlicher Wegweiser in die fremde Welt von Lisp.

Verbesserungsvorschläge sind willkommen, häufige Umstrukturierungen, Formulierungsanpassungen und Ergänzungen in den kommenden Wochen nicht unwahrscheinlich.

*Eigentlich war es kein bewußter Versuch, sondern mehr ein Nebenprodukt meiner kürzlichen Begeisterung für Emacs Muse, aber jetzt pflege ich es als ein eigenes kleines Projektchen.

Objective-CL 0.2.0

Ich habe Objective-CL 0.2.0 veröffentlicht. Die Version bietet einige Neuerungen und sicher auch die ein oder andere Überraschung in Form eines Abstürzes oder einer Inkompatibilität. Vor allem aber können jetzt Klassen und Methoden von Lisp aus definiert werden, womit wir der Praxistauglichkeit und damit einem 1.0.0-Release einen großen Schritt näher gekommen sind.

Die Dokumentation ist wie üblich auf dem neuesten Stand und vermutlich neben der oben verlinkten Releaseankündigung sowie der Projektseite die beste Informationsquelle.

SpamAssassin-Migration zwischen Rechnern

Ich habe heute versucht, meine bayessche SpamAssassin-Datenbank von einem Computer auf einen anderen Computer zu übertragen. Leider funktionierte ein einfaches scp nicht, denn:

Dec 30 18:54:20 Minimulk spamd[7158]: bayes: cannot open bayes databases /Users/mulk/.spamassassin/bayes_* R/W: tie failed: Inappropriate file type or format\n  
 

Und:

Dec 30 18:54:26 Minimulk spamd[7158]: auto-whitelist: open of auto-whitelist file failed: auto-whitelist: cannot open auto_whitelist_path /Users/mulk/.spamassassin/auto-whitelist: Inappropriate file type or format\n  
 

Das Problem war wohl, daß die Varianten von SpamAssassin in Debian und MacPorts unterschiedliche Versionen von Berkeley-DB verwendeten. file gab über meine Dateien folgende Auskunft:

bayes_toks:      Berkeley DB (Hash, version 8, little-endian)  
 

Die Lösung war einfacher als erwartet (mein erster Versuch mit einer einfachen Pipe und --restore - hatte leider nicht funktioniert):

ssh alter-rechner sa-learn --backup > sa-learn.backup  
 
sa-learn --restore sa-learn.backup  
 
rm sa-learn.backup  
 

Das Resultat:

bayes_toks:      Berkeley DB 1.85 (Hash, version 2, native byte-order)  
 

Ein Downgrade wohl, aber was kümmert's mich -- SpamAssassin läuft jedenfalls wieder.

Eine Projektseite für Objective-CL

Ich habe eine kleine Projektseite für Objective-CL angelegt. Es ist erstaunlich, wie wenig Arbeit es ist, ein bißchen was zusammenzuschreiben und einen einfacheren Einstieg für potentielle Benutzer und Mitwirkende zu ermöglichen.

Ob ich in diesen Weihnachtsferien dazu kommen werde, viel an Objective-CL zu arbeiten, ist übrigens eher fraglich. Es sind genügend Mathehausaufgaben zu erledigen.

Formulierungsspießertum

Als ich gestern Kapitel 40 von Horst Stowassers Freiheit pur: die Idee der Anarchie, Geschichte und Zukunft (frei erhältlich als PDF sowie in einer neuen Druckauflage unter dem Namen Anarchie! und auf jeden Fall empfehlenswert) überflog, fielen mir einige Abschnitte auf, die ich früher schon einmal gelesen hatte und einfach nicht besser wiedergeben kann als durch ein langes Zitat:

... Auch in der Anarchoszene gibt es ein ritualisiertes* »das tut man nicht« -- nur wird es anders ausgedrückt ...

Dahinter steckt eine merkwürdige und sehr hartnäckige Gleichsetzung von Form und Inhalt. In den meisten Fällen fügt dabei der übersteigerte Formalismus dem inhaltlichen Anliegen schweren Schaden zu.

Nichts hat wohl dem Anliegen der Arbeiterbewegung jemals mehr geschadet, als der idiotische »Proletkult«, mit dem die kommunistische Ideologie der Welt beweisen wollte, daß der arbeitende Mensch der bessere Mensch sei. Arbeiterbewußtsein, Arbeitersprache, Arbeiterlieder, Arbeiterkultur, Arbeiterwitze und Arbeiterkitsch wurden als Errettung aus bürgerlicher Dekadenz hoch in den blauen Himmel der diversen Arbeiterparadiese gejubelt. Alles war per Definition gut und edel, sofern es nur vom Proleten kam. Selbst als dieser Mythos im Ostblock längst eingeschlafen war, feierte er in der »Neuen Linken« als Szene-Mode fröhliche Auferstehung. Nun waren es westdeutsche Studenten, die dem Kult der »werktätigen Massen« fröhnten, und in deren Kampfblättern man lesen konnte, daß »über zweitausend Menschen und Werktätige« an einer Demonstration teilgenommen hatten.

Heute wird hingegen zwischen Menschen und Frauen unterschieden. Mit der gleichen Akribie wie seinerzeit der »Klassenstandpunkt«, wird derzeit der »Frauenstandpunkt« durchgesetzt. Damals war die »Frauenfrage« ein »Nebenwiderspruch des Klassenkampfes«, heute ist jedes Problem ein Ergebnis des »Geschlechterkrieges«. In den einschlägigen InsiderInnenblättern erfährt die erstaunte LeserInnenschaft etwa, daß die Beteiligung der AntifaschistInnen aus dem Spektrum der HausbesetzerInnen bei den Aktionen der betroffenen Leute aus den autonomen Frauen- und Lesben-Männer/Schwulen-Zusammenhängen zu inhaltlicher Kritik am sexistischen Sprachverhalten einiger Typen geführt hat, die die Forderung nach einer getrennten Frauen/Lesben-Küche lächerlichgemacht haben, weil man/frau die Vermittlung eines spezifisch femistischen Standpunkts in dieser Frage vernachlässigt hat, da niemensch mit solchem Chauviverhalten rechnen konnte.

JedeR, der/die sowas liest und nicht zur entsprechenden Szene gehört, wird sich angesichts solch Orwell'schem Neusprech* die Frage stellen: »Haben die sonst keine Sorgen?« Vorausgesetzt, der Inhalt dieses Satzes wurde überhaupt verstanden.

Das fragen sich auch jene selbstbewußten und durchaus emanzipierten Frauen, die eine solche sprachliche Liturgie* ablehnen und sich dagegen verwahren, daß das Anliegen der Frauen auf diese Weise von einer skurrilen Szene zum formalen Modethema verwurstet wird. Es handelt sich ja nicht, wie gern behauptet, um eine Reinigung der Sprache von männerbestimmter Ideologie, sondern um eine Befrachtung der Sprache mit einem permanenten Bewußtseinsbekenntnis. Sie wird dadurch nicht nur auf schauderhafte Weise un-sprechbar, un-lesbar und un-verständlich, sondern wirkt auf neunundneunzig Prozent aller Menschen so grotesk wie alle Ideologie-Jargons. Die politischen Folgen sind verheerend. So wie ein frommer Katholik nicht »Maria« sagen kann, ohne »die Du bist gebenedeit unter den Weibern« anzuhängen, damit sprachlich dokumentiert bleibt, daß ihm die besondere Rolle der Heiligen Jungfrau stets und ständig bewußt ist, so dokumentiert auch der trendbewußte Linksmensch in jedem Satz seine tiefe Betroffenheit: Er weiß, daß es eine Unterdrückung der Frau und ein Patriarchat gibt und er zeigt, daß er dieses Problem allzeit ernst nimmt und nie vergißt. Der gläubige Mohammedaner muß in seinen Nebensätzen ständig betonen, daß Allah der Einzige und Erhabene Gott ist und Mohammed Sein Prophet. Der gläubige Marxist flicht mit seinen proletarischen Sprachschlenkern in jeden Satz das Bekenntnis zum »Klassenstandpunkt« ein. Dem »Frauenstandpunkt« widerfährt leider kein besseres Schicksal.

Es handelt sich mithin um ein semantisches* Glaubensbekenntnis, das gleichzeitig als szene-typisches Identifizierungssignal funktioniert. Dieser Jargon wird in zwanzig Jahren auf diejenigen, die ihn heute anwenden, nicht weniger peinlich wirken als heutigentags die Proletkult-überfrachtete Agitationssprache des SDS auf die alten Achtundsechziger. Die Auswirkungen solcher Sprachghettos auf das eigentliche Thema sind immer negativ -- gleichgültig, wie gut, berechtigt und wichtig das Anliegen auch sein mag. Je intensiver sprachliche Überzeugungs- und Unterwerfungsrituale gepflegt werden, desto weniger werden sie geglaubt. Der formalistische Neofeminismus als derzeitiges Modethema einiger linker »Scenes« wird dem Anliegen der Frauen einen hohen Preis abverlangen.

Dabei steht er hier nur als ein Beispiel für viele linke Zeitgeisterscheinungen, bei deren kultischer Erhöhung stets Form und Inhalt verwechselt werden. Erinnern wir uns, wer in letzter Zeit neben Proletariern und Frauen nicht schon alles als Hoffnungsträger und Übermensch gehandelt wurde: der Vietkong, der Tatmensch Ché Guevara oder der Guerillero an und für sich ... Buddhistische Mönche, Gurus, Visionäre, Menschen mit dem Dritten Auge und esoterisch Erleuchtete aller Schattierungen bis hin zu Ätherleibern und außerirdischen Heerscharen, die mit ihren UFOs zur Rettung der Erde angetreten sind. In größerer Heimatnähe dann noch den allseitig betroffenen Öko- und Friedensaktivisten, den sanften Naturmenschen, den Bewußtseinskünder. Nicht zu vergessen die sagenhaften Rückzugsgefilde des kleinen Hobbit.

Hinter diesen Dingen stecken meist legitime, zumindest interessante Bereiche. Indem ein jedes dieser Themen aber zu dem Thema schlechthin gemacht wird, verwandelt sich der Inhalt eines Anliegens in starre, äußerliche Form. In der Regel werden alle Heilserwartungen auf den neuen, perfekten Menschen projiziert, der dort angeblich schlummere und nun entdeckt worden sei. Diesem Hoffnungsträger wird mit den neu entstandenen Formalien gehuldigt. Solche Formalien tendieren zu Isolation und stoßen auf Unverständnis. Die ultrasofte Müsliszene wirkt mit ihren Ritualen auf Außenstehende natürlich genauso grotesk wie die knallharten Autonomen oder die antisexistisch festgebissenen »Frauen/Lesben-Zusammenhänge«.

Auf die gleiche Weise entstand aus dem Rebellenfreak Jesus eine katholische Kirche, aus dem Kampf der unterdrückten Arbeiter ein Proletkult, aus dem Kampf der unterdrückten Frauen ein neuer Frauenkult. Dem Menschen weiblichen Geschlechts werden heute sämtliche ersehnten Tugenden aufgebuckelt. Aber auch die heftigsten Rituale werden all die Nicht-Gläubigen beiderlei Geschlechts kaum von der Überzeugung abbringen können, daß eine Frau auch bloß ein Mensch ist.

(aus: Horst Stowasser, Freiheit Pur, im oben genannten PDF auf S. 376ff.)

GNUstep: Menüleistenstile

Vertikale Menüs

Die Vorgabe.

Menü mit NSNextStepInterfaceStyle

mulk@wirselkraut:~% defaults read NSGlobalDomain NSInterfaceStyleDefault  
 
NSGlobalDomain NSInterfaceStyleDefault NSNextStepInterfaceStyle  
 
 
 

Horizontale Menüs

Ohne WildMenus.

Menü mit NSMacintoshInterfaceStyle

mulk@wirselkraut:~% defaults read NSGlobalDomain NSInterfaceStyleDefault  
 
NSGlobalDomain NSInterfaceStyleDefault NSMacintoshInterfaceStyle  
 
 
 

WildMenus

Menü mit WildMenus

mulk@wirselkraut:~% defaults read NSGlobalDomain GSAppKitUserBundles  
 
NSGlobalDomain GSAppKitUserBundles '(  
 
    "/gentoo/usr/GNUstep/Local/Library/Bundles/WildMenus.themeEngine",  
 
    "/gentoo/usr/GNUstep/System/Library/Bundles/Camaelon.themeEngine"  
 
)  
 
 
 

Étoilé-Menüs

Siehe zum Beispiel http://www.etoile-project.org/etoile/mediawiki/images/4/4d/Etoile-shot2.png.

Menü im Étoilé-Stil

mulk@wirselkraut:~% defaults read NSGlobalDomain GSAppKitUserBundles  
 
NSGlobalDomain GSAppKitUserBundles '(  
 
    "/gentoo/usr/GNUstep/System/Library/Bundles/EtoileMenus.bundle",  
 
    "/gentoo/usr/GNUstep/System/Library/Bundles/Camaelon.themeEngine"  
 
) 

Gezwungen, Subversion zu verwenden? git-svn bringt den Spaß zurück.

Im Rahmen des Systempraktikums an der Uni bin ich gezwungen, Subversion zu verwenden. Als Fan von Darcs im speziellen und verteilten SCMs im allgemeinen stört mich das etwas. Ich empfinde Branching und Merging in Subversion als eine furchtbare Pein -- ganz zu schweigen freilich davon, daß der Server bei jedem Commit verfügbar sein muß, was meine übliche Praxis, viele kleine Commits zu erstellen, die sich in einem Satz beschreiben lassen, unterwandert, da ich keine permanente Internetverbindung und der Server keine 100%ige Uptime hat.

Ich sah mich also nach Alternativen zum normalen Subversion-Workflow um. Ich wußte von der Existenz von SVK, doch hatte ich meine Zweifel, daß es bezüglich Merging besser ist als Subversion. Auch wollte ich das interaktive Commitverfahren von Darcs nicht aufgeben. Gelandet bin ich bei git-svn, das eine Brücke zwischen Git und Subversion schlägt. Mein Eindruck: git-svn ist phantastisch. Es funktioniert genau so, wie man es sich wünschen würde.

Blieben noch die interaktiven Commits, die Git nicht von Haus aus unterstützt. git-hunk-commit --darcs simuliert diese hinreichend gut, daß ich mit der jetzigen Zusammenstellung meiner Toolbox recht zufrieden bin.

Wer sind die Alt-Katholiken?

Die alt-katholische Kirche hört sich interessant an. Sie ist, wie es scheint, eine Art aufgeklärte Variante der römisch-katholischen Kirche, von der sie sich einst abspaltete und nicht mehr abhängig ist.

Laut meinen kurzen Recherchen

(Oh, und nicht zu vergessen: Sie ist nicht protestantisch! Tut mir leid, aber schon allein der protestantischen Arbeitsethik wegen befinde ich den Protestantismus für höchst zweifelhaft, so modern die evangelische Kirche teilweise auch scheinen mag.)

Das ist mein persönlicher, laienhafter und uninformierter Eindruck, dem nur minimale Recherchen zugrundeliegen. Ich mag einiges mißverstanden haben. So oder so werde ich wahrscheinlich das Münchner Gotteshaus der Alt-Katholiken einmal besuchen. Das Gebäude kann man sich ja mal ansehen.

Was es wohl sonst noch so an aktiven christlichen Non-Mainstream-Religionsgemeinschaften gibt?

Perl-Einzeiler für Multiline-Regexps

Ich habe ein kleines Shellskript geschrieben, um die Codezeilen meiner Lisp-Quelldateien halbwegs sinnvoll abschätzen zu können. Leer- sowie durch Strichpunkte eingeleitete Kommentarzeilen können recht leicht mithilfe von grep herausgefiltert werden, aber Docstrings sind ein nichttriviales Problem. Man mag es für einfach halten, schlicht alle Strings herauszufiltern; doch weder sed noch grep können hier helfen, weil sie nur zeilenweise arbeiten.

Also versuchte ich mich an einem Perl-Einzeiler. Auch perl -p und perl -l arbeiten lediglich zeilenweise, doch fand ich heraus, daß sich dieses Verhalten durch die Option -0777 ändern läßt. Ich habe nicht die leiseste Ahnung, was diese Option bedeutet, aber sie scheint zu funktionieren.

Die fertige Zeile lautet: egrep -v "^$| *;" dateiname.lisp | perl -pne 's/".*?"//mgs' -0777 | wc -l. Das Skript außen herum kann man sich dazudenken.

Gedenkt unser jemand?

Heute war Weltmännertag. Ich vermute, daß das die meisten Menschen nur marginal interessiert.

Qualität? In der Wikipedia? Auf welchem Planeten leben Sie?

Ich bin mittlerweile gewohnt, daß gute Artikel in der Wikipedia nicht lange Bestand haben, weil sie mit der Zeit durch zusammenhanglose Editierungen bis zur Unkenntlichkeit verunstaltet werden. Dennoch ärgert es mich, daß die Qualität des Artikels über Common Lisp derart abgenommen hat, seit ich ihn das letzte Mal sah.

Vor einiger Zeit ergänzte ich selbst den Artikel noch um einige Abschnitte. Seitdem scheinen da einerseits Leute am Werk gewesen zu sein, die die deutsche Sprache nicht beherrschen und meinen, »Lisp-Listen« durch »Common Lisp-Listen« (was für Lisp-Listen?) ersetzen zu müssen, und andererseits solche, die Common Lisp unbedingt als einen Standardisierungsversuch der »Sprache LISP« bezeichnen wollen und sich nicht darüber aufklären lassen, daß Common Lisp in der Tat eine eigene Sprache ist. Ich wette, daß die Betroffenen noch nie Scheme und Common Lisp ernsthaft verwendet haben, denn sonst würden sie einsehen, daß »LISP« genauso verwandt mit Common Lisp ist wie ALGOL mit Java.

Halbwissen ist oft gefährlicher als gar kein Wissen -- nur, daß es sich in der Wikipedia zu allem Überfluß auch noch fortpflanzt. Das Dumme ist, daß ich nichts dagegen tun kann. Ich kann die ungrammatischen Ausdrücke wegeditieren (und heute habe ich das noch einmal grob nach dem vgrep-Verfahren gemacht), aber sie werden wiederkehren. Ich könnte auch die fachlichen Fehler beheben, aber dann müßte ich mich auf einen Streit mit den anderen Autoren gefaßt machen, die nicht meiner Meinung sind, und darauf habe ich nun wirklich keine Lust.

Ich werde vermutlich irgendwann meine eigene kleine Zusammenfassung der Sprache Common Lisp schreiben, die ich anderen Leuten zeigen und überall verlinken kann, wo die Bezeichnung »Common Lisp« vorkommt. Von der Verlinkung des Wikipedia-Artikels werde ich jedenfalls in Zukunft absehen.

CL-ObjC — freundliche Konkurrenz zu Objective-CL

Vor einer Weile wurde in comp.lang.lisp die Veröffentlichung eines Projekts namens CL-ObjC angekündigt . CL-ObjC ist, wie mein eigenes Objective-CL, eine freie Objective-C-Bridge für Common-Lisp-Systeme. Im Vergleich zu Objective-CL

  • ist sie BSD-lizenziert (ObjCL unterliegt momentan der GPLv3, aber ich denke, daß ich in Zukunft die LLGPL verwenden werde -- oder um es zurückhaltender zu formulieren, »ein möglicher Umstieg auf die LLGPL wird geprüft« :)),

  • kann Objective-C-Klassen und -Methoden erzeugen (ObjCL ist dazu noch nicht in der Lage),

  • ist ausschließlich in Lisp geschrieben (ObjCL teilweise in Objective-C),

  • und hat eine Art Handbuch,

aber dafür

  • hat sie keine Unterstützung für Exceptions (ObjCL kann Objective-C-Exceptions auffangen und in Lisp-CONDITIONs kapseln),

  • kann structs nur über einen üblen, unportablen Hack übergeben und empfangen,

  • hat keine Speicherverwaltung (d.h. es wird bei der Verwendung vermutlich zu Speicherlecks kommen),

  • ist ihre Referenzdokumentation knapper,

  • ihr API größer und expliziter (z.B. gibt es keine designators für solche Dinge wie selectors, welche stattdessen explizit erzeugt werden müssen), und vor allem

  • läuft sie ausschließlich unter Mac/x86 (ObjCL läuft hingegen nicht nur unter Mac OS X, sondern auch einer Menge GNUstep-Plattformen).

Meine Antwort auf eine Anfrage des Projektverwalters von CL-ObjC dokumentiert grob einige Aspekte des inneren Aufbaus von Objective-CL und könnte daher auch für ObjCL-Fans vonnutzen sein, die sich nicht für das »Konkurrenzprodukt« interessieren.

Hoffentlich können die beiden Projekte in Zukunft gut zusammenarbeiten und Code austauschen.

Mit kleinen Schritten in die Utopie — sozial wie ökologisch

Blog Action Day 2007

Freilich ist der einzig glaubwürdige langfristige Schutz gegen die Ausplünderung unserer Welt eine freie Gesellschaft. Leider sind wir von einer solchen deprimierend weit entfernt.

Bis es soweit ist, können wir jedoch wenigstens versuchen, unseren kleinen Beitrag zu leisten, indem wir unsere Energie aus regenerativen Quellen beziehen und, was noch wichtiger ist, unseren Energieverbrauch senken. Vielleicht achten wir beim nächsten Computerkauf ja ausnahmsweise auch einmal auf Leistungsaufnahme und Lärm- und Hitzeentwicklung, und nicht nur auf das Vorhandensein von x MiB Arbeitsspeicher und eines Prozessortakts von y GHz. Die Güte von Komponenten ist nicht eindimensional.

  • Auch, wenn ein Intel GMA 950 nicht an die Geschwindigkeit einer neuen GeForce herankommt -- er reicht für 3D-Desktopspielereien nicht nur locker aus, sondern verbraucht vor allen Dingen signifikant weniger Energie. Das ist indes nicht ihr einziger Vorteil: Freie Betriebssysteme unterstützen sie treibermäßig viel besser, und auch die Treiber für unfreiere Systeme können sich sehen lassen.

  • Intel bietet mit PowerTOP ein Werkzeug an, das Energieineffizienzen auf dem individuellen System anzeigt, auf dem es ausgeführt wird. Jeder Laptopbesitzer sollte es sich einmal angesehen haben.

  • Es muß kein mobiles Gerät sein. Ein Verbrauch von 20 Watt ist auch auf dem Desktop keine Utopie (allerdings sei gleichfalls davor gewarnt, dem vorangehenden Hyperlink ohne gesunde Skepsis zu folgen, denn nicht alle Äpfel sind grün).

  • Daß gute thermische Aussteuerung meist eine Kiste voraussetzt, die hochintegriert und damit nicht gut erweiterbar ist, ist nur ein Scheinnachteil.

  • Man muß daheim keinen Server für Websites und VPNs herumstehen haben. Routerkästchen mit offener Firmware können genauso gut als VPN-Server fungieren und ersetzen zugleich den alten WLAN-Router. Websites kann man bei NearlyFreeSpeech.NET für wahrscheinlich weniger Geld hosten lassen, als die Stromrechnung für einen eigenen Server beträgt. (Ehrlich. Auch Websites, die gerade mal drei Besucher pro Monat haben, profitieren finanziell von dem Hosting dort.)

Denkwürdige Merkmale der Sprache C: #import versus #include

Wo wir schon bei unportablen Erweiterungen von C sind, möchte ich ein nützliches Feature von GCC erwähnen, über das ich im Zuge meiner Objective-C-Expeditionen gestolpert bin: die #import-Direktive.

#import ist eine Alternative zu #include , die darauf achtet, dieselbe Datei nicht mehrmals einzubinden, ohne auf die althergebrachten Tricks mit #define und #ifndef angewiesen zu sein. Objective-C-Programmierer nutzen sie eigenartigerweise ständig. Vielleicht hängt das damit zusammen, daß Objective-C nie ein eigener offizieller Standard war und implementierungsspezifische Erweiterungen daher nicht so verpöhnt sind wie in C.

Von der Verwendung der Direktive wurde von Seiten der FSF lange Zeit abgeraten, weil ihre Semantik nicht ganz klar war und von GCC nicht richtig unterstützt wurde. GCC 3.4 behob dieses Problem jedoch, so daß einer Verwendung heutzutage bis auf die Nonportabilität nichts im Wege steht:

File handling in the preprocessor has been rewritten. GCC no longer gets confused by symlinks and hardlinks, and now has a correct implementation of #import and #pragma once. These two directives have therefore been un-deprecated.

Ob man die Direktive auch in C-Code verwenden sollte, ist freilich eine andere Frage. Es ist sicher nicht das Schlechteste, in der Regel standardkonformen Code zu schreiben. Andererseits braucht man vor implementierungsspezifischen Erweiterungen dann jedenfalls nicht zurückzuschrecken, wenn der Code ohnehin schon aus grundsätzlichen Überlegungen heraus unportabel ist.

Denkwürdige Merkmale der Sprache C: dynamische Speicherreservierung einmal anders

Jedem C-Programmierer sollten die Funktionen malloc(3) und free(3) geläufig sein. Weniger bekannt sind hingegen die zusätzlichen Speicherverwaltungsroutinen, die die GNU-C-Bibliothek anbietet. Ich jedenfalls hatte bis jetzt noch nie von Obstacks gehört, und auch den doch recht nützlichen Aufruf alloca(3) trifft man eher selten an. Portablen C-Code kann man mit diesen Routinen freilich leider nicht schreiben, außer man kann es sich leisten, sich von der GNU-C-Bibliothek abhängig zu machen.

Bekannter ist vermutlich, daß man über den Boehm-Demers-Weiser-Speicherbereiniger auch in C automatische Speicherverwaltung nutzen kann. Doch seien wir ehrlich: alloca(3) und Konsorten haben einen bei weitem höheren Coolnessfaktor.

Denkwürdige Merkmale der Sprache C: getc liefert keinen char zurück

Es hat einen Grund, daß getc(3) den Rückgabetypen int hat und nicht etwa char. Die Konstante EOF läßt sich sonst nämlich mit einem gelesenen Zeichen verwechseln.

EOF ist in der GNU-C-Bibliothek als -1 definiert, weshalb es als char nicht unterscheidbar vom Zeichen mit der Nummer 255 ist. Analoges gilt für wchar_t und wint_t mit WEOF als problematischer Wert.

Kampf dem Spam!

Ich habe für mein Journal Spamschutz durch Akismet implementiert. Laut des Autors von Spam-Karma ist es unwahrscheinlich, daß es sich dabei um eine langfristige Lösung handeln kann, derzeit sei es aber (neben Spam-Karma selbst, versteht sich) die effektivste.

Als ich noch einen WordPress-basierten Blog führte, war ich mit Spam-Karma ausgesprochen zufrieden. Wenn ich irgendwem eine Aussage über die zukünftige Entwicklung des Spamproblems zutraue, dann dem Autor davon.

Objective-CL, eine Objective-C-Brücke für Common Lisp

Für ein Release mit großem Trara reicht es zwar noch nicht, doch möchte ich heute dennoch meine neueste Kreation vorstellen: Objective-CL, eine portable Objective-C-Brücke für Common Lisp. Sie ermöglicht es dem Anwender, Objective-C-Instanzen zu erzeugen und diesen sowie ihren Klassen Nachrichten zu schicken. Eine Möglichkeit, eigene Objective-C-Klassen von Lisp aus zu definieren, ist fest geplant, kann aber noch eine Weile auf sich warten lassen.

Das nicht mehr gerade brandaktuelle Referenzhandbuch, das bookzumarken womöglich noch keine gute Idee ist, weil sich die Adresse jederzeit ändern kann, sollte für die geschwinde Verwendung der aktuellen Version ausreichen.

Die Hauptentwicklungsplattform ist SBCL/x86 unter Debian GNU/Linux. Die Objective-C-Runtime, für die Objective-CL öfter ausgiebiger getestet wird, ist daher naheliegenderweise die GNU-Runtime zusammen mit den GNUstep-Foundation-Klassen. Ich versuche jedoch, die Bridge auch regelmäßig mit der Apple-Runtime unter Mac OS X zu testen, und werde das wohl stets tun, bevor ich meine Änderungen der Öffentlichkeit zugänglich mache. (Gebt mir einfach einen Schubser, wenn ich es mal vergessen sollte.)

Der Code ist zurzeit ausschließlich in einem Darcs-Repository zu finden:

 darcs get http://matthias.benkard.de/code/objective-cl 

Ich möchte darauf hinweisen, daß es sich hierbei um ausgesprochen unvollständige, wahrscheinlich von Fehlern durchsetzte Vorschausoftware handelt. Wie üblich gebe ich keine Garantie für das Funktionieren des Codes, auch nicht dafür, daß er Computern und Daten keinen Schaden zufügt. Patches werden gern entgegengenommen, und wenn mir jemand mit Objective-C-bezogenem Wissen dienen kann, wird er sicherlich ebenfalls in den CREDITS Erwähnung finden werden. (Wer mir verständlich erklären kann, wie Metaklassen in Objective-C funktionieren, was genau ihre Aufgabe ist und wie sie erzeugt und verwendet werden, der hat diesbezüglich momentan bereits gute Karten.)

Wie dem auch sei. Ich wünsche viel Spaß mit meinem Code!

Mulkutils: Version 0.2.0

Ich habe meine kleine Makrosammlung für Common Lisp, die bislang lediglich einige Makros enthält, die das Schreiben anonymer Funktionen vereinfachen sollen, ein wenig überarbeitet. Der Code ist jetzt in kleinere Stücke aufgeteilt, Unittests wurden erzeugt (hauptsächlich aus den Benutzungskommentaren, die von Anfang an da waren), und die Syntax der Makros wurde entscheidend verbessert.

Alter Code ist nicht kompatibel mit der neuen Version , doch erscheint mir dies noch als kein großes Problem, da ich momentan von einer Anwenderbasis von genau einer Person (mir selbst) ausgehe. Kommentare hat jedenfalls noch niemand in das Journal hier geschrieben...

Die Installation funktioniert wie gehabt:

(asdf-install:install "http://matthias.benkard.de/code/mulkutils-0.2.0.tar.gz") 

Es ist allerdings mit Kompilierfehlern der Testcases zu rechnen. Ich bin diesbezüglich noch am Rätseln. Anwender müssen das System mulkutils-tests ja nicht laden: Sie dürften sich eher für das System mulkutils interessieren, das eigentlich problemlos kompilierbar sein sollte.

Die Veränderungen ###

Syntax ####

Die Syntax für die einfache Form der FN -Makros wurde so verändert, daß sie für Common-Lisp-Programmierer einfacher zu lesen ist. Anstelle von (fn OPERATOR ARGUMENTE ...) muß nunmehr (fn #'OPERATOR ARGUMENTE ...) geschrieben werden. Dies betont die Tatsache, daß OPERATOR tatsächlich der Name eines Operators ist. Als praktische Nebenwirkung eröffnet es die Möglichkeit, Funktionsobjekte als Operatoren zu verwenden.

Korrekte lexikalische Suche der Argumentnamen ####

Zuvor war es nicht möglich, die Funktion (efn (efn () _)) ohne Argumente aufzurufen, obwohl der korrekte Rückgabewert eine Funktion ohne Argumente hätte sein sollen. Dies lag daran, daß die Makros der FN-Familie nicht in der Lage waren, innere von äußeren Argumenten oder gar lexikalischen Variablen zu unterscheiden, weshalb das innere Argument » _ « als sowohl der inneren als auch der äußeren FN-Form zugehörig erkannt wurde. Dieser offensichtliche Fehler wurde in der neuen Version durch die Verwendung eines Codewalkers aus Marco Baringers ARNESI-System behoben.

Neues Makro FN* ####

Die Familie der FN -Makros hat Nachwuchs bekommen: Es gibt jetzt ein Makro namens FN* , das genauso funktioniert wie FN , außer daß es eine Funktion erzeugt, die überschüssige Argumente nicht einfach verwirft, sondern sie noch dem angegebenen Operator übergibt. Damit erfüllt sie einen Zweck, der einerseits dem eines CURRY-Makros gleichkommt, andererseits aber analog zu FN funktioniert und dessen Features erbt.

Ich bin mir noch nicht sicher, ob die Makros FN und EFN später einmal ein Symbol für restliche Argumente einführen werden. Wenn, dann wird es vermutlich __ heißen.

Dokumentation ###

Da sowohl die Syntax über den Haufen geworfen und idiomatisiert als auch neue Funktionalität hinzugefügt sowie dokumentiert fehlerhaftes Verhalten behoben wurde, reproduziere ich im folgenden noch einmal die bisher vorhandene Dokumentation.

#| Basic usage  
 | ===========  
 |  
 | (fn #'+ _ 10)  
 | (fn (+ _ 10))  
 | (fn #'+ _0 _1)  
 | (fn #'+ _ _1)  
 | (fn #'+ _ (/ _1 2))  
 | (mapcar (fn (cons _ _)) '(1 2 3))       ;=> ((1 . 1) (2 . 2) (3 . 3))  
 | (funcall (fn (+ _ 10 _3)) 20 30 40 50)  ;=> 80  
 |  
 |  
 | Simple variant FN1  
 | ==================  
 |  
 | (funcall (fn () _) 42)                ;=> 42  
 | (funcall (fn _) 42)                   ;=> error (usually)  
 | (funcall (fn1 _) 42)                  ;=> 42  
 | (funcall (fn +))                      ;=> error (usually)  
 | (funcall (fn1 +))                     ;=> value of +  
 | (funcall (fn #'+))                    ;=> 0  
 | (funcall (fn1 #'+))                   ;=> #<FUNCTION +>  
 |  
 |  
 | Argument-number-safe variant EFN  
 | ================================  
 |  
 | (funcall (fn (fn () _)))              ;=> #<LAMBDA ...>  
 | (funcall (efn (efn () _)))            ;=> #<LAMBDA ...>  ; new!  
 | (funcall (fn1 (fn1 _)))               ;=> #<LAMBDA ...>  
 | (funcall (efn1 (efn1 _)))             ;=> #<LAMBDA ...>  ; new!  
 | (funcall (fn1 _3) 1 2 3 4 5)          ;=> 4  
 | (funcall (efn1 _3) 1 2 3 4 5)         ;=> error  
 |  
 |  
 | Currying variant FN* (cf. LIST vs. LIST*)  
 | =========================================  
 |  
 | (funcall (fn* #'+ 1) 2)               ;=> 3  
 | (funcall (fn* #'+) 1 2)               ;=> 3  
 | (funcall (fn* (/ (* _ 4))) 3 6)       ;=> 2  
 |# 

Optimierungsflags für den Intel Core Duo

Ein Patch in der Mailingliste gcc-patches hat mich darauf aufmerksam gemacht, daß es nichttrivial ist, für eine Intel-Core-basierte Maschine die richtigen GCC-Optimierungsflags zu finden. Wie es scheint, sind -march=prescott und -mtune=pentium-m zu setzen. Neuerdings gibt es jedoch auch die Möglichkeit, einfach -march=native und -mtune=native zu schreiben.

DellfanD für Solaris

Der für viele Dell-Notebooks unter alternativen Betriebssystemen lebensnotwendige Lüftersteuerungsdienst DellfanD lief bisher nur unter GNU/Linux. Nicht mehr! Ich habe ihn nach Solaris (genauer gesagt OpenSolaris) portiert. Sollte der Maintainer sich dazu entschließen, meine Veränderungen nicht offiziell in sein Programm aufzunehmen, werde ich den vollständigen Quellcode meiner Version noch veröffentlichen. Wer darauf nicht warten möchte, kann den denkbar minimalen Patch gegen die offizielle Version 0.7 des Daemons verwenden:

--- dellfand.cc.orig    2006-12-22 10:19:36.000000000 +0100  
+++ dellfand.cc 2007-06-27 21:43:44.000000000 +0200  
@@ -19,7 +19,14 @@  
 */  
 /*************************************************************/  
 
+#if defined (__SVR4) && defined (__sun)  
+#include <sys/types.h>  
+#include <sys/sysi86.h>  
+#include <ia32/sys/psw.h>  
+#else  
 #include <sys/io.h>  
+#endif  
+  
 #include <stdio.h>  
 #include <stdlib.h>  
 #include <fcntl.h>  
@@ -86,16 +93,22 @@  
 }  
 int set_ioperms( void )  
 {  
+#if defined (__SVR4) && defined (__sun)  
+       int err = sysi86(SI86V86, V86SC_IOPL, PS_IOPL);  
+#else  
        int err = ioperm(0x84,1,1);  
-       if ( err != 0 ) {  
+#endif  
+       if ( err < 0 ) {  
                puts( "error: syscall ioperm failed: probably you're not running as root." );  
                return SYSTEM_ERROR;  
        }  
 
+#if !(defined (__SVR4) && defined (__sun))  
        if ( ( err = ioperm(0xb2,1,1) ) != 0 ) {  
                puts( "Error: syscall ioperm failed: odd that." );  
                return SYSTEM_ERROR;  
-       }  
+       }  
+#endif  
 
        return 0;  
 } 

Mulkutils: kleine Werkzeuge für Common Lisp

Update 13. Jul 2007: Die Syntax des Makros FN hat sich in der Version 0.2.0 aufgrund von problematischem ursprünglichem Design entscheidend geändert . Dieser Blogeintrag ist damit nur noch von historischer Bedeutung. Bitte lesen Sie für korrekte Informationen den Eintrag über die neue Version der Mulkutils.

Ich habe ein kleines Makro geschrieben, das es leichter machen soll, Lambdaausdrücke in Common Lisp zu schreiben. Anstelle von (lambda (x y z &rest stuff) (+ y z)) heißt es ab jetzt für mich nur noch wahlweise (fn + _1 _2) oder (fn (+ _1 _2)). Wenn das mal keine Verbesserung ist!

Das Paket sollte ASDF-installierbar sein. Falls ich keine Fehler gemacht habe, lautet der dazu nötige Befehl wie folgt:

(asdf-install:install "http://matthias.benkard.de/code/mulkutils-0.0.1.tar.gz") 

Ich reproduziere hier als kleine Einführung einfach den Kommentar am Anfang der Datei:

#| Basic usage  
 | ===========  
 |  
 | (fn + _ 10)  
 | (fn (+ _ 10))  
 | (fn + _0 _1)  
 | (fn + _ _1)  
 | (mapcar (fn (cons _ _)) '(1 2 3))     ;=> ((1 . 1) (2 . 2) (3 . 3))  
 | (funcall (fn + _ 10 _3) 20 30 40 50)  ;=> 80  
 |  
 |  
 | Simple variant FN1  
 | ==================  
 |  
 | (funcall (fn () _) 42)                ;=> 42  
 | (funcall (fn _) 42)                   ;=> error (usually)  
 | (funcall (fn1 _) 42)                  ;=> 42  
 | (funcall (fn +))                      ;=> 0  
 | (funcall (fn1 +))                     ;=> value of +  
 |  
 |  
 | Argument-number-safe variant EFN  
 | ================================  
 |  
 | (funcall (fn (fn () _)))              ;=> #<LAMBDA ...>  
 | (funcall (efn (efn () _)))            ;=> error  
 | (funcall (fn1 (fn1 _)))               ;=> #<LAMBDA ...>  
 | (funcall (efn1 (efn1 _)))             ;=> error  
 |# 

MAPCAN, ein kleines Juwel

Ab und an entdecke ich in den Programmiersprachen, die ich verwende, kleine Details, die mir bislang unbekannt waren und die mich dann gleich dermaßen faszinieren, daß ich über sie bloggen muß.

Ein solches kleines Juwel ist die Funktion MAPCAN. Sie ist im Grunde identisch mit der Komposition von NCONC bzw. APPEND mit MAPCAR, das heißt: Sie iteriert über eine Liste und baut aus ihr eine neue Liste auf, wobei in jedem Schritt beliebig viele Elemente in die neue Liste aufgenommen werden können. Ein Beispiel:

(mapcan #'(lambda (x)  
            (if (fboundp x)  
                (list (intern (symbol-name x) :keyword)  
                      (fdefinition x))  
                '()))  
        '(cadr cons hello mulk +))  
;; => (:CADR #<Function CADR {102EBD41}>  
;;     :CONS #<Function CONS {10555969}>  
;;     :+ #<Function + {10185A81}>) 

Wie man an diesem zugegebenermaßen an den Haaren herbeigezogenen Beispiel sieht, eignet sich MAPCAN dazu, eine Liste in eine andere Liste zu verwandeln, wenn entweder nicht alle Elemente verarbeitet werden sollen oder man für jedes einzelne Element mehrere Ausgabeelemente braucht -- und zwar auch, wenn man nicht a priori weiß, wie viele.

Zurück aus der Dunkelheit

Da mein altes Webjournal den Geist aufgegen zu haben scheint, habe ich mir ein neues eingerichtet. Es mag keine Schönheitswettbewerbe gewinnen werden, aber viel häßlicher als der alte WordPress-basierte Blog ist es sicher auch nicht.

Geschrieben habe ich die neue Blogsoftware diesmal selbst. Sie besteht zurzeit aus 634 Zeilen Common-Lisp-Code und basiert in erster Linie auf CL-Markdown, Yaclml und den Lisp-CGI-Utils. Der Atom-Feed wird mithilfe von XML-Emitter erzeugt.

Im Gegensatz zu meinem alten Blog wird dieses Journal nicht von meinem eigenen Server gehostet, sondern von NearlyFreeSpeech.NET, einem wirklich feinen Webhoster, der nicht nur ein wunderbares, risikoarmes Gebührenschema hat, sondern neben allgemeiner Coolness auch Sprachen wie GNU CLISP als CGI-Plattformen zur Verfügung stellt.

Older posts

Full archive (slow!)

Posts by date
Title Date Comments
Linkliste: Spieleprogrammierung in Haskell 2018-07-10 08:30 no comments
Rust async/await Example 2018-06-27 20:43 no comments
Rancher OS mit Cloud-Init konfigurieren 2018-06-08 19:54 no comments
Continuation-Based Fibers for Scala 2018-01-07 21:59 no comments
Auf der Suche nach Blogtexteditorkomponenten 2017-06-25 08:25 no comments
Haskell: Machines in ST 2017-06-09 19:47 no comments
Repositories of my source code 2017-05-15 20:05 no comments
Erweiterungen von MulkCMS 2017-05-15 19:54 no comments
Souveräne Geldpolitik 2016-06-01 05:30 no comments
h2o-Webserver mit SELinux-Policy 2016-06-01 05:00 no comments
Rechtspopulisten auf dem Vormarsch – kein Wunder 2016-03-23 03:07 no comments
Supporting the Software Freedom Conservancy 2016-02-23 15:47 no comments
Die Eurokrise: Analyse und Vorschau. Warum Wachstum ohne Schulden ein Märchen ist und Syriza recht hat 2015-07-08 12:41 1 comment
An alle Bahnfahrer 2014-11-09 04:32 4 comments
Seeking the Pit of Success 2014-10-01 12:45 1 comment
Derivation rules are kind of like fractions. 2013-03-31 16:40 2 comments
ICFP 2012 poster presentation: Type Checking Without Types 2013-02-07 09:03 no comments
ECL and C++ 2013-01-01 23:22 1 comment
Dataflow and Reactive Programming in C++ 2012-12-08 22:29 no comments
Discordian Calendar (printable) 2012-12-06 16:25 1 comment
node.js Reverse Proxy: Handling Self-Signed SSL Client Certificates 2012-06-23 17:11 2 comments
A Programmable Reverse Proxy Using node.js 2012-06-19 12:24 no comments
Enumerating the Cartesian Product of Two Enumerable Sets 2012-04-17 11:32 no comments
MulkyID, an IMAP-based BrowserID Primary 2012-04-03 10:41 no comments
Adding BrowserID Support to a Clojure-based Web App is Ridiculously Easy 2012-03-29 19:39 no comments
Updating Chromium with Common Lisp 2012-03-10 20:32 no comments
Mulkrypt Update 2012-02-23 11:53 no comments
Instadump: Semi-automatic disk serialization of Clojure refs 2012-02-21 22:00 no comments
Inaction and Neutrality 2012-01-16 21:21 2 comments
Immanuel Wallerstein et al. über die Grenzen des Wirtschaftssystems 2011-10-26 19:51 1 comment
Mulkrypt: A Library of Cryptographic Algorithms for Racket 2011-08-31 18:36 2 comments
Purely Functional Integer Maps in C 2011-08-31 17:55 no comments
Using a Bounded Prime Sieve Algorithm to Generate Infinite Prime Number Lists 2011-07-31 15:56 2 comments
Compressed Mail Storage with Dovecot and Procmail 2011-07-15 21:39 no comments
Key Bindings in DrRacket 2011-06-26 13:33 no comments
JSON Template for R6RS 2011-06-26 12:21 no comments
JSON Template for Regular and Typed Racket 2011-06-25 22:26 no comments
Running Debian on a Mostly ZFS Filesystem 2011-05-08 09:49 4 comments
Lisp Is Not An Acceptable Java 2011-04-01 08:34 20 comments
CL-JSON-Template 2011-03-22 18:51 no comments
META: New Software 2011-03-22 00:33 no comments
What is Source Code? 2011-03-16 04:07 no comments
Church-Numerale in C++ 2011-02-12 21:25 no comments
PostgreSQL für Ad-Hoc-Daten 2010-12-07 16:50 no comments
META: Neuer Server 2010-11-20 18:07 no comments
Der Mac ist tot 2010-10-24 13:37 2 comments
Merke: Keine CD-Abbilder auf die Festplatte dd'en (Mac) 2010-09-22 13:45 1 comment
Produktivitätssteigerung vs. Lohn- und Arbeitszeitentwicklung 1991-2006 2010-09-19 14:23 no comments
Google Street View vs. Gemeinden 2010-08-15 11:09 1 comment
Das Metaobjektsystem von ECMAScript Harmony und das CLOS-MOP: ein Vergleich 2010-07-30 21:13 no comments
Eduroam/802.1X mit dem Palm Pre (speziell LMU/TU/LRZ München) 2010-07-14 18:01 5 comments
Multiple Dispatch in JavaScript (ECMAScript 5) 2010-06-03 12:06 no comments
Äquivalenz von Daten und Code — in Lisp und anderswo 2010-05-18 17:26 no comments
Ein Tool-Wrapper als Programmiersprachentest 2010-03-30 14:38 no comments
Die Dualität zwischen Conditions und dynamischen Variablen 2010-03-26 17:10 1 comment
Pre-Scheme und Freunde: doch noch richtiges Low-Level-Lisp 2010-02-16 09:42 no comments
Low-Level-Lisp 2010-02-15 21:00 2 comments
Das bayerische Studentenwerk wird ab sofort kaputtgespart 2010-01-30 08:44 1 comment
„Aber was ist mit denen, die sich dann einfach durchfüttern lassen?“ — Eine Verteidigung des Rechts auf Faulheit 2010-01-23 16:17 4 comments
Ein Rat für Android-Freunde: Wartet auf das Nexus One. 2010-01-19 17:27 no comments
Chaosradio Express zu: Mut zur Freiheit 2010-01-18 18:15 no comments
Eindrücke vom Notizenprogramm Circus Ponies NoteBook 2010-01-17 14:50 no comments
myBlogEdit — ein einfacher Desktop-Blogging-Client, der seine Arbeit tut 2010-01-17 13:12 no comments
Die Welt der freien Software vor dem Scheideweg 2010-01-14 15:25 1 comment
Syntaxhervorhebung von Lisp-Code im Web 2009-12-07 22:56 no comments
Stilistische Unterschiede zwischen Clojure und Common Lisp anhand von HTML-Generierung 2009-12-03 12:50 no comments
Backups auf einen Dateiserver 2009-10-17 23:24 no comments
ecto — Sinn und Unsinn eines Desktop-Blogging-Clients 2009-10-09 15:32 no comments
Implementierung eines Atom-basierten Webdienstes 2009-10-08 22:27 no comments
Deutsche Kleinparteien: Ökologisch-Demokratische Partei 2009-09-23 00:19 no comments
Deutsche Kleinparteien: Liberale Demokraten 2009-09-21 08:25 1 comment
Serie: Deutsche Kleinparteien 2009-09-21 07:51 2 comments
Befehls- als partielle Metataste in iTerm 2009-08-11 22:39 2 comments
Lisp im Chaosradio Express 2009-08-10 21:39 2 comments
Transfinite Wahrscheinlichkeitslogik online 2009-08-10 13:18 2 comments
Monaden in Scala 2009-07-31 15:41 1 comment
Bearbeiten von Tabellen in Numbers mit AppleScript 2009-07-18 22:37 no comments
Ad-hoc-Polymorphie in Scala: besser als Haskell? 2009-06-25 19:08 no comments
Funktionale Programmierung ist algebraische Programmierung 2009-06-21 14:07 1 comment
Scala 2009-06-21 14:07 no comments
Entwurf einer auf natürliche Weise homoikonischen objektbasierten Sprache 2009-05-30 22:12 no comments
.tar.bz2/.tar.gz in .lzma.io (cpio/afio) umwandeln 2009-03-28 17:59 4 comments
Interaktive GUI-Programmierung mit SLIME, Clojure, Qt und Swing 2009-03-11 12:07 3 comments
Lektionen aus dem Erfolg von Clojure und dessen Bedeutung für Lisp 2009-02-20 14:39 2 comments
Öffentliche Petition zum bedingungslosen Grundeinkommen 2009-02-17 17:41 no comments
Freitag 2009-01-31 19:40 no comments
Kryptofaschistische Blut- und Bodenideologie des Sportfanatismus 2009-01-11 16:54 1 comment
Inkscape 2009-01-05 13:23 no comments
Dovecot, launchd und die Leopard-Firewall 2009-01-04 13:19 no comments
F-Spot und ipernity 2008-12-31 17:24 no comments
Diskret-topologische Wahrscheinlichkeitslogik und die transfinite Behandlung endlicher Gruppen 2008-12-20 23:00 5 comments
Wörter zählen mit Common Lisp 2008-12-16 14:59 2 comments
Genossenschaften vs. öffentliche vs. Privatunternehmen 2008-12-15 20:02 1 comment
Feministische Doppelmoral 2008-11-15 15:13 no comments
Dresden for the win! 2008-11-15 14:41 no comments
Verschiedene Beweisstrategien in der Mathematik und ihre Anwendungen 2008-11-12 21:36 no comments
Die universelle Eigenschaft der Klumpentopologie 2008-11-01 19:57 no comments
Ein weiterer Klassiker 2008-10-18 14:27 no comments
Vorträge über Clojure 2008-10-18 13:13 no comments
Backquote in Clojure und die Referenztransparenz 2008-10-14 18:34 no comments
Clojure 2008-10-12 15:04 2 comments
Das deprimierende Thema der Familienpolitik 2008-09-24 19:23 no comments
SOLID, ein SLIME für O'Caml 2008-09-06 16:46 no comments
Das schöne C-Interface von Objective Caml 2008-09-01 16:26 no comments
Survey: Mehrsprachendokumentationsgeneratoren 2008-08-29 19:44 no comments
Die Objective-C-Runtime als Compilertarget 2008-08-25 15:36 no comments
FIXNUMs für Toilet Lisp 2008-08-04 16:43 no comments
Toilet Lisp: MACROLET als COMPILER-LET-Ersatz 2008-08-03 20:07 no comments
OpenJDK und libmawt.so 2008-07-22 13:47 no comments
Adé, Bill 2008-07-02 18:35 no comments
Toilet Lisp: der Code 2008-06-22 17:24 no comments
Toilet Lisp 2008-06-22 08:56 2 comments
Die Avantgarde schlägt zurück 2008-06-21 18:07 no comments
Étoilé nähert sich Smalltalk an 2008-06-07 12:42 no comments
Systemupdates mit NetBSDs pkgsrc-System 2008-05-31 12:03 no comments
Warum ich gegen den Antifaschismus-Vorschlag gestimmt habe 2008-05-27 18:03 no comments
Die USA, erklärt 2008-05-21 19:06 no comments
Smalltalk als Standardsprache für Étoilé 2008-05-19 19:10 no comments
Paketmanagement unter Mac OS X 2008-05-03 19:48 1 comment
CamlP4 und CamlP5 2008-04-29 12:06 no comments
Lesbare Syntax für OCaml 2008-04-27 15:31 no comments
Ad-hoc-Polymorphie: Gut? Schlecht? Keines von beiden? 2008-04-23 13:07 no comments
Leseprobe: Der gebrauchte Mann 2008-04-19 11:56 no comments
Qualität in der Wikipedia, continued 2008-04-14 17:11 no comments
Pico Lisp, Fenster in eine andere Welt? 2008-04-02 10:07 no comments
Lisp-Einführung 2008-04-01 18:14 1 comment
Objective-CL 0.2.0 2008-03-05 21:17 no comments
SpamAssassin-Migration zwischen Rechnern 2007-12-30 20:24 no comments
Eine Projektseite für Objective-CL 2007-12-24 14:23 no comments
Formulierungsspießertum 2007-12-09 14:47 no comments
GNUstep: Menüleistenstile 2007-11-25 14:46 no comments
Gezwungen, Subversion zu verwenden? git-svn bringt den Spaß zurück. 2007-11-24 13:10 no comments
Wer sind die Alt-Katholiken? 2007-11-05 22:14 no comments
Perl-Einzeiler für Multiline-Regexps 2007-11-04 20:34 no comments
Gedenkt unser jemand? 2007-11-03 18:30 no comments
Qualität? In der Wikipedia? Auf welchem Planeten leben Sie? 2007-11-03 17:09 no comments
CL-ObjC — freundliche Konkurrenz zu Objective-CL 2007-11-01 21:40 6 comments
Mit kleinen Schritten in die Utopie — sozial wie ökologisch 2007-10-15 16:06 no comments
Denkwürdige Merkmale der Sprache C: #import versus #include 2007-10-14 11:22 no comments
Denkwürdige Merkmale der Sprache C: dynamische Speicherreservierung einmal anders 2007-10-11 17:20 2 comments
Denkwürdige Merkmale der Sprache C: getc liefert keinen char zurück 2007-10-11 17:00 no comments
Kampf dem Spam! 2007-10-07 17:12 no comments
Objective-CL, eine Objective-C-Brücke für Common Lisp 2007-09-25 22:58 no comments
Mulkutils: Version 0.2.0 2007-07-13 14:16 no comments
Optimierungsflags für den Intel Core Duo 2007-07-11 18:54 no comments
DellfanD für Solaris 2007-06-28 13:53 no comments
Mulkutils: kleine Werkzeuge für Common Lisp 2007-06-28 13:18 no comments
MAPCAN, ein kleines Juwel 2007-06-10 15:43 no comments
Zurück aus der Dunkelheit 2007-05-30 19:46 1 comment