How Serokell Contributes to the Future of Haskell

At Serokell, we believe that Haskell still has a lot of room to grow.

The language already has one of the most expressive type systems in practical use. It gives programmers algebraic data types, type classes, higher-kinded types, GADTs, type families, promoted data constructors, and many other tools for making invalid states harder to represent.

Over the past years, Serokell has worked on several parts of the Haskell ecosystem: GHC development, language proposals, Dependent Haskell, developer tooling, and education. In this article, we’ll go through the main contributions and explain why they matter.

Summary

Here’s a brief overview of the areas covered in this article:

  1. Work towards Dependent Haskell.
  2. Haskell ecosystem tools and education.
  3. Contributions to GHC.
  4. Implemented language extensions.
  5. Parser and type-system improvements.
  6. Accepted GHC proposals.

Let’s start with the big picture.

Dependent Haskell roadmap

One of Serokell’s long-term goals is to help make dependent types available in Haskell.

Dependent types allow types to depend on values. This makes it possible to express more information in types and move more correctness checks from runtime to compile time. In a dependently typed language, we can write APIs that encode stronger invariants directly in their types.

For Haskell, this is not a single feature that can be added all at once. It is a large design and engineering project. GHC needs better support for visible type arguments, type syntax in terms, predictable quantification, improved kind checking, and a more uniform relationship between the term-level and type-level languages.

To make this work easier to follow, Serokell created the Dependent Haskell roadmap. The roadmap presents the path towards dependent types as a sequence of concrete steps rather than as one vague future goal.

This is useful for compiler developers, library authors, and advanced Haskell users. It shows which features are already implemented, which ones are in progress, and how individual changes fit into the bigger design.

In other words, the roadmap helps answer the question: “What exactly has to happen before Haskell gets dependent types?”

Hackage is the central package repository for Haskell. If you use Haskell in practice, you spend a lot of time looking for packages, modules, functions, and examples.

Serokell built Hackage Search, a tool for searching through the Haskell package ecosystem.

Good search matters more than it may seem. Haskell libraries often expose many small, composable functions. Finding the right abstraction can save a lot of time, especially when working with unfamiliar libraries.

Hackage Search improves discoverability. It helps users navigate available packages and find relevant definitions faster. This is the kind of tooling that does not change the language itself, but makes the ecosystem easier to use.

Haskell Certification

Serokell also initiated the Haskell Certification program, which has since been taken up by the Haskell Foundation.

Haskell has a steep learning curve. Newcomers have to learn not only new syntax, but also a different way of structuring programs: pure functions, algebraic data types, type classes, effects, lazy evaluation, and eventually type-level programming.

A certification program gives learners a clearer path. It also helps teams and companies evaluate Haskell knowledge in a more structured way.

The fact that the program is now handled by the Haskell Foundation is important. It means that the effort has moved from a company initiative into broader community infrastructure.

Contributions to GHC

Serokell has made over 300 commits to the Glasgow Haskell Compiler.

These commits range from bug fixes and refactorings to entirely new language features. This distinction is important. Compiler work is not only about adding visible extensions. A large part of the work is maintenance: simplifying internal representations, improving error messages, fixing subtle interactions between extensions, and preparing the codebase for future changes.

GHC is an old and sophisticated compiler. Every change has to respect existing language behavior, interact with many extensions, and preserve compatibility where possible. Small changes can have wide consequences.

That is why long-term GHC work requires both language-design understanding and engineering discipline.

Standalone kind signatures

Serokell implemented the StandaloneKindSignatures extension.

Kinds are the types of types. For example, ordinary types such as Int or Bool have kind Type, while type constructors such as Maybe have kind Type -> Type.

Before standalone kind signatures, kind information was often written directly inside declarations or inferred by GHC. With this extension, programmers can write a kind signature separately from the declaration it describes.

This mirrors the way ordinary type signatures are written for functions.

One benefit is clarity. In type-level programming, explicit kind signatures make APIs easier to read and help GHC check declarations in a more predictable way. Beyond readability, there’s a deeper reason to introduce standalone kind signatures: they enable type-level polymorphic recursion, replacing a brittle, ad-hoc heuristic (CUSKs).

Type abstractions

Serokell implemented the TypeAbstractions extension.

