11.8 Generic Arrow Functions in TSX Files

Right, let’s talk about generic arrow functions in the wild west of TSX files. You’re going to write a lot of these, especially for React components and helper functions. And this is where TypeScript’s two syntaxes—one for the language and one for its type system—decide to have a bit of a scuffle. It’s not hard, but the first time your editor lights up with a red squiggly line, you’ll think you’ve broken the very fabric of reality. You haven’t. The designers just made a… questionable choice.

11.7 Multiple Type Parameters and Their Relationships

Right, so you’ve mastered the single-type-parameter function. You feel pretty good about identity<T>(thing: T): T. You can pass a string, get a string. Pass a number, get a number. It’s elegant. It’s simple. It’s also, frankly, a bit boring. The real world is a messy place full of relationships, and our types need to reflect that. This is where we graduate from solitaire to playing with multiple cards at once.

11.6 Default Type Parameters

Right, default type parameters. This is where we stop merely using generics and start designing with them. It’s the feature that lets you build truly flexible and ergonomic APIs. The core idea is brilliantly simple: you can specify a default type for a generic parameter, just like you can specify a default value for a function parameter. Think of it as a contingency plan for your types. If the user of your function or class is kind enough to specify a type, you’ll use that. If they don’t, and just use empty angle brackets <> or, even better, no brackets at all, you gracefully fall back to the default. It makes your generics feel less demanding and more helpful.

11.5 Type Parameter Constraints with extends

Right, so you’ve got generics. They’re fantastic. You slap a T on a function and suddenly it works for anything. Freedom! Power! Until you try to T.toLowerCase() and the whole thing explodes because, newsflash, not every type T has a toLowerCase method. TypeScript, in its infinite wisdom, doesn’t assume your T is a string; it assumes your T is an enigma wrapped in a mystery. This is where we stop being polite and start getting real with extends.

11.4 Generic Classes

Right, so you’ve got generic functions under your belt. They’re fantastic for writing one piece of logic that works across different types. But what about when you want to bake that same flexibility into a blueprint? That’s where generic classes come in. Think of them as a cookie cutter where you get to decide what kind of dough you’re using at the very last second. A generic class is simply a class that has a type parameter (or several). You define this parameter list in angle brackets < > right after the class name. This parameter is a placeholder for the actual type that a user of your class will provide.

11.3 Generic Interfaces and Type Aliases

Alright, let’s talk about giving your interfaces and type aliases a serious upgrade. You’ve seen how generics can supercharge functions and classes, making them flexible and type-safe. It would be downright rude if we didn’t extend that same power to the contracts we define with interfaces and the shortcuts we create with type aliases. This is where you stop writing one-off, brittle type definitions and start building a truly scalable type system.

11.2 Generic Functions: Syntax and Type Inference

Right, let’s talk about generic functions. You’ve probably felt the pain this solves: writing a function that works perfectly for a string, and then you need the exact same logic for a number. Your first instinct might be to reach for any and call it a day. Don’t. You’re better than that. any is a declaration of surrender to the type system; it’s you saying, “I give up, just let me compile.” Generics are how you win instead.

11.1 The Problem Generics Solve: Reusable Typed Code

Look, you’ve written this function. It’s a good function. It takes a string and returns a string. You’re proud of it. Then your PM walks over and says, “Hey, that’s great. Now can it also work with numbers?” Your heart sinks. You don’t want to write concatNumbers and concatStrings. You’re not an animal. You could use any, but then you’d just be throwing your type safety out the window and inviting bugs to a free-for-all in your codebase. That’s where generics come in—they’re your way of telling TypeScript, “I don’t know what type this will be yet, but whatever it is, it’s going to be consistent, dammit.”

31.8 Constraints Defined in golang.org/x/exp/constraints

