From a definitional standpoint, a higher-order effect is simply an effect that includes higher-order operations. An effect is a set of operations, and higher-order operations are those that include actions (such as Eff es
or m
) in their argument positions.
I don’t think there’s any inconsistency in how people use these terms. At the very least, I don’t believe that dynamic scoping behavior is part of the definition of higher-order effects. The definition of higher-order effects is formal and symbolic regarding their data structures and is independent of their behavior, semantics, or functionality—that is, independent of the question of “what can be achieved with them.”
I consider them to be on separate axes. I imagine that there’s a broad framework called higher-order effects, within which countless semantic possibilities exist. Among those, we’re searching for user-friendly and practical semantics that are useful to users.
The functionality of higher-order effects lies in being able to handle higher-order operations (like local
or inSpan
) within the same framework as first-order operations (like ask
or get
). In other words, it allows us to uniformly handle both first-order and higher-order operations as first-class effects.
For example, in effectful
, thanks to higher-order effects, the interpose
function—as shown in this post—can be used not only for first-order operations like ask
but also for higher-order operations like inSpan
. (The following is pseudocode):
logOnSpan :: (Trace :> es, Log :> es) => Eff es a -> Eff es a
logOnSpan = interpose \(InSpan spanName action) -> do
log $ "[logOnSpan] Start " <> spanName
inSpan spanName action
log $ "[logOnSpan] End " <> spanName
main = runTrace $ runLog $ do
inSpan "scope1" do
logOnSpan do
inSpan "scope2" do
putStrLn "hello"
{-
> main
[logOnSpan] Start scope2
hello
[logOnSpan] End scope2
-}
I suspect that currently, bluefin
’s framework for implementing effects is quite different from existing ones, and It might be facing difficulties in neatly finding counterparts to higher-order effects within bluefin
. If we were to say that “bluefin supports higher-order effects,” I think that would mean—as mentioned in this discussion—that not only the behavior of ask
but also that of local
can be modified afterward by changing the passing value-level local
effect.
Initially, I thought that bluefin
was propagating evidence (effect handlers) via arguments instead of storing the evidence in a vector, as is done in so-called evidence passing. In this method, for example, the Reader effect type—including local
—would be defined as follows:
data Reader r es = Reader
{ ask :: Eff es r
, local :: forall a. (r -> r) -> Eff es a -> Eff es a
}
However, when I looked into how custom effects are implemented using the Compound
module, it seemed different from what I had imagined. Perhaps passing effect handlers, rather than using IORef
, enables more functionality. But since I’m not very familiar with bluefin
’s mechanisms, please correct me if I’m mistaken.