A Dictionary of Single-Letter Variable Names

Posted on October 12, 2024 by Jack Kelly
Tags: haskell, coding

Haskell’s expressive type system means that type signatures can carry a lot of information. Haskell’s polymorphism means that you sometime write a function that works across an enormous range of types, and are often left wondering “what do I actually call my variables?”. It is often the case that there’s nothing to say beyond “this variable is a Functor”, or “this variable is a monadic action”, and so a single-letter variable name is appropriate. An unofficial and largely undocumented convention has emerged around these variable names, and so I wanted to write them all down in one place.

It should go without saying that single-letter variable names are not always the answer. Like point-free style, it can sometimes obscures more than it helps and people get carried away with it. But when you have a highly polymorphic function and no good words to use, choosing the right letter can convey a surprising amount of meaning.

With the warning out of the way, the dictionary is after the jump.

Types

a, b, c, d

Arbitrary types, usually of kind Type, like the a in a Foldable t => t a. Almost never used as type variables for higher-kinded types, unless they’re kind-polymorphic (like data Proxy (a :: k) = Proxy).

a

(Rarely) the type of an Arrow, but because of the above common usage of a, this is often confusing. I recommend arr or infix k instead.

e

An “error” or exception type, though I personally prefer ex or exc for true exceptions. Using e in a type like Either e a indicates that Either is being used for its Monad instance, where Left is considered an exceptional/early/error return, and not as an unbiased choice between two types.

-- From packages `mtl`, `generic-lens`:
trySomething ::
  (MonadError e m, AsType SomeSpecificError e) =>
  SomeArgument ->
  m SomeResponse

f, g, h

Used to indicate Functor and “Functor-like” types including Applicative, Alternative, and their contravariant versions Contravariant, Divisible, and Decidable.

i, j

An index: a type that identifies an element in a structure (a key in a key-value map; an Int for a list, vector, or sequence; etc).

-- From package `indexed-traversable`:
class Functor f => FunctorWithIndex i f | f -> i where
  imap :: (i -> a -> b) -> f a -> f b

instance FunctorWithIndex Int List
instance FunctorWithIndex k (Map k)

k

Most commonly the keys of a key-value map, or the arrows of a category. The latter (mnemonic: kategory) is sometimes written infix. Also used as the “kind variable” for a poly-kinded type.

-- From package `containers`:
mapWithKey ::
  (k -> a -> b) ->
  Map k a ->
  Map k b

-- From package `categories`:
class Category k => HasTerminalObject k where
  type Terminal k :: Type
  terminate :: a `k` Terminal k

data Proxy (a :: k) = Proxy
--               ^-- The variable `a` has polymorphic kind.

m

A Monoid or Monad. n is sometimes also used when a second type variable is needed, but is hard to visually distinguish from m. Consider m', m1 and m2, or (for Monads) f and g instead.

foldMap :: (Foldable t, Monoid m) => (a -> m) -> t a -> m
(>>=) :: Monad m => m a -> (a -> m b) -> m b

n

A type-level natural number.

p, q

Mnemonically, a Profunctor, but often used for Bifunctors as well. (The obvious first choice — b — would clash with the use of a/b/c/d for arbitrary types.)

-- From package `profunctors`:
class Profunctor p where
  dimap :: (a -> b) -> (c -> d) -> p b c -> p a d
  lmap :: (a -> b) -> p b c -> p a c
  rmap :: (b -> c) -> p a b -> p a c

class (forall a. Functor (p a)) => Bifunctor p where
  bimap :: (a -> b) -> (c -> d) -> p a c -> p b d
  first :: (a -> b) -> p a c -> p b c
  second :: (b -> c) -> p a b -> p a c

r

A “return type”. Often used as the result type for streams and the like, as well as in type signatures for functions written in continuation-passing style:

-- From package `streaming`:
data Stream f m r
--              ^-- Result type of the stream.

-- From package `constraints-extras`:
class Has c f where
  has :: forall a r. f a -> (c a => r) -> r
  --                                ^     ^
  -- Continuation-passing style: the return type
  -- of the function argument is the return type
  -- of the whole function.

s, t, a, b

These four type variables often appear together in lenses or other optics. The simplest complete example is a Lens s t a b: it can extract an a from an s and overwrite it with a b. Doing so would produce a value of type t.

