Skip to main content

📝 LLM Memory (Grant Slatton). I like mini taxonomy of approaches to memory. I think this shows us that we don't yet have a really good model for our own memory.


📝 The Price of Remission (David Armstrong / ProPublica).

When I started taking the drug, I’d look at the smooth, cylindrical capsule in my hand and consider the fact I was about to swallow something that costs about the same as a new iPhone. A month’s supply, which arrives in an ordinary, orange-tinged plastic bottle, is the same price as a new Nissan Versa.




📝 I don't like NumPy (Dynomight). This one is for everyone who has ever had to try axis=0 when doing an operation in numpy. The points about indexing and broadcasting hit home hard. I'm looking forward to the API proposals.


castfit 0.1.2 #

With suggestions from Gemini and ChatGPT

castfit 0.1.2 is available. I previously asked both Gemini 2.5 Pro and ChatGPT o3 to review my previous release and provide suggestions. While implementing those suggestions I ended up simplifying several things and making certain interfaces more consistent.

To install castfit:

# modern (recommended)
uv add castfit

# classic
python -m pip install castfit

Read more


Release Notes 0.1.2 - 2025-05-21T20:54:51Z #

Fixed

  • #4: get_args on raw types
  • #5: to_tuple with too-long input
  • #18: handling of types.UnionType
  • #25: delinted using pyrefly
  • #27: str to int conversion when the string has decimal places
  • #28: float to datetime conversion; added UTC timezone
  • #29: handling an untyped default value in a class

Changed

  • #14: TypeForm comment to clarify what we want
  • #19: set instance fields based on class metadata rather than tried to put all the data into the instance
  • #22: register converters based on both source and destination types rather than assuming that each function must convert everything to a specific destination type
  • #24: renamed casts_to to casts and added support for short-form (1 argument) cast function
  • #30: updated the public API to be more compact and consistent

Added

  • #2: support for nested types
  • #3: original cause of to_type error
  • #6: additional datetime formats
  • #7: custom casts to castfit (closes #7)
  • #11: more README examples
  • #12: more complete docstrings for the public API
  • #15: cache for fetching get_origin and get_args information
  • #16: DEFAULT_ENCODING constant for to_bytes and to_str
  • #17: alternatives to README
  • #20: infer types based on class field defaults
  • #31: more negative tests

Removed

  • #21: castfit on an instance instead of a type

Won't Fix

  • #8: Gemini suggested having an explicit caster for pathlib.Path
  • #9: Gemini suggested having an explicit recursive dataclass/class casting
  • #10: Gemini suggested optionally collecting errors instead of raising the first one encountered. It's a good idea, but not for now.
  • #13: Tried implementing a workaround for TypeGuard in older versions of python, but it didn't work.
  • #23: Started and rolled back is_callable because castfit can't currently do anything with a callable that is the wrong type.
  • #26: Rolled back having a checks parameter that overrides how types are checked.
  • #32: Tried fixing TypeForm to be the union of type[T] and _SpecialForm, but only pyright was able to handle it. mypy still can't handle it and ty isn't mature enough yet.

TIL: invariant, covariant, and contravariant type variables #

I avoided learning this for so long.

While working on castfit, I ended up going down a rabbit hole learning about what makes something a subtype of something else. I vaguely knew the terms "invariant", "covariant", and "contravariant", but have very carefully avoided ever learning what they meant.

If you look at the Wikipedia entry on Covariance and contravariance (computer science) you see explanations like:

Suppose A and B are types, and I<U> denotes application of a type constructor I with type argument U. Within the type system of a programming language, a typing rule for a type constructor I is:

  • covariant if it preserves the ordering of types (≤), which orders types from more specific to more generic: If A ≤ B, then I<A> ≤ I<B>;
  • contravariant if it reverses this ordering: If A ≤ B, then I<B> ≤ I<A>;
  • bivariant if both of these apply (i.e., if A ≤ B, then I<A> ≡ I<B>);[1]
  • variant if covariant, contravariant or bivariant;
  • invariant or nonvariant if not variant.

Uh, super helpful. The introductory description is a lot better than it used to be, but talking it over with ChatGPT was actually the best way for me to understand what these terms mean.

Simpler Definitions #

Suppose we have a type hierarchy like:

Labrador → Dog → Animal
Siamese  → Cat → Animal

The arrow indicates an "is a" relationship moving from more specific (narrow) to more general (broad): a Labrador is a type of Dog; a Dog is a type of Animal, etc.

The question of co/contra/invariance comes up when you want to know: Can I substitute one type for another?