Alright, let’s talk about the golang.org/x/exp/constraints package. This is where the Go team put all the shiny, useful constraint toys for us to play with when generics landed. Think of it as the official, but slightly experimental, toolbox for describing what kinds of types your generic functions can accept. First, a crucial reality check: this package lives in exp, which is Go-speak for “experimental.” This means the Go team reserves the right to change their minds, break your code, and move things into the main standard library whenever they feel like it. It’s incredibly useful, but you don’t want to bet your company’s core infrastructure on it without a clear exit strategy. Most of these constraints are so fundamental that they’ll likely be stabilized somewhere, but just be aware you’re living on the edge.

31.7 Generic Data Structures: Stack, Queue, and Set

Alright, let’s get our hands dirty. Generic data structures are where this whole “type parameter” thing stops being an academic exercise and starts paying rent. You’ve probably hand-rolled a Stack of ints or a Queue of SomeStruct a dozen times. It’s boring, it’s error-prone, and it’s exactly the kind of mind-numbing repetition generics are meant to erase. We’re going to build three classics: a Stack, a Queue, and a Set. But we’re going to build them once, for any type (well, any comparable type, in the Set’s case). You’ll see where the power lies, and also where Go’s designers, in their infinite wisdom, decided to put some frustrating little speed bumps.

31.6 Instantiation: How the Compiler Generates Code

Alright, let’s pull back the curtain on the real magic: instantiation. This is the moment where our generic function blueprint gets stamped out into a real, concrete, type-specific function that your CPU can actually execute. The compiler isn’t generating code at runtime; it’s doing all the heavy lifting right there while it compiles your program. Think of it less like a factory and more like a master baker who prepares all the ingredients before the party starts.

31.5 Type Sets: Union Constraints with ~

Alright, let’s talk about the tilde (~). This little squiggle is one of the most elegant and simultaneously confusing additions to Go’s type system. It exists to solve a very specific, very real problem that emerges the moment you start writing generic functions with constraints like int. Imagine you write this perfectly reasonable function: func PrintInts[T int](values []T) { for _, v := range values { fmt.Println(v) } } You try to use it, and immediately hit a wall.

31.4 comparable: The Constraint for == and !=

Alright, let’s talk about comparable. You’ve probably been banging your head against the wall, wondering why you can’t just write a generic function to check if two things are equal. You try this: func Equal[T any](a, b T) bool { return a == b } And the compiler slaps you down immediately. “Invalid operation: a == b (operator == not defined on T)”. Rude. The reason is simple and, honestly, sensible: Go is a brutally pragmatic language. The == operator doesn’t work for every single type you could possibly shove into a type parameter. What does == mean for a map? Or a function? Or a slice? It’s undefined, so Go just says “nope” rather than letting you stumble into a runtime panic.

31.3 Constraints: Limiting What T Can Be

Right, so you’ve got your shiny new type parameter T and you’re ready to write some beautifully generic code. You try to write a function that sums a slice of numbers, func Sum[T any](s []T) T, and immediately you hit a wall. You can’t use the + operator. Why? Because any means literally any type. You could be summing []int, []string, or []http.Request—and Go’s compiler, being the stubbornly pragmatic friend that it is, refuses to let you add two http.Requests together. This is the problem constraints are designed to solve.

31.2 Type Parameters: func F[T any](x T) T

Alright, let’s get our hands dirty. You’ve probably written a dozen functions that do the exact same thing, just for different types. You copy, paste, change int to string, and die a little inside. We’ve all been there. Generics in Go, specifically the T any part you see here, are our long-awaited pardon from that particular flavor of tedium. The syntax func F[T any](x T) T might look a bit alien at first, but break it down. Before the function name F, we declare our type parameters in square brackets. [T any] is the simplest form: we’re saying “For this function, we’re going to use a type we’ll call T. The any part is its constraint, meaning T can literally be… any type.” It’s the equivalent of a wildcard. Inside the function body, x is of that type T, and the function also returns a value of type T.

31.1 The Problem Before Generics: Code Duplication and interface{}

