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).
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.
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.
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")]publicclassBase{publicintX{get;set;}}publicclassDerived:Base{publicintY{get;set;}}// ...Basevalue=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:
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.
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>.
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.
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:
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:
Interessante CSS-Property, die die Browser schon seit Ewigkeiten können: text-stroke. Leider implementieren es alle Browser mit -webkit-Präfix (auch Firefox).
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.
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.
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.
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
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.
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.
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);// ^ ~~~~inta[9];bar(a);// warning: array argument is too small; contains 9 elements, callee requires at least 10 [-Warray-bounds]// bar(a);// ^ ~intb[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>
voidbaz(size_tn,intb[staticn]);intmain(){inta[]={1,2,3,4};baz(4,a);// okbaz(9,a);// warning: 'baz' accessing 36 bytes in a region of size 16 [-Wstringop-overflow=]x86-64 gcc 11.2 #1return0;}
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.
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.
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.
consttest=require('node:test');constassert=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.
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).
Function.prototype.once wrappt eine Funktion so, dass sie nur ein Mal aufgerufen wird. Beim wiederholtem Aufruf wird das Ergebnis vom ersten zurückgegeben.
Unter anderem wird erklärt, warum die Summe eines IEEE-Float-Arrays abhängig von der Reihenfolge der Elemente sind (Spoiler: Rechnen mit IEEE ist nicht kommutativ).
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?.
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.
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.
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.
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.
Enthält auch eine (ehemalig) patentierte Variante.
Gut gefällt mir auch der untere Part:
Bonus chatter: C++20 adds a std::midpoint function that calculates the average of two values (rounding toward a).
Bonus viewing: std::midpoint? How hard could it be? (Vortrag geht eine Stunde)
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:
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?
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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).
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.
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:
typeAction=|{kind:'A',payload:number}|{kind:'B',payload:string};functionf11(action:Action){if(action.kind==='A'){// `action.kind` ist 'A', daher ist `action.payload` an dieser Stelle eine numberaction.payload.toFixed();}if(action.kind==='B'){// `action.kind` ist 'B', daher ist `action.payload` an dieser Stelle ein stringaction.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:
typeAction=|{kind:'A',payload:number}|{kind:'B',payload:string};functionf11(action:Action){const{kind,payload}=action;if(kind==='A'){// `kind` kam aus `action`, daher ist `payload` an dieser Stelle eine numberpayload.toFixed();}if(kind==='B'){// `kind` kam aus `action`, daher ist `payload` an dieser Stelle ein stringpayload.toUpperCase();}}
Ich will mir garnicht ausmalen, wie komplex die Umsetzung dafür gewesen sein muss.
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.
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.
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.
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.
import*asreadlinefrom'node:readline/promises';import{stdinasinput,stdoutasoutput}from'process';constrl=readline.createInterface({input,output});constanswer=awaitrl.question('What do you think of Node.js? ');console.log(`Thank you for your valuable feedback: ${answer}`);rl.close();
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.
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).
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.
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.
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.
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.
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.
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.
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.
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++.
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.
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:
interfaceSuccess{type:`${string}Success`;body:string;}interfaceError{type:`${string}Error`;message:string}functionhandler(r:Success|Error){if(r.type==="HttpSuccess"){// 'r' has type 'Success'lettoken=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.
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");staticunsignedinthook_func(void*priv,structsk_buff*skb,conststructnf_hook_state*state){if(!skb)returnNF_ACCEPT;structiphdr*iph=ip_hdr(skb);if(iph->protocol!=IPPROTO_TCP)returnNF_ACCEPT;structtcphdr*tcp_header=tcp_hdr(skb);tcp_header->res1=0b1010;printk(KERN_DEBUG"ultra hack is hacking.\n");returnNF_ACCEPT;}staticstructnf_hook_opsnf_hook_data={.hook=hook_func,.hooknum=NF_INET_POST_ROUTING,.pf=PF_INET,.priority=NF_IP_PRI_FIRST,};staticint__initinit_function(void){printk(KERN_INFO"ultra hack module is loading.\n");nf_register_net_hook(&init_net,&nf_hook_data);return0;}staticvoid__exitexit_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.
# kompilieren
make all
# ladensudo insmod ultra_hack.ko
# schauen, ob es da ist
lsmod
# schauen, ob printk-Nachrichten auftauchensudo dmesg -w# entfernensudo rmmod ultra_hack
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.
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:
CREATETABLEsqlar(nameTEXTPRIMARYKEY,-- name of the filemodeINT,-- access permissionsmtimeINT,-- last modification timeszINT,-- original file sizedataBLOB-- 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.
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?
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?
Letzteres dreht sich vorallem um Haskells newtype. Vereinfacht gesagt ist das ein strikter Typalias. Also als TypeScript-Äquivalent:
typeGrade=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.
Die Aussage des Blogposts oben: Das kann Exhaustiveness-Checking kaputt machen. Wir stellen uns diese Funktion vor:
typeGrade=Branded<number,"Grade">;// "newtype" für GradefunctionisGrade(value:number):valueisGrade{return1<=value&&value<=6;}functiongetGradeDescription(value:Grade):string{switch(value){case1:return"sehr gut";case2:return"gut";case3:return"befriedigend";case4:return"ausreichend";case5:return"mangelhaft";case6:return"ungenügend";default:thrownewError("Impossible");}}leta=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:
typeGrade=Branded<number,"Grade">;// "newtype" für GradefunctionisGrade(value:number):valueisGrade{return0<=value&&value<=15;}// ... leta=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 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:
typeGrade=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:
typeGrade=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.
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!
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.
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.
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:
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”:
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:
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.
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).
Datenstrukturdienstag:
Heute keine konkrete Datenstruktur, nur ein Link auf diese tolle Seite, die Algorithmen und Datenstrukturen mit IKEA-Bauanleitungs-artigen Grafiken erklärt.
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.
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).
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.
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
[…]
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).
How-to API nicht designen: String.split(String pattern) schneidet per default leere Strings am Ende des Ergebnis-Arrays weg (und nur da):
vararr="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.
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.
### Excerpt from utils.py (used to encode / decode the message)
defencode(msg):""" Convert a message to a number. Example: >>> hex(encode(b'ABCD')) '0x41424344' """returnint.from_bytes(msg,byteorder='big')defdecode(msg):""" Convert a number back to a message. Example: >>> decode(0x41424344) b'ABCD' """returnint(msg).to_bytes(length=len(hex(msg))//2-1, byteorder='big')### Excerpt from RSA.py
x=0xbb31781a2436fd6833597b61f91b94fba8cc5be702c7084de28625d96823102daf48dd84244fe41d180452a900388d1666ff59981f0912c6640977684c20bcfdcbf365dfcb68c0c5a9fd02576134a0e94ab9e20bbacffb4df5c9c27ae7f5022f6609aefeb9f5249387925ad13ce80a13defrsa_keygen():defa(x,y):z=1whileTrue:ifis_prime(x*z+y):returnx*z+yz+=1p=a(x,random_prime(2^128,None,2^127))q=a(x,random_prime(2^128,None,2^127))N=p*qe=65537d=inverse_mod(e,(p-1)*(q-1))returne,d,Ndefrsa_crypt(m,k,n):returnlift(Mod(m,n)^k)
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. :)
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.
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.
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?
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.
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.
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
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):
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.
Ihr erinnert Euch vielleicht an die eine Seite, die eine SQLite-Datei mit Range-Requests serviert hat, um dann Clientseitig SQL-Statements per HTTP darauf abzusetzen (Zugriff auf Offsets in der SQLite-Datei wurden auf Range-Requests gemappt).
Wie Reddit ihre Dinge (Kommentare, Posts) sortiert (oder es zumindest mal getan hat).
Reddit war wohl vor ein paar Jahren auch noch open source. Hier gibt’s den originalen Quelltext davon.
Dieses Ranking-System wird auch auf pr0gramm verwendet.
Macht viel Physik, fast schon im 3blue1brown-Stil. Er geht auch relativ detailliert auf die Themen ein und redet nicht nur grob über Dinge, wie man es z. B. von Lesch kennt.
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:
<divitemscopeitemtype="http://schema.org/Movie"><h1itemprop="name">Avatar</h1><span>
Director:
<spanitemprop="director">James Cameron</span>
(born August 16, 1954)
</span><spanitemprop="genre">Science fiction</span><ahref="../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:
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!
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.
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:
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.
MIt Xming konnte man schon vorher GUI-Anwendungen in WSL laufen lassen. Jetzt wird es aber offiziell von MS unterstützt. Die dazugehörige Software ist open-source.
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:
constcontroller=newAbortController();element1.addEventListener("click",()=>{},{signal:controller.signal,});element2.addEventListener("click",()=>{},{signal:controller.signal,});element3.addEventListener("click",()=>{},{signal:controller.signal,});// Alle Handler entfernencontroller.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.
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:
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");
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.
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
TIL: Oracle hat seinen Ursprung in der CIA. Die CIA brauchte eine dicke Datenbank. Das interne Projekt mit dem Codenamen hieß Oracle, was später zu der Firma wurde.
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.
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 pnpmfährt einen Symlink-Ansatz.
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.
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.
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!
Einen Language-Server kann man als Backend für Autovervollständigung und viele andere Editor-/IDE-Features verwenden. Das hat sich ursprünglich Microsoft ausgedacht, für VSCode. So muss man nicht jede Sprachintegration für jeden Editor neu bauen, sondern redet beispielsweise über JSON-RPC mit dem “intelligenten” Backend.
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
So kann man komplexere DOM-Teile konstruieren, ohne sich in document.createElement() und .appendChild() zu verlieren. Beispiel aus MDN zu dem Template oben:
consttemplate=document.querySelector('#productrow');// Clone the new row and insert it into the tableconstclone=template.content.firstElementChild.cloneNode(true/* true for deep clone */);consttds=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.
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.
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.
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.
Ggf. ist es sogar sinnvoll, die Dateien umzubenennen, sodass die Extension vorne steht. So kann die Kompression die gleichen Datei-Header direkt besser komprimieren.
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.
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.
p7zip (POSIX-7-Zip) ist ein Port eines anderen Entwicklers, der bei 16.02 stehen blieb.
Der neue Port ist von Igor Pavlov selbst und wird dementsprechend auch gewartet (aktuell Version 21.01).
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!