The important property of capabilities, as Bluefin treats them, is that they are value-level entities that do not fundamentally differ from other program values (e.g. strings, numbers, …) and can be manipulated in just the same ways (e.g. passing them as arguments to functions, returning them from functions, putting them in tuples and records, closing over them in function definitions, …).
As an aside, the notion of “purity” or “pure function” is awkward when
applied to Haskell. Although computeFunction :: String -> Int could
throw an exception or loop forever, if it doesn’t it seems reasonable
to describe it as computing a pure, mathematical function that maps
String to Int.
There is a misconception that “Haskell is a pure language”, implying
that it “can’t do effects” and needs “something else”—specifically
“monads” and particularly “the IO monad”—to allow it to “do
effects”. This is false. Haskell implementations have always been
able to do more than just calculate the result of mathematical
functions, i.e. perform pure calculations. Rather than “pure”, I
prefer to use “referentially transparent” to describe this aspect of
Haskell. It is not possible for a program component written in a
referentially transparent language to interact with an external
environment without the possibility of that interaction being
reflected in the type of the component.
The problem with IO is that it is “too big”.
Every Haskell program starts in main with the “I/O capability” as
provided by the “IO monad”. Bluefin’s runEff function converts
this into a Bluefin IOE capability:
runEff :: (forall e. IOE e -> Eff e r) -> IO r
From there, various other capabilities (reading and writing the
filesystem, connecting to a database, making HTTP requests …) can be
derived from IOE.
Migrating Bluefin’s terminology from “handle” to “terminology” is
ongoing work. See
Bluefin.Capability