Coding 2022-01-22

By Max Woerner Chase

Okay, I'm feeling better, but I mostly took things easy today. As such, rather than dive into anything really heavy, I'm going to dash off a quick summary/critique of the card prototype I was working on earlier.

Let's start with the basic goal of what it represents, and what I want to do differently, or additionally.

So, right now, it represents basic playing cards that can be put into a deck, and it handles rank-suit combinations, named cards, duplicates of cards, and duplicates of ranks and suits.

One thing that could be interesting to add is cards that have a name and metadata. I'd like to evaluate what's in there currently before adding that in.

So, the basic concepts are Card, Rank, and Suit. They all expose the capability to generate distinct duplicates of themselves, using the SuccCard, SuccRank, and SuccSuit classes, which are created using the Succ[T] mixin. Currently, this is mostly done with concrete classes. I tried to come up with a Protocol-based alternative as a thought experiment, but it ended up kind of awkward. The basic requirement is that the output of the succ method on Card, Rank, and Suit needs to match the type of the class in question. Since I'm not currently doing isinstance checks using these classes, I could get a less awkward set of static types, in exchange for some much stranger runtime types, by using a cast, but it's better to get a handle on what operations I expect to be able to do on these types first.

Anyway, right now the static behavior of the system is pretty locked-down via some type variables: TSucc is one of the three basic classes, and TPart is just Rank or Suit. These variables are invariant right now.

Then, there are some types that I haven't really named well. Base[T] is a mixin that essentially indicates "instances of this type are not a duplicate of another card", and Primitive[T] is an extension of Base[T] that indicates "instances of this type are not defined in terms of other Base types, but use only more primitive types". These types feel kind of awkward to use and reason about, and I think they need some work to make the code that uses them intuitive, which it really isn't right now. Right now, the only class that is Base[T] and not Primitive[T] is RankSuitCard, which is a cad defined by its rank and suit.

(Maybe make the base classes abstract and put the method implementations in the mixins, and inherit the marker classes into the mixins. This makes sense currently, because in leaf classes, Named and Primitive are one-to-one.)

Moving right along, then I've got a helper class, a protocol that provides generic slicing support for a container class, given an implementation of just integer-based indexing. The first implementation of this, because I decided to do type-based checking for empty sequences, is Empty[T]. Thinking about this in retrospect, it would simplify the code by a lot if I reworked it to allow most sequences to be empty. It wouldn't eliminate special cases, but various things would be simplified. The one concern is the weird number of ways something could be empty. It might make the most sense to simply remove some of the union types I'm using currently, but I'm not sure.

Anyway, next up are:

Then things get into specifically sequences of Cards with:

Finally, there are a bunch of helper functions after this, and I'm going to hold off on summarizing them for two reasons:

Though I will note that many of them probably could, and maybe should, be methods on the classes.

I'm going to cut things off here before I blink and it's ten minutes later, since I know that can happen to me this late.

Good night.