proj-oot-ootNotes40

Difference between revision 5 and current revision

No diff available.

" Integer representation – two’s complement Floating point – IEEE 754 Power-of-two byte/word sizes – There’s other good choices, but 8 bits per byte and 2^N bytes per word is a pretty good one Byte endianness – not universal, but basically settled on little endian for processors and I only foresee it becoming more universal with time. Network protocols are big-endian by default but that’s at least well defined, and not enough of an impediment to matter UTF-8 – You’re going to have a hard time coming up with a better variable-length integer representation that prefers small integers. There’s others that are just as good, or better for some specific purposes, but UTF-8 still has some nice properties. Serial connections and UART’s, whether RS-232 or TTL or whatever. There’s a million little variations but the basic setup is perfectly good for what it does. ELF executable format. Some improvements could be made, I’m sure, but all in all it’s pretty Fine. Paging over segmentation for memory management. There’s nothing fundamentally wrong with segmentation, but at least on any 32-bit chip, either you are an embedded system without much memory and don’t need an MMU, or you’re large enough to afford an MMU with paging. It’s still used on 8-bit microcontrollers but basically no popular 32+ bit CPU developed since the VAX has used segmentation. " -- https://wiki.alopex.li/ThingsWeGotRight

--- ---

~ mwcampbell 8 hours ago

link flag
    Mistake 7 : Build lifetime heavy API

I’ve struggled a bit with this. More generally, as a newcomer to Rust, I’ve found myself acting as if the slightest compromise in efficiency is a slippery slope straight to the excesses of Electron. Rust promises zero-overhead abstractions, so sometimes I try too hard to use the zero-overhead solution for everything. Another example is trying to make a struct or function generic instead of just using Box<dyn MyTrait?> or even Arc<dyn MyTrait?>.

    6
    zaphar 16 hours ago | link | flag | 

Rust exposes the fact that memory management for a linked list is hard. You can implement a linked list but satisfying the borrow checker while doing so is difficult. Many times you’ll just drop to some form of weak reference handle system backed by a vec. Which gives you the ergonomics of the linked list and less fighting the borrow checker.

~ fouric 8 hours ago

link flag
    Start by implementing a graph based algorithm.

My favorite personal projects are all intrinsically graph-based. Is Rust a poor fit for me?

    ~
    gpm 7 hours ago | link | flag | 

Use something like petgraph you will be fine. Or if you’re implementing it by hand, store the nodes in an arena/vec and instead of pointers use indicies.

It’s a bit of a bigger step for newcomers, because you’re learning both “rust” and “how to deal with graphs in rust without fighting the borrow checker” at the same time, instead of one and then the other.

~ denys_seguret 19 minutes ago

link flag

Same for me and I still manage to write them. Simply be sure to understand that you’ll have to suffer more than other people learning Rust if you start here. The best working approach is the arena+index one but it needs you to be very cautious (test units help, of course) as it means you’re going around the protection given by the ownership model.

---

generic design template:

(core) data structures; use cases; questions; UI screens/modes and flows between them; ui views / filters / queries; questions; inspirations; prime priorities and non-priorities; to do for design; notes/thoughts/main pages; documents/log/fAQ about design decisions that are supposedly finished and in the main pages; list of parts of the design (for example for a programming language, type system, syntax, control flow, etc) or maybe you don't need a list just look at the files that exist for main pages/thoughts/notes. Toreads. Free text introduction, free text why, free text status / to do. User situations (like user stories but less emphasis on the type of user/marketing)

---

"The entire C separation of preprocessor, compiler, assembler, and linker exists because each one of those could fit independently in RAM on a PDP-11 (and the separate link step originates because Mary Allen Wilkes didn’t have enough core memory on an IBM 704 to fit a Fortran program and all of the library routines that it might use). Being able to fit an entire program in memory in an intermediate representation over which you could do whole-program analysis was unimaginable." -- David Chisnall

---

" In defense of complicated programming languages ... What then, would happen if we were to ban classes from Python?

Oh, it would make the language so much simpler! ... users would find the number of variables would get out of control. Until one day, some programmer gets the neat idea that they could reduce the number of variables to keep inside their head if only they grouped variables in a dict:

def bark(dog_dict): print("WOOF!" if dog_dict["weight_kg"] > 25 else "Woof")