Right, let’s talk about the bad old days. You know, the ones we’re all pretending to forget now that we have generics. Before Go 1.18, if you wanted to write a function that could handle multiple types, you were faced with a classic engineering trade-off: do the wrong thing fast, or do the wrong thing slowly. Your two main options were code duplication or the infamous interface{}. Let’s say you wanted a simple function to find the maximum value in a slice. A simple task, right? Not if you needed it for int, float64, and string slices. Your first, most visceral reaction was to just write the same function three times.

10.7 ALTER TABLE: Adding, Dropping, and Modifying Columns

Right, so you’ve built your table. It’s a thing of beauty. It holds your data with the tender embrace of a perfectly normalized schema. But time passes, requirements change, and that perfect table now looks… less perfect. Maybe marketing needs to track a new user preference, or you need to purge a column that was a bad idea from the start (we’ve all been there). This is where ALTER TABLE comes in—your digital wrench for performing table surgery. It’s powerful, but like any surgery, you don’t want to do it blindfolded.

10.6 DEFAULT Values: Literals, Functions, and Sequences

Right, let’s talk about DEFAULT values. This is where you stop treating your database like a passive spreadsheet and start making it do the work for you. The DEFAULT clause is your way of telling the database: “Look, if I don’t bother to specify a value for this column when I insert a row, don’t just yell at me with a NOT NULL error. Use this instead.” It’s the single best way to keep your application code from being cluttered with boilerplate logic for setting trivial, predictable values.

10.5 CHECK Constraints: Expressing Business Rules

Right, so you’ve got your columns defined and your primary key set. Good. But a column’s data type is a pretty blunt instrument. INTEGER can hold the value -999999 just as happily as 42. For your actual data, that’s probably nonsense. This is where CHECK constraints come in. They’re your first and best line of defense against data that, while technically the right type, is complete garbage from a business logic perspective. Think of them as bouncers for your rows; if the data doesn’t meet the criteria, it doesn’t get in. Period.

10.4 PRIMARY KEY: Identity, Implicit Unique Index, and NOT NULL

Right, let’s talk about the PRIMARY KEY. You’ve probably heard it’s important. That’s an understatement. It’s the single most important column (or set of columns) in your entire table. It’s the one thing that absolutely, positively must be unique for every single row. Think of it as your table’s social security number, its fingerprint, its “this one is mine” identifier. And because it’s so important, SQL gives it a whole bundle of superpowers automatically. Let’s break down what you’re really getting when you declare one.

10.3 UNIQUE Constraints: Single-Column and Multi-Column

Right, so you’ve told your data it has to be there (NOT NULL) and what it has to look like (CHECK). Now let’s talk about telling it to be, well, unique. A UNIQUE constraint is how you tell the database, “Look, I don’t care what value you put in this column, but it had darn well better be different from every other value in this column for all the other rows.” It’s the database’s way of enforcing that one-of-a-kind snowflake status, but for your data.

10.2 NOT NULL: Enforcing Mandatory Values

Right, let’s talk about NOT NULL. It’s the simplest constraint in the book, but also one of the most important. It doesn’t add fancy logic or relationships; it just does one job: it stops NULL from getting into a column. And that, my friend, is 90% of data sanity. Think of NULL as the database’s way of saying “I have no idea.” It’s not zero, it’s not an empty string, it’s not false. It’s the absence of a value. This is useful sometimes (like when you genuinely don’t have the data), but letting NULL run rampant through your tables is like inviting a poltergeist into your application logic. Your code will spend half its time checking IS NOT NULL before it can do anything useful. NOT NULL is your first and best line of defense against this chaos. It forces you, the human, to make a decision: “What must I have for this record to be valid?”

10.1 Column Definitions: Names, Types, and Defaults

Alright, let’s get our hands dirty and build a table. Think of a CREATE TABLE statement as the architectural blueprint for your data. It’s where you lay down the law: what data you’ll store, what it’s allowed to look like, and what happens when someone tries to break the rules. Screw this up, and you’re building on a foundation of digital quicksand. We’ll start with the core of any table: the column definitions. This is where you name your data, give it a type, and tell it how to behave when it’s feeling indecisive.

— joke —

...