Haskell programmers have long been able to pass type arguments explicitly with visible type application, using syntax such as f @Int. Type abstractions complete the other side of the story: they allow programmers to bind type variables explicitly in places such as patterns, lambdas, and function equations.

For example, instead of relying on implicit scoping rules, a programmer can bind a type variable exactly where it is needed.

This makes code easier to reason about. It also reduces reliance on older mechanisms such as ScopedTypeVariables, where the scope of a type variable can be non-local and sometimes surprising.

Type abstractions are also part of the larger movement towards making term-level and type-level programming more uniform.

Required type arguments

Serokell implemented the RequiredTypeArguments extension.

Usually, Haskell infers type arguments. This is convenient, but sometimes a function really needs the user to provide a type explicitly.

Required type arguments allow a function to request a type as a visible argument. For example, a function may say: “The caller must provide this type parameter.”

This is especially important for Dependent Haskell. If types and values are going to interact more closely, Haskell needs a clear way to pass types as arguments.

Required type arguments make this interaction explicit and ergonomic.

StarIsType and NoStarIsType

Serokell implemented the StarIsType and NoStarIsType extensions, the latter enabling a cleaner kind syntax

Historically, Haskell used * as syntax for the kind of ordinary types. For example, Int has kind *.

But * is also an operator. This overloaded meaning creates confusion and makes the grammar less regular. Modern Haskell uses Type instead.

The move away from star-as-type syntax makes the language cleaner. It also frees * to behave like an ordinary type operator.

This work is part of a broader effort to remove special cases from Haskell’s kind syntax.

CUSKs and NoCUSKs

Serokell implemented the CUSKs and NoCUSKs extensions, the latter retiring the old kind inference heuristic

CUSK stands for “complete user-supplied kind signature”. CUSKs were an older mechanism that allowed GHC to decide whether a declaration had enough kind information to be checked independently.

As Haskell’s kind system became more expressive, CUSK rules became harder to explain and harder to use correctly.

The work around CUSKs helped clarify this area of the language and prepared the way for more explicit mechanisms such as standalone kind signatures.

The goal is simple: users should be able to tell GHC what kind a declaration has without relying on subtle inference rules.

Lexical negation

Serokell implemented the LexicalNegation extension.

Negative literals look simple, but they are surprisingly tricky to parse.

Consider expressions involving -, custom operators, sections, and whitespace. Is -x a negative variable, an operator application, or something else? What about f -1, f - 1, or a- b?

LexicalNegation improves how GHC treats negation at the lexical level. The result is more predictable parsing of negative literals and fewer surprising edge cases.

This is a good example of a small-looking feature that requires careful language design.

Namespace-specified imports

Serokell implemented namespace-specified imports as part of the ExplicitNamespaces extension.

Haskell has several namespaces. For example, type constructors and data constructors live in different namespaces. With advanced type-level programming, this distinction becomes more visible.

Namespace-specified imports allow users to say more precisely which kind of name they want to import.

This helps avoid ambiguity and makes imports clearer in codebases that use type-level features heavily.

List and tuple puns

Serokell participated in the design and implementation of the ListTuplePuns and NoListTuplePuns extensions.

List and tuple syntax in Haskell has some convenient shorthand forms. However, shorthand syntax can also make the grammar harder to reason about, especially when the same notation appears at both the term and type levels.

The ListTuplePuns work gives programmers and compiler developers more control over this syntax.

This matters because Dependent Haskell requires a cleaner relationship between terms, types, and syntax. Reducing accidental ambiguity is a step in that direction.

Improvements to TypeOperators

Serokell made numerous improvements to TypeOperators, especially around operators such as dot and tilde.

Type operators let programmers write symbolic operators at the type level. They are widely used in type-level programming, embedded DSLs, and advanced libraries.

But operators need well-defined parsing rules. Their precedence, fixity, namespace, and interaction with other syntax must be consistent.

Work on dot, tilde, and related operator behavior makes type-level code more regular and less surprising.

Type syntax at the term level

Serokell worked on parsing type syntax at the term level, including syntax such as forall and the function arrow.

This is an important step towards Dependent Haskell.

Traditionally, Haskell has had a separation between terms and types. Some syntax belongs to expressions, and some syntax belongs to types. But with required type arguments, programmers may need to pass a type where an expression is expected.

For example, a function might accept Maybe Int or Int -> Bool as a visible argument.