rex = {"name": "Rex", "weight_kg": 35}

And so they would have accidentally re-introduced classes, only this time the existence of classes would have been implicit in the code, and their behaviour ad hoc defined, their invariants spread all over the source files, and with no language-level tooling or introspection to help the programmer. Classes would still exist, but as implicit patterns.

These kinds of structures emerges spontaneously and constantly, all over code.

Programming languages used to not support functions, but then it was discovered that instructions tended to be grouped in blocks by their functionality, and that conceptualizing it as a function made the code easier to reason about. The introduction of functions did not make programming more complex, on the contrary, it became simpler in the way that matters.

Languages used to not have structs, but then programmers discovered the usefulness of grouping sets of data into an abstract, higher-order kind of data called a struct. And again, this feature did not make programs more complex, but made them simpler. ... Julia has a complicated type system. Types like AbstractSet?{Union{Nothing, <:Integer}} are not simple to learn to parse, or trivial to reason about in existing code. But the structure of this type, and thus its complexity, is merely an instantiation of the programmer's intent about the data it represents. With a simpler type system, that type would not exist, but the same intent would be there, nonetheless. ... Not co-incidentally, recent versions of Python have introduced type hints backed by a complex type system, such that programmers can now express the very same idea as collections.abc.Set[typing.Option[numbers.Integral]]. ... A post of the format "In defense of ..." does not have a lot of room for nuance, but of course the issue in this post is not clear cut on way or the other. "More language features" does not equal "more better", and the detractors of modern language complexity do have points that are worth considering, at least in isolation.

All the language features in the examples above - classes, advanced types, and the borrow checker - have an important trait in common: They all feel like they emerge spontaneously from existing code independently of whether the language designer has thought about them. In that sense, they are the best kind of feature; instead of adding new things to worry about, they merely provide a vocabulary and tooling for dealing with already existing problems.

Not all language features are like this. For example, Julia has four different ways of defining a function, and just as many variations on how a for loop looks. One can define a type to be a struct, mutable struct, abstract type and primitive type (all the former possibly parametric). Types can be placed in the type hierarchy as either concrete types, abstract types, union types or unionall types. The representation of types (i.e. type of types) can be either DataType?, Union, UnionAll?, or Bottom. " -- [1]

" A hypothetical language like the one the author considers – exactly like Python, but without classes – would obviously suck because a lot of the code that is currently very concise would get pretty verbose. Presumably, that’s why they added classes to Python in the first place. However, that doesn’t mean a better abstraction mechanism, which could, for example, unify classes, dictionaries, and enums (currently provided through a standard library feature) wouldn’t have been possible. That would get you the same concise code, only with less complexity. ...

Edit: Or, to put it another way, there is such a thing as an “evolutionary leap”. One sufficiently powerful mechanism can make several less powerful mechanisms obsolete. Templates and template functions, for example, made a whole class of C hacks (dispatch tables, void pointer magic) completely unnecessary. " -- [2]

another comment: " This is so weird because I agree with the general concept but I think it would be a much better article with a different example. Python without classes would legitimately be a much better language; encapsulation is so important that it needs to be available in a way that’s not coupled to inheritance and polymorphism.

The article also ignores the fact that many of these features can be added by a 3rd-party library in a well-designed language, see Lua and the many 3rd-party class systems available for it as opt-in features, or Clojure’s pattern matching and CSP/goroutine libraries. " -- [3]

---

a terrible thing about Lisp is that when you alternate between assigning values to variables, and doing 'if' statements, you end up having deeper and deeper levels of nesting as you nest an 'if' inside a 'let*', which is itself nested inside an 'if', which is itself nested inside a 'let*', etc.

imperative programming with mutation avoids this, but also just automatically lowering "x=3; ...rest of function..." to "(let* ((x 3)) (...rest of function...)" would do the trick.

---

and of course another annoying thing about Lisp is when writing arithmetic expressions, lack of any order of operations leads to extra parens

---

throw10920 10 hours ago

root parent next [–]

Thank you for explaining - now that you've pointed it out, I can see that this is just another form of the "graphs are hard in Rust" problem that I've encountered before.