-- A lens into the second part of a 2-tuple.
-- Inspired by package `lens`, but less polymorphic to make the point clear.
_2 :: Lens (x, c) (x, d) c d
--           ^      ^    ^ ^-- `b`: Type of the new value to put back in.
--           |      |    '---- `a`: Type of the value extracted from the tuple.
--           |      '--------- `t`: Type of the tuple with a new value written back.
--           '---------------- `s`: Type of the initial tuple that is focused upon.

s

A state type, like the ones plumbed around by StateT.

-- From package `mtl`.
modify :: MonadState s m => (s -> s) -> m ()

t

Traversable structures. Foldable is a superclass of Traversable, so Foldables are often called t as well:

class Foldable t where
  foldMap :: Monoid m => (a -> m) -> t a -> m

x

A type that is ignored, irrelevant, or inaccessible:

-- From package `streaming`:
maps ::
  (Monad m, Functor f) =>
  (forall x. f x -> g x) ->
  --      ^-- The function passed in here cannot know anything
  --          interesting about `x`, so it is "inaccessible".
  Stream f m r ->
  Stream g m r

-- From package `streaming`:
for ::
  (Monad m, Functor f) =>
  Stream (Of a) m r ->
  (a -> Stream f m x) ->
  --               ^-- This x is not referenced elsewhere; `for`
  --                   discards the result of the substreams.
  Stream f m r

Terms

I have not seen as many important single-letter variable names in common use. This might be because arguments often carry more semantic information than types and so can be given usage-specific names, or it might be that point-free code has meant that it isn’t as necessary to invent names that often get elided.

b

An arbitrary ByteString. I personally am not a fan of using b or bs for this: there is often a short word that describes what it actually is, and if it truly is an arbitrary collection of bytes, then bytes is only five characters.

e

An error or exception, though I often prefer err for error types and ex or exc for true exceptions.

f, g, h

Arbitrary functions.

h

Sometimes used for the head of a sequence, but the x:xs notation is more common.

i, j, k

An integral value, or (less common) an index into a data structure.

k

A key for a key-value data structure like a Data.Map.Map, or a continuation parameter. (Mnemonic: kontinuation)

-- From package `transformers`:
runCont :: Cont r a -> (a -> r) -> r
runCont m k = runIdentity (runContT m (Identity . k))
--        ^-- The continuation parameter.

m

A Monadic action or a Monoidal value.

n

A numeric (often Natural or at least Integral) quantity, where it is the single induction variable.

p, q

Most commonly stands for a Profunctor or Bifunctor.

p

p is sometimes used to name a proposition — a Bool with no semantic meaning that we can see. All we care about is whether it is True or False.

It is also rarely used for “predicates”. A predicate is (almost always) represented by a function (a -> Bool) but it generally written out either as the abbreviation pred_ (pred without the underscore clashes with a method from class Enum) or with the standard variable name f.

when :: Applicative f => Bool -> f () -> f ()
when p s = if p then s else pure ()

s

Sometimes used for an arbitrary String or Text. str is usually more common, or c:cs or ch:chs if recursing over individual Chars (the s suffix denotes plural “chars”; this is also discussed in the section on x, y, z).

t

An arbitrary Text value, or sometimes a time value. I am leery of using t for times, as times often come with some contextual meaning attached to them and can be given better names like now or createdAt.

t is sometimes also used for the tail of a sequence, but the x:xs notation is more common.

v

The value of an entry in a key-value data structure like a Data.Map.Map.

r

A result of some sort.

x, y, z

An arbitrary value about which nothing is known. Often seen with a suffix s to pluralise the variable and denote a collection of arbitrary values. Read xs, ys, zs as “eckses”, “wyes”, and “zeds” (though American readers may disagree on the final one).

map :: (a -> b) -> [a] -> [b]
map f list = case list of
  [] -> []

  -- `x` binds one value from the list head;
  -- `xs` binds many values from the list tail.
  x:xs -> f x : map f xs

_ (Underscore)

Not a variable name, but a blank pattern. A pattern that matches anything but binds no variable to the matched data.

Thanks

Previous Post
All Posts | RSS | Atom
Copyright © 2024 Jack Kelly
Site generated by Hakyll (source)