That means GHC has to understand type syntax in expression positions. This requires changes to the parser, the AST, name resolution, and error reporting.

It also brings the term-level and type-level languages closer together.

Parser architecture

Serokell contributed improvements to GHC’s parser architecture.

One major change was a new disambiguation mechanism based on a tagless-final encoding. This helps GHC deal with syntax that can mean different things in different contexts.

Haskell’s syntax is difficult to parse because many features overlap. As more type-level syntax becomes available in term-level contexts, this becomes even more challenging.

A better disambiguation mechanism makes the parser easier to extend and maintain.

Serokell also contributed a separate pass for processing Haddock comments. This improves separation of concerns: parsing source code and handling documentation comments should not be unnecessarily tangled together.

Char literals and Nat

Serokell worked on the promotion of Char literals and the unification of Nat with Natural.

Promotion allows term-level constructs to be lifted to the type level. Promoted character literals make it possible to use characters in type-level programming.

Meanwhile, unifying Nat with Natural reduces unnecessary differences between type-level and term-level natural numbers.

Both changes make Haskell’s type-level language more expressive and more consistent with the term-level language.

Dependency analysis for declarations and instances

Serokell implemented a retrying mechanism for type and class declarations and instances, fixing issues in dependency analysis.

GHC has to decide the order in which declarations should be processed. This is easy for simple modules, but difficult when types, classes, instances, and associated declarations depend on each other.

A retrying mechanism lets GHC handle more cases correctly. It reduces the need for users to rearrange code just to satisfy compiler internals.

This is not the kind of feature that users notice every day, but it improves the reliability of the compiler.

Consistent quantification rules

Serokell contributed to consistent quantification rules for type and kind variables.

Quantification determines which type variables are bound, where they are bound, and in what order.

This matters a lot in modern Haskell. With visible type application, type abstractions, type operators, GADTs, and kind polymorphism, the order and visibility of variables can affect how code is written and used.

More consistent quantification rules make GHC easier to predict.

They also make advanced features feel less magical.

GHC proposals

Serokell has authored or contributed to over 15 accepted GHC proposals:

  • #143, “Remove the star kind syntax”, which supports the transition from * to Type.
  • #173, “The dot type operator”, which clarifies the use of dot at the type level.
  • #174, “Lower precedence for the UNPACK pragma”, which improves parsing around strictness annotations.
  • #176, “Meaning-preserving parsing rules for SCC annotations”, which makes profiling annotations behave more predictably.
  • #228, “Function result type signatures”, which improves how result types can be written.
  • #229, “Whitespace-sensitive operator parsing”, which makes operator parsing more predictable.
  • #281, “Visible forall in types of terms”, a key step towards required type arguments and Dependent Haskell.
  • #344, “Negative literals improved”, which refines the treatment of negative literals.
  • #371, “Non-magical type equality operator”, which makes type equality less special.
  • #387, “The Char kind”, which brings characters into type-level programming.
  • #402, “Stable GADT constructor syntax”, which regularizes GADT syntax.
  • #425, “Invisible binders in type declarations”, which improves type declaration binders.
  • #575, “Deprecated instances”, which allows instance deprecation.
  • #640, “Fix quantification order for type operators”, which improves predictability in type-variable ordering.

Taken together, these proposals show a clear direction: make Haskell more regular, more explicit, and more ready for dependent types.

Conclusion

Serokell’s Haskell work spans several layers of the ecosystem. Some contributions are user-facing: Hackage Search, Haskell Certification, and new language extensions.

Some are deep inside GHC: parser architecture, kind checking, dependency analysis, AST changes, and error-message improvements.

Some are strategic: the Dependent Haskell roadmap and the sequence of GHC proposals that move the language towards a more unified term/type system.

The common theme is practicality.

Dependent types in Haskell are not just a research goal. They require years of careful engineering: removing special cases, improving syntax, making quantification predictable, and giving programmers better tools for working with types.

That is the kind of work Serokell has been doing. And there is still more to come.

Banner that links to Serokell Shop. You can buy cool FP T-shirts there!
More from Serokell
dependent haskell post thumbnaildependent haskell post thumbnail
Haskell. History of a Community-Powered LanguageHaskell. History of a Community-Powered Language
Serokell’s Work on GHC: Dependent Types, Part 5Serokell’s Work on GHC: Dependent Types, Part 5