However, I still stand by my point - Rust might be bad at graphs, but I believe that a well-designed language with a borrow checker (maybe something closer to Lobster, which automatically inserts RC cells when you try to multiply mutable borrow[1] - something obviously more correct than what Rust does, I'm not sure how they messed that one up) wouldn't necessarily have to be.

[1] https://aardappel.github.io/lobster/memory_management.html

---

"But these traits are in some ways at odds with each other. The most simple code is probably not the most testable. All those interfaces and injected dependencies make for convenient testing, but have a cost in terms of simplicity.

Your heavy reliance on singletons may make things easy to understand, but it might not lead to a maintainable application." -- https://www.steveonstuff.com/2022/01/27/no-such-thing-as-clean-code

---

https://achievement.org/

---

"I try to optimize my code around reducing state, coupling, complexity and code, in that order. I'm willing to add increased coupling if it makes my code more stateless. I'm willing to make it more complex if it reduces coupling. And I'm willing to duplicate code if it makes the code less complex. Only if it doesn't increase state, coupling or complexity do I dedup code." -- https://news.ycombinator.com/item?id=11042400

---

i guess in order to have something like emacs, where part of the application is written in a scripting language and the user can replace arbitrary provided functions with their own versions, you need an interpreted language and some sort of 'extreme late binding' where eg functions are looked up by name at runtime.

---

FRIGN 19 hours ago

link flag

‘C makes it easy to shoot yourself in the foot; C++ makes it harder, but when you do it blows your whole leg off’ - Bjarne Stroustrup

    ~
    singpolyma 7 hours ago | link | flag | 

How does C++ make it harder?

    ~
    walleye 2 hours ago | link | flag | 

RAII for the most part.

~ snej 22 minutes ago

link flag

RAII, smart pointers to manage memory, safer strings, robust collection classes, type-safe templates instead of ad-hoc casting, references vs. pointers, optionals and variants instead of unsafe raw unions…

---

"Apple M1, ARM64, and PowerPC?64 users rejoice! Go 1.18 includes CPU performance improvements of up to 20% due to the expansion of Go 1.17’s register ABI calling convention to these architectures. Just to underscore how big this release is, a 20% performance improvement is the fourth most important headline!" -- [4]

---

" Why pipes, anyway? In our setup, the web service which generates ZIP files communicates with the web server over pipes; it talks the Web Application Socket protocol which we invented because we were not happy with CGI, FastCGI? and AJP. Using pipes instead of multiplexing over a socket (like FastCGI? and AJP do) has a major advantage: you can use splice() in both the application and the web server for maximum efficiency. This reduces the overhead for having web applications out-of-process (as opposed to running web services inside the web server process, like Apache modules do). This allows privilege separation without sacrificing (much) performance. " https://github.com/CM4all/libwas/

---

"I am looking for a real FP language which means supporting composition, partial application, and first-class functions." -- https://lobste.rs/s/gm2ukd/fp_language_which_will_stand_test_time

---

https://jolynch.github.io/posts/distsys_shibboleths/

---

Animats 2 days ago

parent next [–]

But we did see performance improvements by restricting the language in certain ways that aid in static analysis, which allowed for more performant runtime code.

Well, yes. In Python, one thread can monkey-patch the code in another thread while running. That feature is seldom used. In CPython, the data structures are optimized for that. Underneath, everything is a dict. This kills most potential optimizations, or even hard-code generation.

It's possible to deal with that efficiently. PyPy? has a compiler, an interpreter, and something called the "backup interpreter", which apparently kicks in when the program being run starts doing weird stuff that requires doing everything in dynamic mode.

I proposed adding "freezing", immutable creation, to Python in 2010, as a way to make threads work without a global lock.[1] Guido didn't like it. Threads in Python still don't do much for performance.

[1] http://www.animats.com/papers/languages/pythonconcurrency.ht...

reply

chrisseaton 2 days ago

root parent next [–]

> This kills most potential optimizations, or even hard-code generation.

It doesn’t - this has been a basically solved problem since Self and deoptimisation were invented.

reply

Animats 2 days ago

unvote root parent next [–]

In theory, yes. In CPython, apparently not. In PyPy?, yes.[1] PyPy? has to do a lot of extra work to permit some unlikely events.

[1] https://carolchen.me/blog/jits-impls/

reply

---

"Something like Kotlin but with a borrow checker might be the ultimate in developer ergonomics for me. " -- [5]