It turns out that this depends on many things, but the situations are classified as:

  • Invariant: It must be this exact type. No broader or narrower substitutions allowed.
  • Covariant: The same or broader (more general) type is acceptable.
  • Contravariant: The same or narrower (more specific) type is acceptable.
  • Bivariant (haven't seen this in Python): The same, broader, or narrower type is acceptable.

This mostly comes into play when you start looking at containers of objects like list, set, tuple, dict, etc. Also Python has several exceptions to its own rules that are not immediately obvious.

General Python Rules #

  1. The contents of mutable containers are invariant. For example, if a function takes list[Animal], you cannot pass list[Dog] because that function might add a Cat (which is an Animal) there by violating the type safety of list[Dog] that was passed in.

An exception to this rule in Python is when "implicit conversion" occurs. A function that takes list[int] is ok to take list[bool] because there is an implicit conversion that happens. It seems that the numeric tower of bool → int → float → complex all happens implicitly. There are a few other implicit conversions that I'm still learning about.

  1. The contents of immutable containers (or read-only situations) are covariant. A function that takes Sequence[Animal] is ok to take Sequence[Dog] because Sequence is read-only (and Animal is broader than Dog).

Not really an exception, but fun fact: fixed-length tuple are covariant, while variable-length tuples are invariant.

PEP 483 has a nice example of contravariant types: Callable. While it is covariant in its return type (Callable[[], Dog] is a subtype of Callable[[], Animal]), it is contravariant in its arguments (Callable[[Animal], None] is a subtype of Callable[[Dog], None]).

This leads to a good rule of thumb:

The example with Callable shows how to make more precise type annotations for functions: choose the most general type for every argument, and the most specific type for the return value.

It looks like PEP 695 made it into Python 3.12, so maybe reasoning about this will get easier in the future especially because it automatically infers the variance of the type variables.


Trying pyrefly, Meta's type checker #

I guess I like trying type checkers.

Meta announced that they are open sourcing their type checker, pyrefly. It was easy enough to try:

uvx pyrefly check

On my tiny 640 lines of castfit (which passes pyright and mypy --strict) I got 74 errors. Going through some of these was instructive.

== is not supported between type[@_] and type[MyList]

This was in response to code like:

assert castfit.get_origin_type(MyList) == MyList

Good catch. That == should is.

Can't apply arguments to non-class, got LegacyImplicitTypeAlias[TypeForm, type[type[TypeVar(T, invariant)] | Any]] [bad-specialization]

Not sure what this means or how to handle it.

Argument Forall[T, (value: Any, kind: Unknown) -> bool] is not assignable to parameter with type (Any, Unknown) -> bool [bad-argument-type]

Imagine my confusion when I'm trying to figure out why this is not assignable. Must be that Unknown can't be assigned to Unknown. I might try this again in the future, but the error messages could definitely use some work.


🐦 Tom Johnson on AI-assisted Programming (Tom Johnson).

I just spent a week using Cursor and PyCharm's AI [Assistant], The goal was to take a CLI-based Python script and add a basic front end UI. By the end of the day yesterday, I felt like chewing aluminum foil would be more fun ...

Recommend reading the whole thread. There are ways I imagine asking the AI differently, but I completely get the pain being described.


📝 New paradigm for psychology just dropped (Adam Mastroianni). I queued up the The Mind in the Wheel by Slime Mold Time Mold, but haven't read it yet. Adam's point is that a paradigm has to explain which things do what and what rules they follow.

So let’s get clear: a paradigm is made out of units and rules. It says, “the part of the world I’m studying is made up of these entities, which can do these activities.”

He lays out three different kinds of research:

  1. Naive research: run experiments without knowing anything about the units and rules.

  2. Impressionistic research: make up words and study them.

The problem with this approach is that it gets you tangled up in things that don’t actually exist. What is “zest for life”? It is literally “how you respond to the Zest for Life Scale”. And what does the Zest for Life Scale measure? It measures...zest for life.

  1. Actual science research (my name): doing experiments where you can actually learn about the units and rules.

We’re not doing impressionistic research here, so we can’t just create control systems by fiat, the way you can create “zest for life” by creating a Zest for Life Scale. Instead, discovering the drives requires a new set of methodologies. You might start by noticing that people seem inexplicably driven to do some things (like play Candy Crush) or inexplicably not driven to do other things (like drink lemon juice when they’re suffering from scurvy, even though it would save their life). This could give you an inkling of what kind of drives exist. Then you could try to isolate one of those drives through methods like:

  • Prevention: If you stop someone from playing Candy Crush, what do they do instead?

  • Knockout: If you turn off the elements of Candy Crush one at a time—make it black and white, eliminate the scoring system, etc.—at what point do they no longer want to play?

  • Behavioral exhaustion (knockout in reverse): If you give people one component of Candy Crush at a time—maybe, categorizing things, earning points, seeing lots of colors, etc.—and let them do that as much as they want, do they still want to play Candy Crush afterward?

Adam also covers why neuroscience is the wrong level of abstraction for learning the things we need to learn.


📝 7 Phrases I use to make giving feedback easier for myself (Wes Kao). "I noticed" that "this is a great start" for giving feedback in a modern environment. "At the same time" going forward you could sound "even more" authentic because "I believe you were trying to avoid hard feelings, but it doesn't quite work because it uses canned phrases. I recommend trying having a conversation." "From what I've seen" canned phrases come across as fake. You're "already" giving feedback regularly which is fantastic. Now, I'd love for you to have more real conversations where you're clear about your expectations.

(Seriously, though, the ideas behind the phrases and examples are extremely realistic.)


📝 The Wrong Way to Motivate Your Kid (Russell Shaw / The Atlantic). The advice is to find an "island of competence" and help your kid build up from that. An implication is that we should help kids get competent at more things. Unfortunately, things are trending in the opposite direction.



📝 The magic of software; or, what makes a good engineer also makes a good engineering organization (Moxie Marlinspike; via Changelog News). Nice essay about how vision is translated into engineering. Also discusses abstraction layers and how organizations are structured.

However, there are two ways of interacting with an abstraction layer: as shorthand for an understanding of what it is doing on your behalf, or as a black box. I think engineers are most capable and most effective when abstraction layers serve as shorthand rather than a black box.






📝 AI as Normal Technology (Arvind Narayanan & Sayash Kapoor / Knight First Amendment Institute). This is the approach being discussed in Does AI Progress Have a Speed Limit?. While I don't have as much evidence to support my intuitive opinion, I do agree that modeling the world (especially in general) is particularly hard.


Trying ty, Astral's type checker #

It's not done, but very fast.

Astral has been delivering impressive improvements to the Python ecosystem with ruff, taking over rye, and releasing uv to manage projects and Python installs. And there have been musings about when they'd release a type checker.

Well the alpha release is now here (via Patrick Kage). You can try it out:

# requires rust to compile
curl https://sh.rustup.rs -sSf | sh
uv add git+https://github.com/astral-sh/ty
uv run ty check

Note: It takes a little bit of time to compile ty, but like all the other Astral tools it runs very quickly.

Also: Fair warning, lots of stuff doesn't work yet. For example, when I tried it on my castfit library, it choked on dict() (see #100).

How long before ty replaces mypy and pyright in my build process for all my projects? I'll probably wait for the official release, but I'll probably try it at least once on all my projects just to see what it produces.



📝 Project Xanadu: Even More Hindsight (Gwern Branwen). In Minority Report they have "futuristic" interfaces where you wave your arms around to manipulate 3D projections. In the real world your arms would get very tired very quickly. Project Xanadu tried to create a hypertext environment with lots of guarantees, but Gwern's realization about the UX is notable:

“Oh my god. It’s completely unreadable.”

The lines were confusing clutter, especially as they crisscrossed (a perennial problem in sidenotes layout, made far worse by the outlines). None of the ‘sidenotes’ were readable because the screen was so small. Even as you simply scrolled, for many possible settings, you were unable to read anything! How could a document UI where often you could read nothing have ever seemed like a good idea? The UI was just terrible—it could never have worked. Even on a large screen like my 4k monitor, I wouldn’t want that.

The lesson?

So, to me, Project Xanadu is a case-study in why designers must mock-up and prototype their designs before too much is invested in them. Xanadu wasn’t the victim of “Worse is Better”; it was just a solution in search of a problem.




📝 Product Purgatory: When they love it but still don’t buy (Jason Cohen). Main takeaways:

  1. People are surprised to discover that the prospective customer wouldn't even take your solution if it was free and had many other magical properties. This is an important signal.
  2. You need to solve a burning pain. Even if it's a great idea and otherwise important, if it's not urgent, it's probably not relevant.
  3. Towards the end of the post, there's a nice bit about "how you would find customers in this condition" that I thought was useful.

📝 You Sent the Message. But Did You Write It? (David Duncan). My favorite of these is GPTune:

Like Auto-Tune for writing. GPTune takes someone’s normal idea and smooths it into something that feels more articulate, structured, erudite - but less authentic.


🐦 Euromaidan Press on Gamified War (Euromaidan Press).

Ukrainian soldiers get reward points for destroyed Russian targets, which they can then exchange for new equipment. Just like in a game. - Politico.

The exchange happens through an online marketplace called Brave 1 and the troops need to provide video evidence to earn points.

♦️6 points for eliminating a Russian soldier
♦️40 points for destroying a tank
♦️50 points for taking out a mobile rocket system

For example, 43 points can get a powerful "Vampire" drone capable of carrying a 15-kg warhead.

What benefits?
♦️gets equipment directly to the most effective fighting units without bureaucratic delays
♦️motivates soldiers through friendly competition
♦️has already doubled the rate of Russian casualties since adjusting the point values.
♦️improves Ukraine's military intelligence by creating a verified database of Russian losses

Incentives matter.




View all posts