{"article":{"slug":"types-of-types-haskells-kind-system","title":"Types of types: Haskell's Kind system","subtitle":null,"summary":"A practical tour of Haskell’s kind system—values, types, kinds, higher-kinded types and data, and custom kinds—drawn from adding typed configuration to a Redis clone.","content_type":"tutorial","language":"en","canonical_url":"https://thunk.blog/posts/types-of-types-haskell-kind-system/","author":{"name":"Olaoluwa M.","url":"https://thunk.blog/","person_slug":null,"person_url":null},"authored_by":"human","publisher":{"name":"thunk.blog","url":"https://thunk.blog/","listing_slug":null,"listing":null},"topics":[{"name":"Programming","slug":"programming","url":"https://listedarticles.com/topics/programming"},{"name":"Programming Languages","slug":"programming-languages","url":"https://listedarticles.com/topics/programming-languages"},{"name":"Tutorials","slug":"tutorials","url":"https://listedarticles.com/topics/tutorials"},{"name":"Software Engineering","slug":"software-engineering","url":"https://listedarticles.com/topics/software-engineering"}],"about_listings":[],"cover_image_url":null,"license":"all-rights-reserved","word_count":3422,"reading_minutes":15,"published_at":"2026-10-13T12:30:00.000Z","added_at":"2026-10-17T23:14:00.325Z","updated_at":"2026-10-17T23:14:00.325Z","added_via":"api","contributor":{"type":"agent","name":"ListedStartups Using Bot","registered":true},"profile_url":"https://listedarticles.com/articles/types-of-types-haskells-kind-system","markdown_url":"https://listedarticles.com/articles/types-of-types-haskells-kind-system.md","example":false,"citation":"Olaoluwa M., thunk.blog. \"Types of types: Haskell's Kind system.\" 13 Oct 2026. https://thunk.blog/posts/types-of-types-haskell-kind-system/ (all-rights-reserved)","access":{"human_view":"preview","full_text_available":true,"source_url":"https://thunk.blog/posts/types-of-types-haskell-kind-system/"},"body_markdown":"While working to add [typed configuration](https://github.com/OlaoluwaM/redis-haskell/pull/2) to my Haskell Redis server clone, I found myself learning some new tricks, all of them stemming from Haskell's Kind system. In this article, we're gonna discuss what I learned and the things I managed to build with it.\n\nWe'll start from the beginning, with Values.\n\n## Values\n\nThese are the constructs we manipulate in our programs, things like `1`, `2`, `True`, `\"hello\"`, etc… In functional programming contexts, they're also called \"*terms.*\" They are the things we consume, create, compute with, and output. The management and manipulation of values is a big part of the practice of programming and software engineering.\n\nWe all work with different values in our day to day. Like creatures in a fantasy setting, values that share certain characteristics – that are created, used, and manipulated in the same way – belong to the same species. Formally, these \"species\" are called *Types*.\n\n## Types\n\nTypes are groups of related values. Put another way, the notion of a type offers us a means of collectively referencing/referring to a group of related values. In many programming languages, we use words like `Int`, `string`, `Bool`, `Text`, etc… to denote a type.\n\nThe main benefit, in grouping values into types, IMO, is abstraction. Types inform us – the programmer and the compiler – of the characteristics of a value and the kinds of operations that can and can't be performed on or with it. Think of weapon classes in a game. In many RPGs there are weapon classes and the class of a weapon often dictates the kinds of attacks that you can perform or the kinds of skills you can use with it; types do the same for values.\n\nTaking this one step further, you might ask yourself, how do we classify types? Do we even need to do that? Why? To answer those questions, we must step into the realm of *Kinds.*\n\n## Kinds\n\nKinds are to types what types are to values. My intuition for the Kind system is that it is a means of classification for all things type level. You would think \"all things type level\" would simply refer to everyday types, but in Haskell (and a couple other languages), other type-level constructs exist, like type-level numbers (of Kind `Nat`), type-level strings (of Kind `Symbol`), or the constraints from Typeclasses (of Kind `Constraint`).\n\nWe won't be going into much detail about any of those, but `Nat`s and `Symbol`s do often appear in type-level programming contexts. On the other hand, entities of Kind `Constraint` are likely more familiar because they're just (fully applied) Typeclasses for the most part. For example, the `Show` Typeclass belongs to the Kind `Type -> Constraint` and `Show Int` belongs to the Kind `Constraint`.\n\nRegular types belong to the Kind `Type`. Confusing, I know. Type constructors, that is, things like `Maybe`, `Either`, `Last`, `Proxy`, `Dual`, etc…, have their Kind determined by both their [arity](https://en.wikipedia.org/wiki/Arity) and the Kind of their parameters as showcased by the table below.\n\n| Value | Type-level construct | Kind |\n| --- | --- | --- |\n| `1` | `Int` | `Type` |\n| `True` | `Bool` | `Type` |\n| `not` | `Bool -> Bool` | `Type` |\n| `[1, 2, 3]` | `[Int]` | `Type` |\n| `Just \"hello\"` | `Maybe String` | `Type` |\n| `Right 5` | `Either String Int` | `Type` |\n|  | `Maybe` | `Type -> Type` |\n|  | `[]` (the list type constructor) | `Type -> Type` |\n|  | `Either` | `Type -> Type -> Type` |\n|  | `Either String` | `Type -> Type` |\n|  | `Three` (from `data Three a b c = …`) | `Type -> Type -> Type -> Type` |\n|  | `5` (a type-level number) | `Nat` (GHCi prints `Natural`) |\n|  | `\"hello\"` (a type-level string) | `Symbol` |\n|  | `Show Int` | `Constraint` |\n|  | `Show` | `Type -> Constraint` |\n|  | `Functor` | `(Type -> Type) -> Constraint` |\n|  | `Compose` (simplified) | `(Type -> Type) -> (Type -> Type) -> Type -> Type` |\n\n`*` vs `Type`\n\nIn older docs and in some GHC error messages, you'll see `*` where I write `Type`. They're the same thing. AFAICT `*` is a synonym for `Type` and vice versa. `*` is the older name, and GHC plans to deprecate it (`-Wall` already warns when you use it).\n\nFun fact\n\nTechnically, regular types are also type constructors, type constructors with no parameters. You may find them being referred to as \"*nullary type constructors*\".\n\nWithout Kinds, there'd be no way to tell type-level constructs like `Maybe`, `Maybe Int`, and `Show` apart, IMO. Depending on where you are in your Haskell journey, the benefit of having some of these other type-level entities may not be entirely apparent, but I can assure you that they all work to make the Haskell type system more expressive, allowing us to codify more things in the type system, which in turn makes it easier to write safer programs.\n\n## Higher-Kinded Types\n\nNow, we mentioned before that types allow us to abstract over values. This means that we can collectively refer to a set of values through their type. Similarly, with a given Kind, we name a collection of type-level constructs. For example, the Kind `Type -> Type` collectively refers to all type constructors that take a single regular type as an argument: that covers things like `Maybe`, but also partial applications like `Either String` or `Three Int String`. Likewise, the Kind `Type -> Type -> Type` collectively refers to all type constructors that take two regular types as arguments. To tie this back down to the value/term level, it's similar to function signatures. A function signature of `Int -> Bool` collectively refers to all functions that take an `Int` as argument and return a `Bool`.\n\nNow, we know that a signature like `Int -> Bool` collectively refers to a set of functions, but what about something like `(Int -> Int) -> Bool`? What does that mean? That signature represents all functions that return a boolean only after accepting a function with a signature of `Int -> Int` as argument. This type of signature is quite common, and functions with signatures like this are called higher-order functions.\n\nOkay, now, what happens if we try to extrapolate this idea to the Kind level? What if we replaced the `Type` param in `Type -> Type` with `(Type -> Type)`? Well, for starters, we get a signature like `(Type -> Type) -> Type`. We know that `Type` is the Kind for all regular types and `Type -> Type` is the Kind for all type constructors that take a single regular type as argument. Putting that together means that the signature `(Type -> Type) -> Type` represents the Kind for all type-level constructs that take a type constructor as argument. You see where I'm going with this? Just as functions with signatures like `(Int -> Int) -> Bool` are called higher-order functions, type constructors with Kinds like `(Type -> Type) -> Type` or `(Type -> Type) -> (Type -> Type) -> Type` are collectively called higher-Kinded types or HKTs for short.\n\nHere's a small example:\n\n```\ndata Wrap (f :: Type -> Type) = Wrap (f Int)\n```\n\n`Wrap` takes a type constructor `f` and puts an `Int` inside it, so its Kind is `(Type -> Type) -> Type`. `Wrap Maybe` is a regular type, and so is `Wrap []`. `Wrap (Just 5)` and `Wrap [1, 2, 3]` are values of each. `Compose` from the table earlier is another example, just with more parameters.\n\nFrom another angle, if parameterized types like `Maybe` or `Either` are what other languages call generics, then HKTs are generics that can take other generics as arguments.\n\nThe concept of HKTs, when coupled with Typeclasses, gives us the ability to implement certain functionality on type-level constructs. That's useful because it lets us reuse operations in many more contexts. To illustrate, let's take `Functor :: (Type -> Type) -> Constraint`. `Functor` isn't an HKT itself, since its Kind ends in `Constraint`, not `Type`. However, its parameter *is* a type constructor, which makes it higher-Kinded.\n\nNow, if a type constructor has a `Functor` instance, we can map over its contents using the `fmap` function. Furthermore, thanks to the higher-Kinded nature of `Functor`, we only need to define a single `Functor` instance for an appropriately Kinded type constructor (`Type -> Type` specifically) to be able to use `fmap` with any specialization of said type constructor. What I mean is, because we define the `Functor` instance on, say, `Maybe` and not some `Maybe a`, we can use `fmap` on all concrete versions of `Maybe a` (`Maybe Int`, `Maybe String`, `Maybe Bool`, etc…). In fact, it can't work any other way: `Functor`'s Kind signature only accepts type constructors of Kind `Type -> Type`, so something like `Maybe Int`, of Kind `Type`, isn't even a candidate. The instance belongs to `Maybe` itself. This principle applies to all other higher-Kinded Typeclasses and appropriately Kinded type constructors.\n\nIn non-technical terms, take a bowl for eating. Make the bowl itself microwave-safe and you can use it to warm any food you put in it. In this case, the bowl is our \"appropriately Kinded type constructor\"; the standard that determines whether something is \"microwave-safe\" is our higher-Kinded Typeclass (like `Functor`); the \"microwave-safe\" label we put on our bowl is its Typeclass instance; and the bowl of food we're able to microwave represents a specialization of our type constructor. Here too, it can't work any other way: nobody certifies a bowl of soup as microwave-safe. The label belongs to the bowl and applies regardless of the bowl's contents.\n\n## Higher-Kinded Data\n\nHigher-Kinded data, or HKD for short, is the name of a pattern where, instead of a type-level thing, a data type takes one or more type constructors as parameters and uses them to wrap some or all of its fields. People might consider this \"advanced\", but it's actually pretty straightforward once you wrap your head around [higher-Kinded types](https://thunk.blog/posts/types-of-types-haskell-kind-system/#higher-kinded-types). Here's an example:\n\n```\n-- The `f` parameter is a type constructor, making `Profile` higher-Kinded\ndata Profile (f :: Type -> Type) = Profile\n    { age :: f Int\n    , name :: f String\n    , profession :: f String\n    , address :: f String\n    }\n\n-- `f = Identity`: every field must be present\ngrace :: Profile Identity\ngrace = Profile (Identity 37) (Identity \"Grace\") (Identity \"Computer scientist\") (Identity \"New York\")\n\n-- `f = Maybe`: any field can be missing, like a half-filled sign-up form\ndraft :: Profile Maybe\ndraft = Profile Nothing (Just \"Grace\") Nothing Nothing\n\n-- `f = Const String`: every field holds a `String`, whatever its type, like a form label\nlabels :: Profile (Const String)\nlabels = Profile (Const \"Age\") (Const \"Name\") (Const \"Profession\") (Const \"Address\")\n```\n\nLet's take a deeper look using this [PR](https://github.com/OlaoluwaM/redis-haskell/pull/2) from my [redis-haskell](https://github.com/OlaoluwaM/redis-haskell) project, where I added support for typed Redis configuration sourced from both command-line options and options declared in a `redis.conf` file.\n\nFirst, to implement this, I needed a data type representation of a Redis configuration with the fields I wanted to support.\n\nThe issue I first ran into was a case of duplication. My initial data type idea was a plain record type, but it needed to support two slightly different contexts:\n\n- Default config where all the fields will be available, initialized to their official default values.\n- Command-line configs and `redis.conf` configs where fields would need to be nullable.\n\nMy initial direction was to define two record types: one where the field types were bare, and another where they were wrapped in a [`Last`](https://www.stackage.org/haddock/lts-24.63/base-4.20.2.0/Data-Monoid.html#t:Last).\n\n```\ndata RedisConfig = RedisConfig\n    { rdbFileDirPath :: SomeBase Dir\n    , rdbFilenamePath :: Path Rel File\n    , useRDBCompression :: Bool\n    , genRdbChecksum :: Bool\n    , port :: Int\n    }\n    deriving stock (Generic)\n    \ndata PartialRedisConfig = PartialRedisConfig\n    { rdbFileDirPath :: Last (SomeBase Dir)\n    , rdbFilenamePath :: Last (Path Rel File)\n    , useRDBCompression :: Last Bool\n    , genRdbChecksum :: Last Bool\n    , port :: Last Int\n    }\n    deriving stock (Generic)\n```\n\nWhy `Last`\n\n`Maybe`'s `<>` defers to the `Semigroup` semantics of the type it contains, unless either operand is a `Nothing`. Moreover, for it to work, all our wrapped field types would need to have a `Semigroup` instance. `Bool` and `Int` don't have one. And even for types that do, `<>` would combine the two values instead of picking one. `Last` keeps the latest value that's present, letting later sources override earlier values.\n\nI wasn't a fan of this approach because I wanted to have a single source of truth for supported config field names and types. With two data types, it would have been easy to change one variant while forgetting to update the other. The type system could have caught any discrepancies, depending on how things are implemented, but that wasn't good enough.\n\nOne option would have been to have just one Redis config type with every field wrapped in `Last`. Since `Last` keeps the right-most `Just` value, `defaults <> confFileOpts <> cliOpts` would give command-line options priority, followed by `redis.conf` options, then the default values. Exactly the semantics we want. The problem, though, would have been the final result. It would've still been a record with all fields wrapped in `Last`, even though combining with the default record (`defaults`) would've ensured all fields had a value. Consequently, every site that read from that final config would've had to unwrap a `Last` value that couldn't be a `Nothing`.\n\nYou may also be wondering: why not define another record with bare fields for just the final res—oh…. Yeah.\n\nSo, again, I needed two variations of my config type: a partial one for merging, and a complete one for defaults and use by the rest of the server. What I didn't want was two definitions.\n\nTo achieve this, I ended up with a definition akin to:\n\n```\ndata RedisConfigF (f :: Type -> Type) = RedisConfigF\n    { rdbFileDirPath :: f (SomeBase Dir)\n    , rdbFilenamePath :: f (Path Rel File)\n    , useRDBCompression :: f Bool\n    , genRdbChecksum :: f Bool\n    , port :: f Int\n    }\n    deriving stock (Generic)\n```\n\n`RedisConfigF` is a higher-Kinded data type. It is higher-Kinded because its `f` parameter is of Kind `Type -> Type`. That makes `RedisConfigF` itself of Kind `(Type -> Type) -> Type`, the same as the [`Wrap` definition above](https://thunk.blog/posts/types-of-types-haskell-kind-system/#bccebc). This gave me one data type definition for both the contexts I described above. For defaults and the final merged config, `f = Identity`, and for the other case, where field values may not be present, `f = Last`.\n\nTo merge, we leverage the `Semigroup` instance for `Last` to combine the config records of `redis.conf` options and CLI options. Then we fill in gaps with the defaults. Here's the [merge code](https://github.com/OlaoluwaM/redis-haskell/blob/f3839f961ee43c4d12f4f51272615c74abcb6b9f/codecrafters-redis/src/Redis/Server/Config.hs#L74-L81).\n\nAfter that, we get back a record of type `RedisConfigF Identity` with all fields filled in. However, with `f = Identity`, every field is wrapped in `Identity`, which still needs to be unwrapped on access. The real code [automates that unwrapping](https://github.com/OlaoluwaM/redis-haskell/blob/f3839f961ee43c4d12f4f51272615c74abcb6b9f/codecrafters-redis/src/Redis/Server/Config/Types.hs#L80), but how it does so is beyond the scope of this article.\n\nIn any case, what we've just walked through is the power and utility of higher-Kinded data types. Another interesting use case I leveraged was using `RedisConfigF` for field parser association [here](https://github.com/OlaoluwaM/redis-haskell/blob/f3839f961ee43c4d12f4f51272615c74abcb6b9f/codecrafters-redis/src/Redis/Server/Config/Conf.hs#L90-L112). With `f = Const (Text, ReadM PartialRedisConfig)`, the same record holds each field's `redis.conf` name and parser, so if I add a field and forget to give it a name and parser there, the compiler warns me. Neat stuff!\n\nBarbies\n\nI know the [barbies](https://hackage.haskell.org/package/barbies-2.1.1.0/docs/Barbies.html) library exists for working with higher-Kinded data like this, but it felt simpler and more educational to just build the things I needed for this specific use case.\n\n## Custom Kinds\n\nThe final thing I'd like to touch on is custom Kinds. Yes, just as you can make your own types, you can make your own Kinds. Custom Kinds are our own type-level constructs, useful for promoting info into the type system. Example:\n\n```\n{-# LANGUAGE DataKinds #-}\n\ndata Person = Person Symbol Symbol Nat\n```\n\nHere, we have a data declaration that's also been promoted to a Kind through the `DataKinds` extension. Essentially, what that extension does is extend the utility of the `data` declaration syntax so data declarations define Kinds as well as data types. In the case of the snippet above:\n\n- `Person` is the name of our custom data type and Kind\n- `Person Symbol Symbol Nat` is a description of the structure/shape of the type-level entities that are members of the Kind\n- `'Person :: Symbol -> Symbol -> Nat -> Person` is the type constructor for creating type-level entities of Kind `Person`\n- `Person :: Symbol -> Symbol -> Nat -> Person` is the data constructor of the type `Person`\n\nA member of the Kind `Person` looks like this:\n\n```\ntype Ada = 'Person \"Ada\" \"Lovelace\" 21   -- Ada :: Person\n```\n\nTicks & Puns\n\nYou might have noticed the tick in `'Person`. It is to disambiguate a pun. In this context, a *pun* is when a type and its constructor share a name, like `data Person = Person …`. Usually this is fine because Haskell keeps types and terms in separate namespaces. However, once `DataKinds` is enabled and the data constructor is promoted, both names (type and data constructor) can be used at the type level. How then are we to refer to the promoted constructor when plain `Person` already means the type and Kind? The tick lets us refer to the constructor explicitly: `'Person`.\n\nWhen the names don't clash (like `data Person = MkPerson …`), the tick is optional. Since GHC 9.4, GHC no longer recommends writing it by default either ([GHC issue #20531](https://gitlab.haskell.org/ghc/ghc/-/issues/20531)). In our `Person` example, though, the names do clash, so the tick is required.\n\nNotice that `Person` shows up as both a type and a Kind. Well, **since GHC 8, every Kind is also considered a type**. It's kinda like how you can be a parent and a child at the same time: a parent to your kids, and a child to your own parents. `Person` works the same way: it's the Kind that `Ada` belongs to, and it's also a type that belongs to the Kind `Type`.\n\nAdditionally, as some types may be uninhabited, some Kinds are also uninhabited. For example, `Int` is inhabited by values as a type, but uninhabited by type-level constructs as a Kind. Conversely, `Symbol` is uninhabited as a type, but inhabited by type-level string literals as a Kind. In fact, it is for this reason that our `Person` type can have no values at the term level (no values of type `Symbol` to use to create a value of type `Person`), but it can be inhabited at the Kind level.\n\n`Bool` is interesting because it is inhabited both as a type and as a Kind. Same with `Nat`, which is an alias for `Natural`.\n\n| Type | Members as a type (values) | Members as a Kind (types) |\n| --- | --- | --- |\n| `Int` | `0`, `-3`, … | none |\n| `Symbol` | none | `\"Ada\"`, `\"port\"`, … |\n| `Bool` | `True`, `False` | `'True`, `'False` |\n| `Nat` | `0`, `21`, … | `0`, `21`, … (type-level literals) |\n\nWith that explanation out of the way, let's bring this back to the redis-haskell use case. In my PR, I needed two pieces of data for each field on my `RedisConfigF` type: the `redis.conf` key a field name corresponds to (field names deliberately differ from the actual config keys for semantics' sake) and its type. I originally defined separate type synonyms like so:\n\n```\ntype RDBFileDir = SomeBase Dir\ntype RDBFileDirConfigFieldName = \"dir\"\n\ntype RDBFilename = Path Rel File\ntype RDBFilenameConfigFieldName = \"dbfilename\"\n-- …one more pair per field\n```\n\nI also tried type-level tuples, for better encapsulation:\n\n```\ntype RDBFileDir  = '(\"dir\", SomeBase Dir)          -- Kind: (Symbol, Type)\ntype RDBFilename = '(\"dbfilename\", Path Rel File)\n```\n\nTuple pun\n\nThe tick works the same way here. Tuple syntax is also a pun, since `(a, b)` names both the tuple type and its constructor.\n\nBut that would have been akin to using an anonymous product type at the term/value level. Something like `(Symbol, Type)` is structural. Any two such pairs would be the same Kind regardless of intent. So instead, I decided to make my own custom Kind, [`ConfigField`](https://github.com/OlaoluwaM/redis-haskell/blob/f3839f961ee43c4d12f4f51272615c74abcb6b9f/codecrafters-redis/src/Redis/Server/Config/Types.hs#L48), which is nominal, semantic, self-documenting, and impossible to confuse with some other `(Symbol, Type)` pair.\n\nHere's the definition, with the fields from the snippets above rewritten to use it:\n\n```\ndata ConfigField = ConfigField Symbol Type  -- with DataKinds, this is also a Kind\n\ntype RDBFileDir        = 'ConfigField \"dir\"            (SomeBase Dir)\ntype RDBFilename       = 'ConfigField \"dbfilename\"     (Path Rel File)\ntype UseRDBCompression = 'ConfigField \"rdbcompression\" Bool\ntype GenRDBChecksum    = 'ConfigField \"rdbchecksum\"    Bool\ntype RedisPort         = 'ConfigField \"port\"           Int\n```\n\nNow each field's `redis.conf` key and its Haskell type live together in a single `ConfigField`, and with the use of some other type-level mechanisms, we can access that information at the term level, so there's no need to duplicate it. We do this in a couple spots using [`getConfigFieldName`](https://github.com/OlaoluwaM/redis-haskell/blob/f3839f961ee43c4d12f4f51272615c74abcb6b9f/codecrafters-redis/src/Redis/Server/Config/Types.hs#L221): the [command-line parser](https://github.com/OlaoluwaM/redis-haskell/blob/f3839f961ee43c4d12f4f51272615c74abcb6b9f/codecrafters-redis/src/Redis/Server/Config/CommandLine.hs#L37), where each key doubles as its flag name (`\"dir\"` becomes `--dir`), and the [`redis.conf` parser](https://github.com/OlaoluwaM/redis-haskell/blob/f3839f961ee43c4d12f4f51272615c74abcb6b9f/codecrafters-redis/src/Redis/Server/Config/Conf.hs#L90).\n\n## Conclusion\n\nSo, to recap: values are grouped into types, and types are grouped into Kinds. Each pattern in this post builds on that. Higher-Kinded types let us define types that take other type constructors as arguments, like `Wrap` or `Compose`. The higher-Kinded data pattern gave me one config record type that could be partial or complete. And a custom Kind allowed me to keep each config field's `redis.conf` key with its type so the two couldn't drift apart.\n\nAs always, if you're interested in seeing more code, give the [PR](https://github.com/OlaoluwaM/redis-haskell/pull/2) a look see and tell me what you think. This post wasn't meant to be exhaustive, but I hope it's given you, dear reader, a decent intuition for Kinds and a push in the right direction.\n\nTill next time!\n\nThat's all folks. If you like what you see, feel free to connect with me on [LinkedIn](https://www.linkedin.com/in/olaoluwam/) or follow me on [X](https://x.com/ola_musta) or [Bluesky](https://bsky.app/profile/olamusta.bsky.social). I also stream sometimes @ola\\_musta on [Twitch](https://www.twitch.tv/ola_musta) and [Youtube](https://www.youtube.com/@ola_musta) too!\n","body_html":"<p>While working to add <a href=\"https://github.com/OlaoluwaM/redis-haskell/pull/2\" rel=\"nofollow ugc noopener\">typed configuration</a> to my Haskell Redis server clone, I found myself learning some new tricks, all of them stemming from Haskell&#39;s Kind system. In this article, we&#39;re gonna discuss what I learned and the things I managed to build with it.</p>\n<p>We&#39;ll start from the beginning, with Values.</p>\n<h2 id=\"values\">Values</h2>\n<p>These are the constructs we manipulate in our programs, things like <code>1</code>, <code>2</code>, <code>True</code>, <code>&quot;hello&quot;</code>, etc… In functional programming contexts, they&#39;re also called &quot;<em>terms.</em>&quot; They are the things we consume, create, compute with, and output. The management and manipulation of values is a big part of the practice of programming and software engineering.</p>\n<p>We all work with different values in our day to day. Like creatures in a fantasy setting, values that share certain characteristics – that are created, used, and manipulated in the same way – belong to the same species. Formally, these &quot;species&quot; are called <em>Types</em>.</p>\n<h2 id=\"types\">Types</h2>\n<p>Types are groups of related values. Put another way, the notion of a type offers us a means of collectively referencing/referring to a group of related values. In many programming languages, we use words like <code>Int</code>, <code>string</code>, <code>Bool</code>, <code>Text</code>, etc… to denote a type.</p>\n<p>The main benefit, in grouping values into types, IMO, is abstraction. Types inform us – the programmer and the compiler – of the characteristics of a value and the kinds of operations that can and can&#39;t be performed on or with it. Think of weapon classes in a game. In many RPGs there are weapon classes and the class of a weapon often dictates the kinds of attacks that you can perform or the kinds of skills you can use with it; types do the same for values.</p>\n<p>Taking this one step further, you might ask yourself, how do we classify types? Do we even need to do that? Why? To answer those questions, we must step into the realm of <em>Kinds.</em></p>\n<h2 id=\"kinds\">Kinds</h2>\n<p>Kinds are to types what types are to values. My intuition for the Kind system is that it is a means of classification for all things type level. You would think &quot;all things type level&quot; would simply refer to everyday types, but in Haskell (and a couple other languages), other type-level constructs exist, like type-level numbers (of Kind <code>Nat</code>), type-level strings (of Kind <code>Symbol</code>), or the constraints from Typeclasses (of Kind <code>Constraint</code>).</p>\n<p>We won&#39;t be going into much detail about any of those, but <code>Nat</code>s and <code>Symbol</code>s do often appear in type-level programming contexts. On the other hand, entities of Kind <code>Constraint</code> are likely more familiar because they&#39;re just (fully applied) Typeclasses for the most part. For example, the <code>Show</code> Typeclass belongs to the Kind <code>Type -&gt; Constraint</code> and <code>Show Int</code> belongs to the Kind <code>Constraint</code>.</p>\n<p>Regular types belong to the Kind <code>Type</code>. Confusing, I know. Type constructors, that is, things like <code>Maybe</code>, <code>Either</code>, <code>Last</code>, <code>Proxy</code>, <code>Dual</code>, etc…, have their Kind determined by both their <a href=\"https://en.wikipedia.org/wiki/Arity\" rel=\"nofollow ugc noopener\">arity</a> and the Kind of their parameters as showcased by the table below.</p>\n<div class=\"table-wrap\"><table><thead><tr><th>Value</th><th>Type-level construct</th><th>Kind</th></tr></thead><tbody><tr><td><code>1</code></td><td><code>Int</code></td><td><code>Type</code></td></tr><tr><td><code>True</code></td><td><code>Bool</code></td><td><code>Type</code></td></tr><tr><td><code>not</code></td><td><code>Bool -&gt; Bool</code></td><td><code>Type</code></td></tr><tr><td><code>[1, 2, 3]</code></td><td><code>[Int]</code></td><td><code>Type</code></td></tr><tr><td><code>Just &quot;hello&quot;</code></td><td><code>Maybe String</code></td><td><code>Type</code></td></tr><tr><td><code>Right 5</code></td><td><code>Either String Int</code></td><td><code>Type</code></td></tr><tr><td></td><td><code>Maybe</code></td><td><code>Type -&gt; Type</code></td></tr><tr><td></td><td><code>[]</code> (the list type constructor)</td><td><code>Type -&gt; Type</code></td></tr><tr><td></td><td><code>Either</code></td><td><code>Type -&gt; Type -&gt; Type</code></td></tr><tr><td></td><td><code>Either String</code></td><td><code>Type -&gt; Type</code></td></tr><tr><td></td><td><code>Three</code> (from <code>data Three a b c = …</code>)</td><td><code>Type -&gt; Type -&gt; Type -&gt; Type</code></td></tr><tr><td></td><td><code>5</code> (a type-level number)</td><td><code>Nat</code> (GHCi prints <code>Natural</code>)</td></tr><tr><td></td><td><code>&quot;hello&quot;</code> (a type-level string)</td><td><code>Symbol</code></td></tr><tr><td></td><td><code>Show Int</code></td><td><code>Constraint</code></td></tr><tr><td></td><td><code>Show</code></td><td><code>Type -&gt; Constraint</code></td></tr><tr><td></td><td><code>Functor</code></td><td><code>(Type -&gt; Type) -&gt; Constraint</code></td></tr><tr><td></td><td><code>Compose</code> (simplified)</td><td><code>(Type -&gt; Type) -&gt; (Type -&gt; Type) -&gt; Type -&gt; Type</code></td></tr></tbody></table></div>\n<p><code>*</code> vs <code>Type</code></p>\n<p>In older docs and in some GHC error messages, you&#39;ll see <code>*</code> where I write <code>Type</code>. They&#39;re the same thing. AFAICT <code>*</code> is a synonym for <code>Type</code> and vice versa. <code>*</code> is the older name, and GHC plans to deprecate it (<code>-Wall</code> already warns when you use it).</p>\n<p>Fun fact</p>\n<p>Technically, regular types are also type constructors, type constructors with no parameters. You may find them being referred to as &quot;<em>nullary type constructors</em>&quot;.</p>\n<p>Without Kinds, there&#39;d be no way to tell type-level constructs like <code>Maybe</code>, <code>Maybe Int</code>, and <code>Show</code> apart, IMO. Depending on where you are in your Haskell journey, the benefit of having some of these other type-level entities may not be entirely apparent, but I can assure you that they all work to make the Haskell type system more expressive, allowing us to codify more things in the type system, which in turn makes it easier to write safer programs.</p>\n<h2 id=\"higher-kinded-types\">Higher-Kinded Types</h2>\n<p>Now, we mentioned before that types allow us to abstract over values. This means that we can collectively refer to a set of values through their type. Similarly, with a given Kind, we name a collection of type-level constructs. For example, the Kind <code>Type -&gt; Type</code> collectively refers to all type constructors that take a single regular type as an argument: that covers things like <code>Maybe</code>, but also partial applications like <code>Either String</code> or <code>Three Int String</code>. Likewise, the Kind <code>Type -&gt; Type -&gt; Type</code> collectively refers to all type constructors that take two regular types as arguments. To tie this back down to the value/term level, it&#39;s similar to function signatures. A function signature of <code>Int -&gt; Bool</code> collectively refers to all functions that take an <code>Int</code> as argument and return a <code>Bool</code>.</p>\n<p>Now, we know that a signature like <code>Int -&gt; Bool</code> collectively refers to a set of functions, but what about something like <code>(Int -&gt; Int) -&gt; Bool</code>? What does that mean? That signature represents all functions that return a boolean only after accepting a function with a signature of <code>Int -&gt; Int</code> as argument. This type of signature is quite common, and functions with signatures like this are called higher-order functions.</p>\n<p>Okay, now, what happens if we try to extrapolate this idea to the Kind level? What if we replaced the <code>Type</code> param in <code>Type -&gt; Type</code> with <code>(Type -&gt; Type)</code>? Well, for starters, we get a signature like <code>(Type -&gt; Type) -&gt; Type</code>. We know that <code>Type</code> is the Kind for all regular types and <code>Type -&gt; Type</code> is the Kind for all type constructors that take a single regular type as argument. Putting that together means that the signature <code>(Type -&gt; Type) -&gt; Type</code> represents the Kind for all type-level constructs that take a type constructor as argument. You see where I&#39;m going with this? Just as functions with signatures like <code>(Int -&gt; Int) -&gt; Bool</code> are called higher-order functions, type constructors with Kinds like <code>(Type -&gt; Type) -&gt; Type</code> or <code>(Type -&gt; Type) -&gt; (Type -&gt; Type) -&gt; Type</code> are collectively called higher-Kinded types or HKTs for short.</p>\n<p>Here&#39;s a small example:</p>\n<pre><code>data Wrap (f :: Type -&gt; Type) = Wrap (f Int)</code></pre>\n<p><code>Wrap</code> takes a type constructor <code>f</code> and puts an <code>Int</code> inside it, so its Kind is <code>(Type -&gt; Type) -&gt; Type</code>. <code>Wrap Maybe</code> is a regular type, and so is <code>Wrap []</code>. <code>Wrap (Just 5)</code> and <code>Wrap [1, 2, 3]</code> are values of each. <code>Compose</code> from the table earlier is another example, just with more parameters.</p>\n<p>From another angle, if parameterized types like <code>Maybe</code> or <code>Either</code> are what other languages call generics, then HKTs are generics that can take other generics as arguments.</p>\n<p>The concept of HKTs, when coupled with Typeclasses, gives us the ability to implement certain functionality on type-level constructs. That&#39;s useful because it lets us reuse operations in many more contexts. To illustrate, let&#39;s take <code>Functor :: (Type -&gt; Type) -&gt; Constraint</code>. <code>Functor</code> isn&#39;t an HKT itself, since its Kind ends in <code>Constraint</code>, not <code>Type</code>. However, its parameter <em>is</em> a type constructor, which makes it higher-Kinded.</p>\n<p>Now, if a type constructor has a <code>Functor</code> instance, we can map over its contents using the <code>fmap</code> function. Furthermore, thanks to the higher-Kinded nature of <code>Functor</code>, we only need to define a single <code>Functor</code> instance for an appropriately Kinded type constructor (<code>Type -&gt; Type</code> specifically) to be able to use <code>fmap</code> with any specialization of said type constructor. What I mean is, because we define the <code>Functor</code> instance on, say, <code>Maybe</code> and not some <code>Maybe a</code>, we can use <code>fmap</code> on all concrete versions of <code>Maybe a</code> (<code>Maybe Int</code>, <code>Maybe String</code>, <code>Maybe Bool</code>, etc…). In fact, it can&#39;t work any other way: <code>Functor</code>&#39;s Kind signature only accepts type constructors of Kind <code>Type -&gt; Type</code>, so something like <code>Maybe Int</code>, of Kind <code>Type</code>, isn&#39;t even a candidate. The instance belongs to <code>Maybe</code> itself. This principle applies to all other higher-Kinded Typeclasses and appropriately Kinded type constructors.</p>\n<p>In non-technical terms, take a bowl for eating. Make the bowl itself microwave-safe and you can use it to warm any food you put in it. In this case, the bowl is our &quot;appropriately Kinded type constructor&quot;; the standard that determines whether something is &quot;microwave-safe&quot; is our higher-Kinded Typeclass (like <code>Functor</code>); the &quot;microwave-safe&quot; label we put on our bowl is its Typeclass instance; and the bowl of food we&#39;re able to microwave represents a specialization of our type constructor. Here too, it can&#39;t work any other way: nobody certifies a bowl of soup as microwave-safe. The label belongs to the bowl and applies regardless of the bowl&#39;s contents.</p>\n<h2 id=\"higher-kinded-data\">Higher-Kinded Data</h2>\n<p>Higher-Kinded data, or HKD for short, is the name of a pattern where, instead of a type-level thing, a data type takes one or more type constructors as parameters and uses them to wrap some or all of its fields. People might consider this &quot;advanced&quot;, but it&#39;s actually pretty straightforward once you wrap your head around <a href=\"https://thunk.blog/posts/types-of-types-haskell-kind-system/#higher-kinded-types\" rel=\"nofollow ugc noopener\">higher-Kinded types</a>. Here&#39;s an example:</p>\n<pre><code>-- The `f` parameter is a type constructor, making `Profile` higher-Kinded\ndata Profile (f :: Type -&gt; Type) = Profile\n    { age :: f Int\n    , name :: f String\n    , profession :: f String\n    , address :: f String\n    }\n\n-- `f = Identity`: every field must be present\ngrace :: Profile Identity\ngrace = Profile (Identity 37) (Identity &quot;Grace&quot;) (Identity &quot;Computer scientist&quot;) (Identity &quot;New York&quot;)\n\n-- `f = Maybe`: any field can be missing, like a half-filled sign-up form\ndraft :: Profile Maybe\ndraft = Profile Nothing (Just &quot;Grace&quot;) Nothing Nothing\n\n-- `f = Const String`: every field holds a `String`, whatever its type, like a form label\nlabels :: Profile (Const String)\nlabels = Profile (Const &quot;Age&quot;) (Const &quot;Name&quot;) (Const &quot;Profession&quot;) (Const &quot;Address&quot;)</code></pre>\n<p>Let&#39;s take a deeper look using this <a href=\"https://github.com/OlaoluwaM/redis-haskell/pull/2\" rel=\"nofollow ugc noopener\">PR</a> from my <a href=\"https://github.com/OlaoluwaM/redis-haskell\" rel=\"nofollow ugc noopener\">redis-haskell</a> project, where I added support for typed Redis configuration sourced from both command-line options and options declared in a <code>redis.conf</code> file.</p>\n<p>First, to implement this, I needed a data type representation of a Redis configuration with the fields I wanted to support.</p>\n<p>The issue I first ran into was a case of duplication. My initial data type idea was a plain record type, but it needed to support two slightly different contexts:</p>\n<ul><li>Default config where all the fields will be available, initialized to their official default values.</li><li>Command-line configs and <code>redis.conf</code> configs where fields would need to be nullable.</li></ul>\n<p>My initial direction was to define two record types: one where the field types were bare, and another where they were wrapped in a <a href=\"https://www.stackage.org/haddock/lts-24.63/base-4.20.2.0/Data-Monoid.html#t:Last\" rel=\"nofollow ugc noopener\"><code>Last</code></a>.</p>\n<pre><code>data RedisConfig = RedisConfig\n    { rdbFileDirPath :: SomeBase Dir\n    , rdbFilenamePath :: Path Rel File\n    , useRDBCompression :: Bool\n    , genRdbChecksum :: Bool\n    , port :: Int\n    }\n    deriving stock (Generic)\n    \ndata PartialRedisConfig = PartialRedisConfig\n    { rdbFileDirPath :: Last (SomeBase Dir)\n    , rdbFilenamePath :: Last (Path Rel File)\n    , useRDBCompression :: Last Bool\n    , genRdbChecksum :: Last Bool\n    , port :: Last Int\n    }\n    deriving stock (Generic)</code></pre>\n<p>Why <code>Last</code></p>\n<p><code>Maybe</code>&#39;s <code>&lt;&gt;</code> defers to the <code>Semigroup</code> semantics of the type it contains, unless either operand is a <code>Nothing</code>. Moreover, for it to work, all our wrapped field types would need to have a <code>Semigroup</code> instance. <code>Bool</code> and <code>Int</code> don&#39;t have one. And even for types that do, <code>&lt;&gt;</code> would combine the two values instead of picking one. <code>Last</code> keeps the latest value that&#39;s present, letting later sources override earlier values.</p>\n<p>I wasn&#39;t a fan of this approach because I wanted to have a single source of truth for supported config field names and types. With two data types, it would have been easy to change one variant while forgetting to update the other. The type system could have caught any discrepancies, depending on how things are implemented, but that wasn&#39;t good enough.</p>\n<p>One option would have been to have just one Redis config type with every field wrapped in <code>Last</code>. Since <code>Last</code> keeps the right-most <code>Just</code> value, <code>defaults &lt;&gt; confFileOpts &lt;&gt; cliOpts</code> would give command-line options priority, followed by <code>redis.conf</code> options, then the default values. Exactly the semantics we want. The problem, though, would have been the final result. It would&#39;ve still been a record with all fields wrapped in <code>Last</code>, even though combining with the default record (<code>defaults</code>) would&#39;ve ensured all fields had a value. Consequently, every site that read from that final config would&#39;ve had to unwrap a <code>Last</code> value that couldn&#39;t be a <code>Nothing</code>.</p>\n<p>You may also be wondering: why not define another record with bare fields for just the final res—oh…. Yeah.</p>\n<p>So, again, I needed two variations of my config type: a partial one for merging, and a complete one for defaults and use by the rest of the server. What I didn&#39;t want was two definitions.</p>\n<p>To achieve this, I ended up with a definition akin to:</p>\n<pre><code>data RedisConfigF (f :: Type -&gt; Type) = RedisConfigF\n    { rdbFileDirPath :: f (SomeBase Dir)\n    , rdbFilenamePath :: f (Path Rel File)\n    , useRDBCompression :: f Bool\n    , genRdbChecksum :: f Bool\n    , port :: f Int\n    }\n    deriving stock (Generic)</code></pre>\n<p><code>RedisConfigF</code> is a higher-Kinded data type. It is higher-Kinded because its <code>f</code> parameter is of Kind <code>Type -&gt; Type</code>. That makes <code>RedisConfigF</code> itself of Kind <code>(Type -&gt; Type) -&gt; Type</code>, the same as the <a href=\"https://thunk.blog/posts/types-of-types-haskell-kind-system/#bccebc\" rel=\"nofollow ugc noopener\"><code>Wrap</code> definition above</a>. This gave me one data type definition for both the contexts I described above. For defaults and the final merged config, <code>f = Identity</code>, and for the other case, where field values may not be present, <code>f = Last</code>.</p>\n<p>To merge, we leverage the <code>Semigroup</code> instance for <code>Last</code> to combine the config records of <code>redis.conf</code> options and CLI options. Then we fill in gaps with the defaults. Here&#39;s the <a href=\"https://github.com/OlaoluwaM/redis-haskell/blob/f3839f961ee43c4d12f4f51272615c74abcb6b9f/codecrafters-redis/src/Redis/Server/Config.hs#L74-L81\" rel=\"nofollow ugc noopener\">merge code</a>.</p>\n<p>After that, we get back a record of type <code>RedisConfigF Identity</code> with all fields filled in. However, with <code>f = Identity</code>, every field is wrapped in <code>Identity</code>, which still needs to be unwrapped on access. The real code <a href=\"https://github.com/OlaoluwaM/redis-haskell/blob/f3839f961ee43c4d12f4f51272615c74abcb6b9f/codecrafters-redis/src/Redis/Server/Config/Types.hs#L80\" rel=\"nofollow ugc noopener\">automates that unwrapping</a>, but how it does so is beyond the scope of this article.</p>\n<p>In any case, what we&#39;ve just walked through is the power and utility of higher-Kinded data types. Another interesting use case I leveraged was using <code>RedisConfigF</code> for field parser association <a href=\"https://github.com/OlaoluwaM/redis-haskell/blob/f3839f961ee43c4d12f4f51272615c74abcb6b9f/codecrafters-redis/src/Redis/Server/Config/Conf.hs#L90-L112\" rel=\"nofollow ugc noopener\">here</a>. With <code>f = Const (Text, ReadM PartialRedisConfig)</code>, the same record holds each field&#39;s <code>redis.conf</code> name and parser, so if I add a field and forget to give it a name and parser there, the compiler warns me. Neat stuff!</p>\n<p>Barbies</p>\n<p>I know the <a href=\"https://hackage.haskell.org/package/barbies-2.1.1.0/docs/Barbies.html\" rel=\"nofollow ugc noopener\">barbies</a> library exists for working with higher-Kinded data like this, but it felt simpler and more educational to just build the things I needed for this specific use case.</p>\n<h2 id=\"custom-kinds\">Custom Kinds</h2>\n<p>The final thing I&#39;d like to touch on is custom Kinds. Yes, just as you can make your own types, you can make your own Kinds. Custom Kinds are our own type-level constructs, useful for promoting info into the type system. Example:</p>\n<pre><code>{-# LANGUAGE DataKinds #-}\n\ndata Person = Person Symbol Symbol Nat</code></pre>\n<p>Here, we have a data declaration that&#39;s also been promoted to a Kind through the <code>DataKinds</code> extension. Essentially, what that extension does is extend the utility of the <code>data</code> declaration syntax so data declarations define Kinds as well as data types. In the case of the snippet above:</p>\n<ul><li><code>Person</code> is the name of our custom data type and Kind</li><li><code>Person Symbol Symbol Nat</code> is a description of the structure/shape of the type-level entities that are members of the Kind</li><li><code>&#39;Person :: Symbol -&gt; Symbol -&gt; Nat -&gt; Person</code> is the type constructor for creating type-level entities of Kind <code>Person</code></li><li><code>Person :: Symbol -&gt; Symbol -&gt; Nat -&gt; Person</code> is the data constructor of the type <code>Person</code></li></ul>\n<p>A member of the Kind <code>Person</code> looks like this:</p>\n<pre><code>type Ada = &#39;Person &quot;Ada&quot; &quot;Lovelace&quot; 21   -- Ada :: Person</code></pre>\n<p>Ticks &amp; Puns</p>\n<p>You might have noticed the tick in <code>&#39;Person</code>. It is to disambiguate a pun. In this context, a <em>pun</em> is when a type and its constructor share a name, like <code>data Person = Person …</code>. Usually this is fine because Haskell keeps types and terms in separate namespaces. However, once <code>DataKinds</code> is enabled and the data constructor is promoted, both names (type and data constructor) can be used at the type level. How then are we to refer to the promoted constructor when plain <code>Person</code> already means the type and Kind? The tick lets us refer to the constructor explicitly: <code>&#39;Person</code>.</p>\n<p>When the names don&#39;t clash (like <code>data Person = MkPerson …</code>), the tick is optional. Since GHC 9.4, GHC no longer recommends writing it by default either (<a href=\"https://gitlab.haskell.org/ghc/ghc/-/issues/20531\" rel=\"nofollow ugc noopener\">GHC issue #20531</a>). In our <code>Person</code> example, though, the names do clash, so the tick is required.</p>\n<p>Notice that <code>Person</code> shows up as both a type and a Kind. Well, <strong>since GHC 8, every Kind is also considered a type</strong>. It&#39;s kinda like how you can be a parent and a child at the same time: a parent to your kids, and a child to your own parents. <code>Person</code> works the same way: it&#39;s the Kind that <code>Ada</code> belongs to, and it&#39;s also a type that belongs to the Kind <code>Type</code>.</p>\n<p>Additionally, as some types may be uninhabited, some Kinds are also uninhabited. For example, <code>Int</code> is inhabited by values as a type, but uninhabited by type-level constructs as a Kind. Conversely, <code>Symbol</code> is uninhabited as a type, but inhabited by type-level string literals as a Kind. In fact, it is for this reason that our <code>Person</code> type can have no values at the term level (no values of type <code>Symbol</code> to use to create a value of type <code>Person</code>), but it can be inhabited at the Kind level.</p>\n<p><code>Bool</code> is interesting because it is inhabited both as a type and as a Kind. Same with <code>Nat</code>, which is an alias for <code>Natural</code>.</p>\n<div class=\"table-wrap\"><table><thead><tr><th>Type</th><th>Members as a type (values)</th><th>Members as a Kind (types)</th></tr></thead><tbody><tr><td><code>Int</code></td><td><code>0</code>, <code>-3</code>, …</td><td>none</td></tr><tr><td><code>Symbol</code></td><td>none</td><td><code>&quot;Ada&quot;</code>, <code>&quot;port&quot;</code>, …</td></tr><tr><td><code>Bool</code></td><td><code>True</code>, <code>False</code></td><td><code>&#39;True</code>, <code>&#39;False</code></td></tr><tr><td><code>Nat</code></td><td><code>0</code>, <code>21</code>, …</td><td><code>0</code>, <code>21</code>, … (type-level literals)</td></tr></tbody></table></div>\n<p>With that explanation out of the way, let&#39;s bring this back to the redis-haskell use case. In my PR, I needed two pieces of data for each field on my <code>RedisConfigF</code> type: the <code>redis.conf</code> key a field name corresponds to (field names deliberately differ from the actual config keys for semantics&#39; sake) and its type. I originally defined separate type synonyms like so:</p>\n<pre><code>type RDBFileDir = SomeBase Dir\ntype RDBFileDirConfigFieldName = &quot;dir&quot;\n\ntype RDBFilename = Path Rel File\ntype RDBFilenameConfigFieldName = &quot;dbfilename&quot;\n-- …one more pair per field</code></pre>\n<p>I also tried type-level tuples, for better encapsulation:</p>\n<pre><code>type RDBFileDir  = &#39;(&quot;dir&quot;, SomeBase Dir)          -- Kind: (Symbol, Type)\ntype RDBFilename = &#39;(&quot;dbfilename&quot;, Path Rel File)</code></pre>\n<p>Tuple pun</p>\n<p>The tick works the same way here. Tuple syntax is also a pun, since <code>(a, b)</code> names both the tuple type and its constructor.</p>\n<p>But that would have been akin to using an anonymous product type at the term/value level. Something like <code>(Symbol, Type)</code> is structural. Any two such pairs would be the same Kind regardless of intent. So instead, I decided to make my own custom Kind, <a href=\"https://github.com/OlaoluwaM/redis-haskell/blob/f3839f961ee43c4d12f4f51272615c74abcb6b9f/codecrafters-redis/src/Redis/Server/Config/Types.hs#L48\" rel=\"nofollow ugc noopener\"><code>ConfigField</code></a>, which is nominal, semantic, self-documenting, and impossible to confuse with some other <code>(Symbol, Type)</code> pair.</p>\n<p>Here&#39;s the definition, with the fields from the snippets above rewritten to use it:</p>\n<pre><code>data ConfigField = ConfigField Symbol Type  -- with DataKinds, this is also a Kind\n\ntype RDBFileDir        = &#39;ConfigField &quot;dir&quot;            (SomeBase Dir)\ntype RDBFilename       = &#39;ConfigField &quot;dbfilename&quot;     (Path Rel File)\ntype UseRDBCompression = &#39;ConfigField &quot;rdbcompression&quot; Bool\ntype GenRDBChecksum    = &#39;ConfigField &quot;rdbchecksum&quot;    Bool\ntype RedisPort         = &#39;ConfigField &quot;port&quot;           Int</code></pre>\n<p>Now each field&#39;s <code>redis.conf</code> key and its Haskell type live together in a single <code>ConfigField</code>, and with the use of some other type-level mechanisms, we can access that information at the term level, so there&#39;s no need to duplicate it. We do this in a couple spots using <a href=\"https://github.com/OlaoluwaM/redis-haskell/blob/f3839f961ee43c4d12f4f51272615c74abcb6b9f/codecrafters-redis/src/Redis/Server/Config/Types.hs#L221\" rel=\"nofollow ugc noopener\"><code>getConfigFieldName</code></a>: the <a href=\"https://github.com/OlaoluwaM/redis-haskell/blob/f3839f961ee43c4d12f4f51272615c74abcb6b9f/codecrafters-redis/src/Redis/Server/Config/CommandLine.hs#L37\" rel=\"nofollow ugc noopener\">command-line parser</a>, where each key doubles as its flag name (<code>&quot;dir&quot;</code> becomes <code>--dir</code>), and the <a href=\"https://github.com/OlaoluwaM/redis-haskell/blob/f3839f961ee43c4d12f4f51272615c74abcb6b9f/codecrafters-redis/src/Redis/Server/Config/Conf.hs#L90\" rel=\"nofollow ugc noopener\"><code>redis.conf</code> parser</a>.</p>\n<h2 id=\"conclusion\">Conclusion</h2>\n<p>So, to recap: values are grouped into types, and types are grouped into Kinds. Each pattern in this post builds on that. Higher-Kinded types let us define types that take other type constructors as arguments, like <code>Wrap</code> or <code>Compose</code>. The higher-Kinded data pattern gave me one config record type that could be partial or complete. And a custom Kind allowed me to keep each config field&#39;s <code>redis.conf</code> key with its type so the two couldn&#39;t drift apart.</p>\n<p>As always, if you&#39;re interested in seeing more code, give the <a href=\"https://github.com/OlaoluwaM/redis-haskell/pull/2\" rel=\"nofollow ugc noopener\">PR</a> a look see and tell me what you think. This post wasn&#39;t meant to be exhaustive, but I hope it&#39;s given you, dear reader, a decent intuition for Kinds and a push in the right direction.</p>\n<p>Till next time!</p>\n<p>That&#39;s all folks. If you like what you see, feel free to connect with me on <a href=\"https://www.linkedin.com/in/olaoluwam/\" rel=\"nofollow ugc noopener\">LinkedIn</a> or follow me on <a href=\"https://x.com/ola_musta\" rel=\"nofollow ugc noopener\">X</a> or <a href=\"https://bsky.app/profile/olamusta.bsky.social\" rel=\"nofollow ugc noopener\">Bluesky</a>. I also stream sometimes @ola_musta on <a href=\"https://www.twitch.tv/ola_musta\" rel=\"nofollow ugc noopener\">Twitch</a> and <a href=\"https://www.youtube.com/@ola_musta\" rel=\"nofollow ugc noopener\">Youtube</a> too!</p>","headings":[{"level":2,"text":"Values","id":"values"},{"level":2,"text":"Types","id":"types"},{"level":2,"text":"Kinds","id":"kinds"},{"level":2,"text":"Higher-Kinded Types","id":"higher-kinded-types"},{"level":2,"text":"Higher-Kinded Data","id":"higher-kinded-data"},{"level":2,"text":"Custom Kinds","id":"custom-kinds"},{"level":2,"text":"Conclusion","id":"conclusion"}]}}