Ich benutze seit bestimmt schon einer Dekade bei Outlook den vorgegebenen Kalender “Deutsche Feiertage”. Der schreibt nicht nur Silvester mit y, sondern hat auch alle Feiertage aller Bundesländer drin. Das ist etwas nervig, wenn man in einem Bundesland wohnt, das am wenigsten Feiertage hat.
In CSS gibt es ja die Pseudoklasse :invalid, die immer dann triggert, wenn der aktuelle Inhalt des Inputs nicht passt. Also auch dann, wenn es z. B. noch leer ist, wenn der User noch gar nichts eingetippt hat. Um diese Unschnheit zu behandeln gibt es viele Librarys.
Jetzt braucht man das nicht mehr, sondern kann :user-valid und :user-invalid benutzen. Gibt es beide schon länger, aber viele haben davon nie gehört.
The content-visibility CSS property controls whether or not an element renders its contents at all, along with forcing a strong set of containments, allowing user agents to potentially omit large swathes of layout and rendering work until it becomes needed. It enables the user agent to skip an element’s rendering work (including layout and painting) until it is needed — which makes the initial page load much faster.
Diese Woche habe ich das Konzept von “German Strings” kennengelernt. Das sind immutable Strings, die mit einer erweiterten Variante der Short-String-Optimization gehalten werden. Sie enthalten noch in den 16 Bytes den Präfix vom String. Damit kann man viele String-Operationen machen, ohne den Pointer auf die Daten zu dereferenzieren. Und ist besser für Lokalität bzw. Caching.
Der Name kommt wohl daher, dass in einem Paper von einem deutschen Stand und einer der ersten Implementoren zu faul war, den tatsächlichen Autor zu nennen.
Hier hat sie jemand in Rust implementiert, um einen Punkt zu beweisen. Dort wird auch der Autor vom Paper genannt: Umbra.
Der Compiler von TypeScript ist ja ein nicht-optimierender Compiler. D.h. er lässt auch unerreichbaren Code im Resultat. Das ist durchaus so gewollt, heißt aber nicht, dass die Compiler-API sowas nicht hergeben würde. Hier hat das jemand genutzt und damit eine Dead-Code-Elimination gemacht: ts-remove-unused
Docker kann mehrere Build-Contexte. Damit kann man dann shared-Directorys haben, ohne den Build-Context auf den gemeinsamen Root zu legen.
docker
til
Beim Parsen von Operatorpräzedenzen gibt’s ja verschiedene Ansätze. Der erste und “einfache” Ansatz ist, die Präzedenzen in der Grammatik abzubilden. Die Implementierung mit einem rekursiven Abstieg ist dann trivial. Problematisch ist allerdings, dass der Syntaxbaum dann für einfache Sachen ziemlich tief wird. Für z. B. ein einfaches a = 5 hätte man auf der rechten Seite dann einen kompletten Unterbaum mit Nodes für jeden möglichen Operator, die dann aber quasi leer sind. Das ist sehr ineffizient.
Der nächste Schritt wäre ein Parser mit Dokus auf Operatorpräzedenz. Dort weist man jedem Operator eine Präzedenz in Form einer Zahl zu und löst es später ein einen vergleichsweise flachen Baum auf. Den Operatorpräzedenzparser kann man in den normalen rekursiven Abstieg integrieren und dann für binäre AUsdrücke verwenden. TypeScript macht das z. B. auch so.
Auf HN gibt es noch ein paar Links zu Blogposts von Digikey, die ein bisschen tiefer gehen.
elektronik
In HTML können manche Elemente ja nicht in anderen enthalten sein. Z. B. darf ein div nicht in einem span sein.
Manchmal ist man sich da nicht so sicher. Neulich hab ich auch überlegt, ob ein div in einem label sein darf und die Antwort zu finden hat echt gedauert. Hier kann man das nachschauen.
html
Docker kann beim Bauen linten. Vielleicht ist euch mal eine Warning aufgefallen, wenn gebaut wird. Wenn man z. B. FROM x as y schreibt, gibt es eine Warning, die besagt, dass das as in einem anderen Casing als FROM ist.
Das kann man jetzt zu einem Fehler machen. Dazu entweder docker build mit --check aufrufen oder den Anfang der Dockerfile ein bisschen konfigurieren;
@starting-style ist mittlerweile ganz okay supported (Firefox fehlt noch, 129 kommt aber nächste Woche) und CSS-Sizing mit calc-size wurde vor 5 Tagen in den Standard gemergt. Chrome wird es in in der nächsten Version releasen, alle anderen Browser nicht. Damit kann man jetzt nach height: auto animieren und transitionieren.
Mit progressive-enhancement-Techniken kann man es ja trotzdem schon nutzen.
Ich hab jetzt mal Node.js selbst gebaut, um das Feature mit der TS-Ausführung zu testen und bin positiv überrascht vom Bauprozess. Es ist:
git clone https://github.com/nodejs/node
cd node
./configure
make -j24
Es funktioniert einfach. Neben GCC und GNU/Make braucht man noch ein supportetes Python. Sind jetzt keine krassen Anforderungen. Hat (bis jetzt) auch noch keine Magic gemacht und irgendwelche Sachen aus dem Internet geladen oder global installiert.
Node.js kann jetzt SQLite. Für die WebStorage-API neulich haben sie es ja schon eingebaut. Jetzt exposen sie auch die interne API nach außen.
import{DatabaseSync}from'node:sqlite';constdatabase=newDatabaseSync(':memory:');// Execute SQL statements from strings.database.exec(`
CREATE TABLE data(
key INTEGER PRIMARY KEY,
value TEXT
) STRICT
`);// Create a prepared statement to insert data into the database.constinsert=database.prepare('INSERT INTO data (key, value) VALUES (?, ?)');// Execute the prepared statement with bound values.insert.run(1,'hello');insert.run(2,'world');
Zed ist gerade für Linux erschienen. Das ist ein Editor, an dem ehemalige Atom-Entwickler sitzen. Der ist vorallem interessant, weil sie von Grund auf auf Collaboration gesetzt haben. Die Kern-Datenstrukturen für ihre Textbearbeitung sind CRDTs.
Node.js 22.4.0 ist da und hat die Web Storage API hinter einem Flag. Bei Node kann man also jetzt localStorage und sessionStorage benutzen.
Ich habe ein bisschen damit gespielt.
Der sessionStorage ist für eine Programmlaufzeit gültig. Ist also nach einem Neustart weg. localStorage geht auch über Neustarts hinweg. Dafür muss man aber eine explizite Datei angeben: node --localstorage-file foo. Die Datei ist einfach eine SQLIte-Datenbank (wie die anderen Browser das intern auch machen):
$ echo'localStorage.setItem("hallo", "welt")' | node --experimental-webstorage--localstorage-file foo
$ sqlite3 foo 'select * from nodejs_webstorage'
key value
----------
hallo welt
Ist also eine super Alternative für Key-Value-Storage, wenn man nicht extra SQLite installieren will.
Storage Events scheinen (noch?) nicht zu funktionieren. Da wär auch die Frage, ob die bei localStorage nur innerhalb des selben Prozesses funktionieren, oder ob alle Node-Instanzen da kommunizieren, wenn sie einen Wert in dieselbe Datenbank schreiben.
nodejs
Hatten es gerade im Team, ging um Formatierungs-Commits.
Da gibt es ja 2 Ansätze, die man gehen kann:
Einen pre-commit-hook, der die Dateien formatiert, die ein Commit sowieso anpasst.
Einzelne Formatierungs-Commits, die mehrere Dateien anfassen
Bei 1. is der Vorteil, dass man wenige bis gar keine Konflikte bekommt, wenn andere noch in anderne Branches arbeiten. Der Nachteil ist, dass man Änderungen in einem Commit hat, die nicht zu der Fachlichkeit gehören, die man in dem Commit gerade tätigt. Beides ist mehr oder weniger gut/schlimm.
Der 2. Ansatz ist genau umgekehrt: Er hat keine Vermischung mit fachlichen Änderungen, aber ein hohes Potential für Konflikte.
Beide haben aber ein Problem: Sie machen ggf. git blame für die formatierten Zeilen kaputt. Zumindest für Ansatz 2 gibt es da einen Fix: .git-blame-ignore-revs. Die Datei beinhaltet Commit-Hashes von Commits, die bei git blame ignroriert werden sollen. So sind reine “Format”-Commits nicht mehr so schlimm.
GitHub respektiert die Datei auch bei der Blame-View, solange sie diesen Namen hat. Damit das lokale Git das auch respektiert, muss man es konfigurieren:
Ich hab ja mal einen Prototypen eines SQL-Parsers auf Typebene entwickelt, um rohes SQL statisch zu typisieren. Hier eine Einführung, wie man auf Typebene in TypeScript programmieren kann.
The navigator.sendBeacon(url, data?) method asynchronously sends an HTTP POST request containing a small amount of data to a web server.
Warum?
A problem with sending analytics is that a site often wants to send analytics when the user has finished with a page: for example, when the user navigates to another page. In this situation the browser may be about to unload the page, and in that case the browser may choose not to send asynchronous XMLHttpRequest requests.
Dieses Verhalten hat dazu geführt, dass es dann wieder Workarounds gab, die das Navigieren bewusst blockiert haben. Deshalb diese API.
GitHub stellt bei einem Repo alle PRs als Ref bereit. So braucht man bei einem PR eines externen Repos nicht ein anderes Repo als Remote hinzufügen, sondern kann es direkt fetchen und auschecken. Der Name ist pull/$ID/head.
Immer mehr wird ja nur noch über Discord besprochen oder gelöst. Früher gab es Foren, heute gibt es Discord. Foren wurden immer von Suchmaschinen indexiert. Der Vorteil davon liegt auf der Hand.
Wundert mich sehr, dass das noch niemand nach Rust portiert hat. Klar, so ein HTML-Parser is bestimmt nicht so trivial, aber wenn die Java-Implementierung sich automatisiert nach C++ übersetzen kann, wird das Problem schon relativ vereinfacht sein. Vielleicht benutzen sie in der C++-Version zu viel mutability, die man in Rust einfach nicht tun würde.
ICQ wird am 2024-06-26 abgeschaltet. Habe wie viele andere früher mit vielen Freunden Kontakt über ICQ gepflegt. Viele davon sind heute noch meine Freunde. Danke für deinen Dienst!
Es ist ja gerade die Build-Konferenz von MS. In der Sessionliste habe ich eine Session gefunden, die ich vor 10 Jahren niemals in einer MS-Konferenz in 10 Jahren erwartet hätte:
Wer sich die Stunde sparen will, hier ein paar Highlights:
Winforms bekommt Dark-Mode-Support
Graphics-Klasse kann jetzt abgerundete Rechtecke zeichnen
MVVM für WinForms und generell mehr Data-Binding-Support, Command-Bindings
…und natürlich - I kid you not - KI-Integration für diverse Controls. MakeIntelligent-Property auf einem Date-Input und der Textbox. Funktioniert über semantic-kernel, also auch über irgendwelche OpenAI-LLMs. harold.jpg
Das war eine lange Baustelle und nun geht es endlich. Das Problem war lange Zeit, dass diese Lambdas keinen Type-Guard darstellen und somit die Information verloren geht, dass es bspw. nicht undefined ist. Das erkennt TS jetzt automatisch:
TypeScript will infer that a function returns a type predicate if these conditions hold:
The function does not have an explicit return type or type predicate annotation.
The function has a single return statement and no implicit returns.
The function does not mutate its parameter.
The function returns a boolean expression that’s tied to a refinement on the parameter.
But TypeScript now does basic syntax checking on regular expressions!
Zusätzlich kam gerade React 18.3 als stabile Version, die einem auf React 19 vorbereitet:
To help make the upgrade to React 19 easier, we’ve published a react@18.3 release that is identical to 18.2 but adds warnings for deprecated APIs and other changes that are needed for React 19.
In the case of an index access method, it allows fast exclusion of non-matching tuples via signatures whose size is determined at index creation.
[…]
This type of index is most useful when a table has many attributes and queries test arbitrary combinations of them. A traditional btree index is faster than a bloom index, but it can require many btree indexes to support all possible queries where one needs only a single bloom index. Note however that bloom indexes only support equality queries, whereas btree indexes can also perform inequality and range searches.
Hello again! I’m picking up my series on Automata, with this post that goes into what I had always meant to get to: parsers. We’ll check out the old-school linear time parsing algorithms, which only need to go over the input once, without backtracking or caching. We’ll check out LL and LR, parse tables, recursive descent and recursive ascent. Welcome to the world of deterministic parsing…
xz wurde gebackdoort. Die Story ist schon ganz interessant. Aufgefallen ist es, weil die gebackdoorte Version Performanceprobleme hatte. Die Version mit Backdoor ist noch nicht wirklich verbreitet worden. Bei Debian haben anonyme Accounts darauf gedrängt, das Update mit der Backdoor anzunehmen. Eine ähnliche Geschichte, bei der die Version schnell released werden sollte, berichtet auch ein Fedora-Maintainer. Bei letzterem war es sogar so, dass der Fedora-Maintainer mit dem xz-Maintainer noch zusammen Speicherprobleme gelöst hat, damit sie die Version releasen können. Die Speicherprobleme kamen von genau der Backdoor. Ja, wirklich!
Die Backdoor passiert beim Bauen der tarball. Der scheinbare Backdoor-Autor hat im Vorfeld auf der Projektseite auch eine spannende Notiz hinterlassen:
Note: GitHub automatically includes two archives Source code (zip) and Source code (tar.gz) in the releases.
These archives cannot be disabled and should be ignored.
Das wird ja immer goldiger! In einem anderen Commit hat er die SECURITY.md-Policy “vereinfacht”:
You may submit a report by emailing us at
[xz@tukaani.org](mailto:xz@tukaani.org), or through
[Security Advisories](https://github.com/tukaani-project/xz/security/advisories/new).
-While both options are available, we prefer email. In any case, please
-provide a clear description of the vulnerability including:
-
-- Affected versions of XZ Utils
-- Estimated severity (low, moderate, high, critical)
-- Steps to recreate the vulnerability
-- All relevant files (core dumps, build logs, input files, etc.)
+While both options are available, we prefer email.
Zu der Geschichte kommen immer noch mehr Details ran. Auf HN werden sie in den Kommentaren gesammelt.
Was mich zu der Frage bringt: Angenommen, der GH-User hat diese Commits willentlich getätigt. Hat er dafür rechtliche Konsequenzen zu befürchten? Wie will man das machen? In diesem Fall wurde es noch bemerkt. Wenn daraus keine Konsequenzen folgen, hält das andere Maintainer kaum davon ab, dasselbe zu tun.
Bei der Analyse der installierten Backdoor gab es mittlerweile einige Fortschritte. Anfangs dachte man, dass RSA_public_decrypt gehookt würde, um einen Auth-Bypass zu erreichen. Mittlerweile wissen wir: Das war eine RCE.
Die Backdoor hat einen Ed448-Public-Key hartkodiert. Mit dem wird beim Anklopfen an die Hintertür überprüft, ob es sich um einen legitimen Angreifer handelt, durch einen Signatur-Check. Falls ja, wird die übermittelte Payload ausgeführt. Falls nein wird einfach weitergemacht, wie sonst auch. Das bedeutet: Man kann ein betroffenes System nicht durch sein äußerliches Verhalten erkennen - es sei denn, man hat den private Key zu der Backdoor. Um zu erkennen, ob ein System betroffen ist, funktioniert also kein Netzwerkscanner. Stattdessen muss das System lokal Dateien überprüfen.
Es wurde übrigens nicht nur bei Fedora und Debian versucht, das Paket unterzubringen. Auch bei Ubuntu wurde versucht, es vor dem Freeze aufzunehmen. Wir sind quasi durch Zufall einem Generalschlüssel für’s Internet ausgewichen.
Update:
Auf der Webseite des Maintainers gibt es jetzt auch eine Status-Seite: https://tukaani.org/xz-backdoor
Grundidee ist, dass der Server im Fehlerfall mit dem Content-Type application/problem+json antwortet. Das JSON im Body hat dann einige standardisierte Felder und welche, die die API selbst noch hinzufügen kann, bspw. mehr Kontext zum Fehler.
api-design
http
Weil es aktuell viel gibt, was bei React passiert, hier eine kleine Liste an Dingen, die React in Zukunft deutlich verändern werden oder könnten:
React hat bisher alles zur Laufzeit gemacht, weshalb Memoization 1. von Hand und 2. sehr aufwändig implementiert werden musste. Andere Frameworks setzen einen Compiler ein, der das ganze automatisch optimiert. React Forget ist genau das, für React.
Actions machen das Arbeiten mit Formen etwas angenehmer. Dazu gibt es auch die neuen Hooks useFormStatus und useFormState.
react-strict-dom: Ein Vorhaben, ein Subset von HTML zu definieren, das auch kompatibel mit React Native ist. Ziel ist, dass Komponenten besser und zuverlässiger zwischen React-für’s-Web und React-Native wiederverwendet werden können.
StyleX: Nicht per se nur für React, aber React ist das, wofür es bei Facebook benutzt wird. Eine CSS-in-JS-Lösung, bei der das gesamte CSS in eine einzige Datei kompiliert wird (auch das von Lazy-Loaded-Components). Ziel ist möglichst geringer Runtime-Overhead und eine schmale API. Wird bei Facebook bereits auf Instagram, WhatsApp und Facebook benutzt, um ein Designsystem bereitzustellen. Hat auch einen optimierenden Compile-Schritt.
Alle hier gelisteten Sachen sind noch stark in Entwicklung und noch nicht produktionsreif. Nichtsdestotrotz gut zu sehen, dass immer noch fleißig an React geschraubt wird.
security researcher Tommy Mysk and Open Web Advocacy first noticed that PWAs had been demoted to website shortcuts with the release of the second beta of iOS 17.4. Initially, it was unclear if this was a beta bug — stranger things have happened — or if it was intended to undermine the functionality of PWAs in the EU, a market where Apple is now being forced to allow alternative app stores, third-party payments, and alternative browser engines, among other things
Eine interessante API, mit der man sich ein bisschen gefummel sparen kann: URLPattern. Deno kann es schon und hat ein paar Beispiele.
Damit können bestimmt einige Web-Frameworks ihren Parser neu schreiben und nochmal schneller sein, sofern das URLPattern nativ implementiert wird. Vielleicht kann man das mit TS sogar so statisch typisieren, dass der die Variablen-Platzhalter mit teil des Typs sind.
Ein super Talk über häufig nicht verwendete Sachen in git: So You Think You Know Git? von Scott Chacon, einem Mitgründer von GitHub und dem CEO vom Git-Client GitButler.
Is eine Kurzform dafür, dass man früher die Resolve-Funktion im Promise-Konstruktor nach außem assigned hat. Ältere Leute werden sich vielleicht erinnern, dass das ähnlich zu einem Deferred aus JQuery ist.
atopile ist eine Sprache, mit der man Schaltkreise beschreiben kann. Komponenten kann man in Module aufteilen, erweitern etc. Die haben sogar CI-Support und einen Package-Manager. Bei Bauteilen kann man auch nur die benötigten Specs angeben und es werden passende Bauteile aus dem Katalog ausgesucht.
WebKit zieht eine gute Bilanz aus dem Bestreben, das Web mehr interoperabel zu machen. In der Liste darunter sind auch ein paar coole, neue Features. Darunter das inert-Attribut, font-size-adjust, @starting-style. Gerade das letzte sieht sehr spannend aus.
Ich finde es schön, dass die Browserhersteller zusammenarbeiten. Wenn man aber liest, auf was die sich so bei der Umsetzung konzentrieren: Das sind größtenteils alles neue Features, die sie am Ende wahrscheinlich eh alle umsetzen würden. Da liegt nicht wirklich der Fokus darauf, die Sachen zu fixen, bei denen die Browser schon länger divergent sind.
html
css
Deno hat gerade eine Version mit der Temporal API geshippt. Das war schon sehr lange in Stage 3, aber durch Abhängigkeit zu anderen Prozesse durften noch keine Implementierungen ohne Feature-Flag geshippt werden. Vor drei Wochen wurde der Hinweis dazu entfernt. Deno dürfte damit die erste Plattform sein, die es hat.
Hoffentlich ziehen die anderen nach, die API ist lange überfällig.
Edit:
Bei Deno ist es noch hinter einem Flag: --unstable-temporal. Ich glaube, sie sind trotzdem die ersten, die es überhaupt shippen.
Hack der Woche: linefont - eine Schriftart, mit der man Zeitreihen plotten kann. Font-Stacks sind ja mittlerweile super komplex und Fonts selbst auch eigene Programme. Für diese Schriftart muss man mit ein bisschen JS aus einer Zeitreihe die Schriftzeichen generieren, die dann mit der Font gerendert werden müssen.
Ich bin mir sicher, das würde auch ohne diesen Zwischenschritt gehen und man könnte das auch so machen, dass man einfach die Werte kommasepariert aufschreibt. Es gibt ja auch Schriftarten, die profanität zensieren. Oder ein Kunstprojekt, als die NSA-Geschichte vor 10 Jahren hochkam.
Ist natürlich a11y-Hölle, aber vielleicht ganz nützlich für interne Tools, wenn man keine Lust hat, ein ganzes Frontend-Plotting-Framework einzubinden, wenn man nur ne kleine Zeitreihe plotten will.
Die Curl-CLI hat einen Parameter --libcurl <filename>, mit dem man sich ein C-Programm ausgeben lassen kann, welches die libcurl so benutzt, dass der Request dabei heraus kommt, den man mit der Curl-CLI gemacht hat.
Bun kann mittlerweile nicht nur Rust und sonst irgendwie alles importieren, sondern auch SQLite-Datenbanken:
importdbfrom'./my.db'with{type:"sqlite"};const{id}=db.query("SELECT id FROM users LIMIT 1").get();console.log(id);// 1
Man kann mit Bun seine Anwendung ja auch in eine einzige Binary bundlen. Wenn man das macht beim obigen, muss beim Ausführen die DB im aktuellen Verzeichnis liegen. Man kann die DB aber auch in dei Binary embedden:
importdbfrom'./my.db'with{type:"sqlite",// Embed the database into the executableembed:"true"};const{id}=db.query("SELECT id FROM users LIMIT 1").get();console.log(id);// 1
Ob die DB da aber nur im Readonly-Modus geöffnet wird, wird im Blog nicht beschrieben. Ich gehe mla davon aus.
nodejs
bun
Zu Docker hab ich noch was neues gelernt. Man kann jetzt (mit DOCKER_BUILDKIT=1) auch bei einzelnen RUN-Commands Dinge mounten. Das ist ganz praktisch für Lockfiles oder Dinge, die man nur ein Mal braucht. Dazu gibt es neben Bind-Mounts auch Cahce-Mounts, womit man ein Cache-Verzeichnis in den Build-Container mounten kann. Nimmt natürlich etwas Reproduzierbarkeit, aber in gewissen Fällen (npm, rust) kann man damit echt viel Zeit sparen:
RUN --mount=type=bind,source=package.json,target=package.json \
--mount=type=bind,source=package-lock.json,target=package-lock.json \
--mount=type=cache,target=/root/.npm \ # mit cache des globalen npm-caches
npm ci
RUN --mount=type=bind,source=Cargo.toml,target=Cargo.toml \
cargo fetch
RUN --mount=type=bind,source=clitool.c,target=/clitool.c \
g++ -O3-o /bin/clitool /clitool.c
Je nach Fall kann man sich damit sogar eine Multi-Stage-Dockerfile ersparen.
Für TS 5.4 steht im Iteration-Plan ein Champion zu Throw Expressions auf der Liste. Kommt von rbuckton, einem TS-Engineer bei MS. Das Feature ansich begrüße ich. C# hat das “Problem” auch so gelöst, womit man sehr viele Dinge abkürzen kann. Bei Java sind throws selbst mit den neuen Switch-Expressions weiterhin noch Statements, weshalb sie für Switch extra die Grammatik angepasst haben, um throw zu erlauben. Das würde for free kommen, wenn throw einfach eine Expression wäre.
Wenn ich das richtig sehe, steht das Proposal kurz vor Stage 3 und damit kurz vor der finalen Umsetzung in JS.
Swift ist IMHO eine wirklich unterschätzte Sprache. Die haben keine Angst vor breaking Changes und trotzdem schaffen sie es, kein Python 3 zu erzeugen. Der Code ist üblicherweise sehr lesbar und seit einiger Zeit haben sie sogar ein Ownership-Modell, das zwar etwas verboser ist, aber IMHO deutlich verständlicher als das von Rust ist.
Vielleicht wären sie populärer geworden, wenn Linux- und Windows-Support nicht so lange gedauert hätte, wer weiß ¯\_(ツ)_/¯
Ich finde bei der Scream-Reihe ja immer lustig, wie der vorherige Film im Film vorkommt. Das geht teilweise mehrere Layer tief. Hier gibt es eine Übersicht an “Filmen in Filmen”, die sowas sammelt: Nestflix
Dokumentation von TS-Projekten wird bisher immer relativ dezentral gemacht. In meinen Projekten benutze ich tsdoc, ein CLI-Tool zum erstellen einer statischen Seite, um eine Seite für GitHub Pages zu generieren. In anderen Sprachen gibt’s ne zentrale Stelle, die die Docs aus den Paketen in der entsprechenden Paketverwaltung ziehen (z. B. bei Rust). Das hat vor allem den Vorteil, dass die Docs immer zu der bestimmten Version passen. Das Versionieren müsste man vorher selbst übernehmen - und dann war auch nicht garantiert, dass die Versionen nicht divergiert sind.
Jetzt hat jemand tsdocs.dev gebaut. Die Seite lädt das npm-Paket und baut aus den Typen innerhalb des Pakets eine Seite, die ähnlich zu den normale tsdoc-Seiten aussieht, zusammen mit der Version.
Interessante Lösung. Mal schauen, ob sich das durchsetzt.
Man kennt ja aus Docker-Multistage-Builds das COPY --from=build, um eine Datei aus einer anderen Stage zu kopieren.
Gerade habe ich beim Docker-Image von composer gesehen, dass das nicht nur beschränkt auf Images im selben Multi-stage-Build ist. Man kann dort jedes Image angeben.
Gestern hatte ich einen Talk über E-Mail von Dylan Beattie. Der Speaker hat mir so sehr gefallen, dass ich geschaut habe, welche Talks er noch so gehalten hat. Gerade habe ich From Hot Metal to HTML: The Story of Typography gesehen. Empfehlung geht raus!
Auf seiner Webseite sind noch mehr. Die werde ich jetzt nacheinander mal durchklicken.
Talkempfehlung: Windows and Linux: A Tale of Two Kernels von Mark Russinovich auf der Tech-Ed 2004. Er hat den Talk aber auch vor den MS-Kernel-Devs gehalten und die Folien von diversen Leuten (u. A. Linus Torvalds) reviewen lassen.
Gibt keine wirklich spannenden Updates. Mein Highlight ist das hier:
> A new experimental flag to flip module defaults (–experimental-default-type) – Node.js has two module systems: CommonJS modules and ECMAScript modules. Node.js treats files with a .js extension by default as CommonJS modules. This can now more easily be flipped.
Damit kann man jetzt immerhin ESM-Dateien ausführen, ohne eine package.json anzulegen.
Poes Gesetz (englisch Poe’s law) beschreibt ein Phänomen aus der Welt der netzwerkbasierten Kommunikation. Es besagt, dass es darin nicht möglich ist, eine politisch oder religiös extremistische Aussage so zu parodieren, dass die Parodie als solche eindeutig erkennbar ist, sofern man dies nicht explizit durch einen Smiley o. Ä. kenntlich macht.
HLI: Der X--Präfix für eigene HTTP-Header wurde bereits vor über 10 Jahren deprecated und wird nicht mehr empfohlen.
http
Node 20.6 ist da und kann nativ .env-Files laden. Damit kann man in einfachen Fällen das dotenv-Package rauswerfen. Nicht nur das, denn die Env-File wird von Node selbst geladen, nicht von der Anwendung. Das heißt, damit kann man auch Env-Vars setzen, die das Verhalten von Node selbst beeinflussen (z. B. NODE_OPTIONS).
Die Nightly-Version von TS im Playground kann es scheinbar noch nicht. Bin gespannt, zu sehen, wie das vim Compiler in ältere JS-Versionen gelowert wird.
Gerade bin ich in einem Gespräch mal wieder auf Root (die Bank) gekommen. Scheint nicht so gut gelaufen zu sein, zumindest leitet die Webseite jetzt auf ein komplett anderes Business weiter.
Echt schade, sowas fänd’ ich echt cool für ITler. In DE könnte man das sicher über Solaris machen - gibt es da das?
Valhalla is officially trying to bite on the topic of Nullability in Java
Valhalla ist der Projektname dafür, user-defined Value-Types in die JVM zu bringen, ähnlich zu denen in C# (Structs). Das zieht einen langen Rattenschwanz hinter sich her, darunter auch die Generics, die in Java mit Type-Erasure umgesetzt werden (in C# wird zur Laufzeit ein konkret anderer Typ instanziiert).
Teil dieses Rattenschwanzes ist jetzt wohl auch Nullability.
Das wird jetzt nochmal viel interessanter, denn Nullability ist in Kotlin nochmal ganz anders gelöst. Ob wir am Ende einen super komplizierten Interop zwischen den Sprachen haben (was einer der Vorteile von Kotlin war) oder werden sie sich soweit voneinander entwickeln, dass sie inkompatibel werden?
import{random}from'./random.ts'with{type:'macro'};console.log(`Your random number is ${random()}`);
Output im gebundleten js-File:
$ bun build ./cli.tsx
console.log(`Your random number is ${0.6805550949689833}`);
Interessantes Feature, das im Bundler-Bereich ein paar Grenzen verschieben könnte. So sontan könnte man damit einfach alle Bundler-Plugins durch einfache es-module ersetzen. Das würde IMHO Build-Toolchains deutlich vereinfachen.
javascript
Das Downs-Thomson Paradoxon besagt, dass die durchschnittliche Geschwindigkeit des Autoverkehrs auf einem Straßennetz durch die durchschnittliche Tür-zu-Tür-Geschwindigkeit von gleichwertigen Fahrten mit öffentlichen Verkehrsmitteln bestimmt wird.
Es gibt in SQL ein SELECT FOR UPDATE (MariaDB, Postgres), was man in Transaktionen benutzen kann, um schon frühzeitig zu sagen, dass man die entsprechenden Zeilen updaten wird, damit sie schon direkt gelockt werden können. Dadurch kann verhindert werden, dass Transaktionen unnötig neu versucht werden müssen.
Man kann sich mit einer SQL-DB ja eine Task-/Message-Queue für Arme bauen. Das ist besonders dann eine mögliche Lösung, wenn man noch kein ein Queuing-System hat oder die Anzahl an Messages pro Sekunde überschaubar ist.
In diesem Fall kann man nicht nur SELECT FOR UPDATE verwenden, sondern auch zusätzlich noch SKIP LOCKED. Durch das SELECT FOR UPDATE wird ja nicht verhindert, dass sich ein anderer Worker die Nachricht zusätzlich noch holt. Mit SELECT FOR UPDATE SKIP LOCKED kann man die Zeilen überspringen, die gerade in einer Transaktion sind.
db
SQL 2023 ist da. Der Standard kostet natürlich Geld. Hier gibt es eine Übersicht über die neuen Features. Interessant sind “Property Graph Queries”.
Beispiel aus dem verlinkten Post:
TypeScript 5.1 ist da. Auf der Build gab es einen Talk von Anders zu dem Release. Ist leider noch nicht auf YouTube hochgeladen. Vielleicht kommt es aber auch nie.
Jemand von OpenAI war auf der Build und hat ein bisschen erklärt, wie GPT intern funktioniert und was das für Implikationen hat, wenn man eine Anfrage macht.
HTML hat seit Kurzem ja ein Dialog-Element. Jetzt bald kommt das Popover-Element. Mehr hier.
css
html
Windows bekommt ein neues Feature namens “Dev Drive”.
Das ist eine gemountete VHD, die statt NTFS dann ReFS nutzt.
Warum will man das? Das hab ich mich auch gefragt. Scheinbar ist ReFS deutlich schneller. Außerdem kann das Antivirensystem bei der On-Access-Prüfung der Dateien dafür ausgeschaltet werden. Das Schlangenölsystem prüft dann wohl bei jedem npm install nicht mehr 10mio Dateien.
Aber cool zu sehen, dass ReFS es mittlerweile auch auf Desktopsysteme schafft.
USB-C zu kompliziert? Man braucht 4 Stunden, um rauszufinden, welches Kabel man kaufen muss? Manchmal ist das dann irgendwie doch DisplayPort? TIL: HDMI geht auch über USB-C, war aber DOA.
Bei Linux war (und bin) ich nicht so der Fan von Rust im Kernel. Der Grund dafür ist vorrangig, dass Rust zwar eine Systemsprache ist, aber nicht für solche Szenarien entwickelt wurde. Ein Beispiel dafür sind hidden allocations, die man nicht ausschließen kann. Man kann zwar bei vielen Dingen mittlerweile einen Allocator mit angeben, das war aber eher so ein afterthought. Bei Linux lösen sie das (soweit ich weiß) so, dass sie die C-Datenstrukturen im Kernel Wrappen, man also z. B. der Linked-List, die man bei Linux seit jeher verwendet, ein Rust-Interface geben. Das lässt mich vermuten, dass man Rust im Linux-Kernel nicht so schreiben wird, wie man Rust im Userland schreibt und man lediglich die Compilerfeatures wie den Borrow-Checker sinnvoll verwenden kann. Sowas wie Vec<T> habe ich da bisher nicht gesehen. Vielleicht irre ich mich da aber auch!
Rust wurde als C++-Ersatz konzipiert, nicht als C-Ersatz. Als C-Ersatz hingegen wurde z. B. Zig konzipiert. Das ist einer der Gründe, warum ich Zig eher im Linux-Kernel sehe, als Rust. Zig ist leider noch weit weg davon, stabil zu sein.
Bei Windows hingegen scheint das ne andere Story zu sein. Dort verwenden sie schon länger C++. Sie werden damit schon lange einen Weg gefunden haben, mit den Allocations umzugehen und damit einfacher Rust zu adoptieren.
Deshalb überrascht mich es auch nicht, dass sie es hinbekommen, die Rust-spezifischen APIs im Kernel zu verwenden. Als Beispiel führen sie Vec und Result an. Während Rust-Menschen zu OOM-Fehlern gesagt haben “installier doch einen globalen handler für OOM-Fehler” (das ist ja jetzt auch nicht die Spitze des Software-Engineering), hat MS die andere Route gewählt und die Standard-Rust-APIs um mehr try_ ergänzt.
Das Pilotprojekt war scheinbar ein GDI-Rewrite. Das freut mich besonders, denn ich hab schon so einige Probleme mit GDI gehabt. Hat bestimmt auch Spaß gemacht, Code aus den 80ern wegzuwerfen.
Chrome hat bei den Lighthouse-Tools jetzt einen Bundle-Analyzer. Bisher hat man sowas ja über PLugins für sein Frotnend-Build-System machen müssen. Dass das im Browser ist, hat jetzt den Vorteil, dass es “einfach geht”. Zusätzlich kann der Browser auch noch anzeigen, welcher Code davon gar nicht verwendet wurde. So kann man sich noch besser fokussieren.
Situation: Man hat eine Tabelle mit vielen Einträgen und möchte aber einen großen Teil (z. B. alte Einträge) löschen. Problem: Ein einfaches DELETE FROM logs WHERE created_at < '2023-04-01' kann sehr lange dauern und damit das Schreiben in die Tabelle blockieren. Ein Index hilft da leider nur bedingt.
Eine mögliche Lösung: Partitionieren der Tabelle. Indem man die Tabelle nach dem Löschkriterium partitioniert, kann man das Droppen von Einträgen deutlich beschleunigen.
Dieses Beispiel hab ich aus SQL-for-Devs übernommen. Dort gibt’s auch noch eine detailliertere Beschreibung sowie ein MySQL-Beispiel, wer mehr wissen möchte.
db
sql
Preview-Features von C# 12. Die haben echt eine Geschwindigkeit drauf. C# 11 ist gefühlt erst seit 2 Tagen draußen.
Safari 16.4 kann jetzt auch WebPush. Damit können es alle 3 Engines auf allen Plattformen. Man muss die Webseite bei Safari aber mindestens auf dem Home-Screen gepinnt haben, damit man die Notifications bekommen kann.
Gerade geht die “Acropalypse” rum. tl;dr: Das Standard-Bildbearbeitungstool unter Pixel-Geräten macht auf bearbeiteten Screenshots/Bildern kein Truncate, bevor es die neue Version speichert. Die ursprünglichen Daten sind also noch teilweise in der Datei vorhanden, wenn das bearbeitete Bild kleiner ist. Der CVE dazu ist CVE-2023-21036.
Bei PNGs lässt sich das relativ einfach detecten, denn da gibt’s einen “End-of-File-Marker” (der IEND Chunk). Wenn danach noch Daten kommen, sind diese aus dem ursprünglichen Bild.
Das Problem sollte aber prinzipiell nicht nur PNGs, sondern auch JPGs und damit Fotos betreffen. Nur das Rekonstruieren der Daten und das Finden von Extradaten ist anders.
Vor einiger Zeit gab es auch einen anderen lustigen Fehler bei GIMP: Wenn man einen Bereich auswählt und löscht, wurde nur der Alpha-Kanal auf 0 gesetzt. Die eigentlichen RGB-Daten blieben erhalten. Das hat damals Hanno Böck gefunden. Der PoC war damals ein relativ langsames Bash-Skript. Hier hab ich das damals mal implementiert, um damit einen Massenscan auf Bildern durchzuführen.
Die Leute bei Rust wollen das Coloring-Problem lösen. Den Artikel dazu hatte ich hier auch schon.
Der Versuch: Sagen, dass eine Funktion agnostisch bzw. “generisch bezüglich ihrer Asynchronizität” ist. Da hört es aber nicht auf, sie wollen das auch über const generisch machen. Der Plan ist sogar, das in Zukunft auch für andere Keywords zu machen.
trait?const?asyncRead{?const?asyncfnread(&mutself,buf:&mut[u8])->Result<usize>;?const?asyncfnread_to_string(&mutself,buf:&mutString)->Result<usize>{..}}/// Read from a reader into a string.?const?asyncfnread_to_string(reader:&mutimpl?const?asyncRead)->io::Result<String>{letmutstring=String::new();reader.read_to_string(&mutstring).await?;Ok(string)}
Das ist ein Read-Trait, bei dem die read-Funktion sowohl async als auch const sein kann. Primeagen (ein Rust-Enthusiast) ist nicht so begeistert. Wirt auf mich jetzt auch erstmal ziemlich verbose und bei einer Sprache, die bewusst keine Keywords mit mehr als 5 Buchstaben nimmt, fehl am Platz. Das haben sie aber offenbar auch selbst erkannt.
Animationen mit Code erstellen. Erinnert ein bisschen an manim, die Rendering-Engine von 3blue1brown. Motion-Canvas könnte vielleicht noch async/await verwenden, dann wird das aber schwieriger mit vollautomatisiertem Rendering, denk ich.
Zu Windows-8-Zeiten hat MS ein neues Dateisystem ausgerollt: ReFS. Das haben sie dann etwas abflachen lassen. Es könnte sein, dass ReFS bei Windows 11 wieder mehr Verwendung findet. Lange Zeit konnte man es nur auf Windows Server verwenden.
SQLite hat aktuell einen concurrent writer. Aktuell arbeiten sie an einer Erweiterung des DB-Formats, womit diese Limitierung entfällt: HC-tree
Der Multi-Process-Support war deshalb immer etwas hakelig. Das zu verbessern ist jetzt eine Designziel.
Datenstrukturdienstag hatten wir schon lange nicht mehr. Dafür mal etwas neueres: Der XOR-Filter (das Paper dazu).
Abstract:
The Bloom filter provides fast approximate set membership while using little memory. Engineers often use these filters to avoid slow operations such as disk or network accesses. As an alternative, a cuckoo filter may need less space than a Bloom filter and it is faster. Chazelle et al. proposed a generalization of the Bloom filter called the Bloomier filter. Dietzfelbinger and Pagh described a variation on the Bloomier filter that can be used effectively for approximate membership queries. It has never been tested empirically, to our knowledge. We review an efficient implementation of their approach, which we call the xor filter. We find that xor filters can be faster than Bloom and cuckoo filters while using less memory. We further show that a more compact version of xor filters (xor+) can use even less space than highly compact alternatives (e.g., Golomb-compressed sequences) while providing speeds competitive with Bloom filters.
# Note! MF2 syntax is under development; this may still change
greeting = {Hello {$place}!}
new_notifications =
match {$count}
when 0 {You have no new notifications}
when one {You have {$count} new notification}
when * {You have {$count} new notifications}
Schon ganz interessant. Das Format “MF2” wird allerdings nicht von den Browserherstellern standardisiert, sondern vom Unicode-Konsortium.
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!
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?
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:
voidMatchMessage(stringmessage){varresult=messageis['a'or'A',..vars,'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.voidValidate(int[]numbers){varresult=numbersis[<0,..{Length:2or4},>0]?"valid":"not valid";Console.WriteLine(result);}Validate(new[]{-1,0,1});// output: not validValidate(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.
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.
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.
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.
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.
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.
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.
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)
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.
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")]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>.
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:
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.
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.
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
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.
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.
Still x86-only.
Brave shippt jetzt mit einem Feature namens De-AMP. Damit umgeht man das AMP von Google, zwecks Privatsphäre.
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.
nodejs
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).
Das Proposal mit den Type-Annotations für JavaScript von neulich ist nun ein offizielles TC39-Proposal.
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)
cpp
language-design
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 Behavior (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 Implementierungen 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.
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.
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 (beliebige 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.
Da gibt es ein sehr gutes Video von einem meiner lieblings-Youtuber zu, 3blue1brown.
Update:
Korrektur der Punktposition, 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, schickt 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 Parameter ü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.
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.
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 synchronisiert 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.
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 Variante 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 ist’s 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 ab und zu 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 vor allem 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 parallelisiert 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.
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 behavior?
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 realen 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-Bastelei 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 README:
# 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).
Anwendungsfall: 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 Datenstruktur 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 folgenden Alias bei Git hinzugefügt:
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.
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.
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");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 probabilistische 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:
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.
db
Man kann in mit CSS ein Element veranlassen, keine Click-/Pointer-Events zu verarbeiten.
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 vor allem 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.
software-engineering
typescript
Ein “Gotcha” bei Floating-Point-Kram (IEEE 754) ist das Runden:
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 gerundeter 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 schöne Tabelle!
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# mittlerweile zwei neue Access-Modifiers, die noch “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. Vor allem waren es Strings, die semantisch mehr miteinander zu tun hatten, je gleicher der Prefix ist. Das hätte vor allem 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:
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 vereinfachen. 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: Postgres 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:
Die Seite lädt die Datei allerdings wo hoch ud überträgt nur den Link. Leider wird das nicht Ende-zu-Ende verschlüsselt.
Es gibt noch zwei Projekte, die sowas machen: wave-share und ggwave.
Datenstrukturdienstag:
Heute keine konkrete Datenstruktur, nur ein Link auf diese tolle Seite, die Algorithmen und Datenstrukturen mit IKEA-Bauanleitungs-artigen Grafiken erklärt.
Da gibt es noch eins: htmlq. Das ist wie jq, nur für HTML und als Abfragesprache 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.
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. Und zwar 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
[…]
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):
vararr="a.b.".split("\\.");// ["a", "b"]
Dieses Verhalten alleine produziert schon Bugs (tatsächlich habe ich dadurch 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:
vararr="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.
### 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. […]
Betrachtet natürlich nur die Fälle, die Trends wurden. Nicht die, die auf HN waren, aber nichts wurden.
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 beschreibt 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.
Ab und zu 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 einiger 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. Vorrangig 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 vor allem verrückte Ideen für Programmiersprachen.
Im Vergleich zur Beta ist vor allem 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):
(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 vor allem 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.
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. Vor allem 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. Auszug 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 hab’s 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.
Auch schon etwas älter (2007), aber hat nicht an Relevanz verloren. Auf HN war auch gerade ein passender Post dazu.
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 Durchschnittsgröß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 Blog-Posts, die man als Internetmensch normalerweise mal gesehen hat. Ich fange hier mal eine Serie an.
Der Anfang ist: The Web is Doom aus 2016. Grundaussage: Die durchschnittliche 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.
Sieht cool aus. Sind aber kommerziell - vielleicht gibt’s auch FOSS-CLI-Tool-Alternativen?
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 Lambdas 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 machen 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
tools
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 experimentierfreudig ist, kann das mit dem Hardlinks ja auch mal auf einem node_modules ausprobieren.
Die npm-Alternative pnpmfä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.
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 Zweifelsfall 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, einen 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.
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.
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 Riesenbeispiel 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('#row');// 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 vor allem 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 beeinflusst.
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:
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:
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.
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.
Ist generell ein toller Kanal, falls Euch Assembler interessiert.
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 Embedded-Software gibt’s hier auch einen Eintrag.
Wer noch andere kennt oder Verbesserungen hat, her damit!