– Tom Ellis, September 2026
When I released the first version of my Haskell package
Bluefin in early
2024 I called it an “effect system”, specifically, an “IO-wrapper”
(or “analytic”) effect system1, in which regard it follows
effectful. Now in
late 2026 I have decided to start referring to Bluefin as a
“capability system” first and an “effect system” second, that is,
something like “a capability system that can be used to obtain the
benefits of a traditional Haskell effect system”.
A capability system is a programming language framework for working with “capabilities”, a capability being a program entity that references a program or system resource and, moreover, grants authority to access that resource.
In a capability system, each capability points from a subject to a resource. Consequently every capability can serve both to designate which resource to access, and to provide the authority to perform that access.2
An example of a capability that most programmers will have encountered is the file handle. In the words of Lampson3:
A familiar example in operating systems is a file descriptor or file handle for an open file. When a process opens the file, the OS [after checking that the process has sufficient rights to access the file] creates a handle for the open file.
The notion of capability was originally introduced for security purposes, as a model for access control and protection in operating systems4. se4L is a capability-based kernel with an executable specification written in Haskell! Capabilities are also used in programming languages (e.g. Pony), where they are often referred to as “object capabilities”.
After writing Bluefin I realised that there’s no reason to view capabilities in a narrow, security-only context. Effect systems can be considered to be capability systems. Read on to learn more.
In Haskell the notion of “effect system” is not precisely defined. However, all usages of the terminology have something important in common: an effect system allows the programmer to circumscribe the range of externally visible behaviours of program components.
Interpreted narrowly, “effect system” might mean a library in the
style of
extensible-effects5,
in which all effects take place in a specific monad (e.g. in
effectful
“Eff”)
and the possibility of an effect is indicated by a constraint, i.e. at
the type level. For example, the following function (using types from
effectful) may only interact with a database and/or throw an
exception of String:
operationEffectful ::
(Database :> es, Error String :> es) =>
Eff es ()A slight broadening of this narrow interpretation permits Bluefin to be described as an effect system. In Bluefin, the difference is that the possibility of an effect is indicated by a value-level argument, for example:
operationBluefin ::
Database es ->
Throw String es ->
Eff es ()Further stretching the definition allows MTL to be considered an effect system:
operationMTL ::
(MonadDatabase m, MonadError String m) =>
m ()or even
transformers:
operationTransformers ::
DatabaseT (Except String) ()A very broad definition allows
ST
to be considered an effect system (that only allows mutable state
effects) and even IO (which allows mutable state, exceptions, and
the whole kitchen sink of interactions with external resources).
(The order in which the effect systems were presented here is close to the opposite of the order in which they were developed historically. For more information see my talk “A History of Effect Systems”.)
It is important to emphasise that the reason effect systems are useful is not that they allow the programmer to define program components that perform effects. Rather, they are useful because they allow the programmer to define program components that are forbidden from performing effects outside a specified set.
In the extreme we can forbid all externally visible effects, thereby
using the type system to enforce that our operation must be what is
sometimes called a “pure function” or a “mathematical function”. The
types for an operation that takes a String and returns an Int,
performing no externally visible effects, might look as follows in
different effect systems:
computeEffectful :: String -> Eff es Int
computeBluefin :: String -> Eff es Int
computeMTL :: Monad m => String -> m Int
computeTransformers :: String -> Identity IntIn ST and IO there is no way of circumscribing the externally
visible effects short of not using them at all, so we could settle for
the following (which has no effect system in sight):
computeFunction :: String -> IntIn a capability system there are operations you can only perform if
you have access to a capability that provides authority to perform
them. Above we saw an example: you can only write to an open file if
you its handle in scope. Correspondingly, in an effect system there
are effectful operations you can only perform if their corresponding
effect is somehow “in scope”. For example, you can only throw an
exception if some sort of error, exception or throw effect is in
scope (in extensible-effects and descendants and MTL, at the type
level; in Bluefin at the value level; in transformers provided by a
monad transformer). What, then, is the difference between
capability systems and effect systems? I think there is no
difference!
Let’s investigate the supposed correspondence a bit more closely. Can
all capabilities be treated as effects? When introducing capability
systems above we noted that authority to access a file is provided by
a capability, often called a “file descriptor” or “file handle”.
Likewise, the authority to access a database would be considered a
capability, generally provided through a “connection handle”. The
operation* examples above show how different Haskell effect systems
represent such a capability. Haskell effect systems already have the
ability to pass around, implicitly or explicitly, a program resource
that is required to use a particular program component.
How about the converse? Can all effects be treated as capabilities? I think yes. In order to perform a particular effectful operation something has to be “in scope”. It can be interpreted as the capability to perform that effect.
Let’s look at a few examples of effects as capabilities. Firstly, a
state effect can be interpreted as a capability that grants authority
to interact with a mutable value of a given type. I think it is
particularly interesting to look at the case of ST, IO and
Bluefin, where STRef, IORef and
Modify
respectively provide what is traditionally called a “mutable
reference”, at the value level. A “mutable reference” matches exactly
the definition of capability we saw earlier (“a program entity that
references a program or system resource and, moreover, grants
authority to access that resource”). A mutable reference is a pointer
which references some mutable region of memory and “grants access to
it”, i.e. if you hold such a mutable reference you can use it to
modify the mutable region of memory.
Secondly, an exception effect can be interpreted as the capability
which grants the authority to throw a value of a particular type to a
particular handler. Again, I find the case of Bluefin particularly
interesting: a value of type
Throw
grants access to the exception mechanism, allowing you to throw an
exception to the handler that was installed by
try
(or catch or handle), the same site that introduced the Throw
into scope6.
Thirdly, a reader effect can be interpreted as the capability to
interact with some thread-local, locally-mutable state. No such
primitive mutable state reference exists in Haskell at present, but
there is a proposal to add
one. I like
the name IOScopedRef for the new reference type7. (Bluefin and
effectful simulate this kind of reference, as
Ask
and Reader respectively.)
Finally, “I/O effects”, i.e. the ability to interact in arbitrary ways
with Haskell’s runtime system (RTS), can be interpreted as the
capability granting authority to do so. That is to say, effectful’s
type level IOE, Bluefin’s value level IOE, MTL’s MonadIO
constraint and the plain IO monad itself can all be interpreted as
capabilities granting authority to interact with the RTS.
So why have I decided to describe Bluefin as a “capability system”? Because I think that “capability” terminology communicates better than “effect” terminology. Firstly, the latter raises the awkward question of what an “effect” is. One might attempt to define “effect” in terms of opposition to a notion of “pure”, but I’ve never seen a convincing such attempt. In fact I haven’t come across a convincing definition of “pure” either8.
Secondly, I think that, regrettably, the terminology “effect system” gives the wrong impression about Haskell to outsiders, specifically that, unlike other languages, Haskell needs a “special system” to do “effects”. The converse is true: other languages, unlike Haskell, cannot circumscribe the possible range of effects. I hope that using the terminology “capability system” to describe Bluefin moves the perception of Haskell by outsiders a modest amount in a favourable direction.
Thirdly, “effect systems” are used as a software engineering tool for structuring programs in the large, but the name doesn’t fit the use case. A talk by Tom Wells entitled “Encoding Architecture Into Your Code (So Agents Can’t Break It)” describes how to use an effect system to enforce layering in an application by defining abstraction boundaries. The executive summary is that effect systems can be used to constrain architecture. That’s a concise way of summarising a benefit of Haskell I’ve found when programming in the large. But the phrase sounds odd to me. What does architecture have to do with effects? “Capability systems can be used to constrain architecture” sounds much more natural! In fact it’s so natural it’s almost obvious how that works: you give each component in your architecture the capability to perform only what it needs to. (A very interesting open question remains: how do you use your capability system to propagate capabilities to where they are needed?)
Tom Wells’s talk was specifically on the topic of maintaining good architecture when code is generated by AI agents. In a world where the cost of generating technically correct code is declining rapidly but good design is not becoming commensurately easier I believe that effect systems have a critical role to play. Their role is not limited to architecture either: they can help enforce security properties of code at scale. I think the Haskell community will have an easier time explaining how our favourite language helps in that regard, and maybe win a few converts to our community, if instead of calling them “effect systems” we call them “capability systems”.
For more information on “IO-wrapper” or “analytic” effect
systems, as well as the history of effect systems in Haskell in
general, see my talk A History of Effect
Systems↩︎
extensible-effects originates from Extensible Effects: an alternative to Monad
Transformers by
Kiselyov, Sabry and Swords↩︎
The ealiest example of this idea of “scoped exception” may have been Brachthäuser, Schuster, Ostermann in Effects as Capabilities: Effect Handlers and Lightweight Effect Polymorphism.↩︎
See also my article Haskell’s missing mutable reference type↩︎
Unless it’s just “‘pure’ means ‘referentially transparent’”↩︎