Fn is pretty unique among Haskell frameworks in how it handles application state.
To use Fn, you create a "context" data type with a field for the FnRequest. You make your context an instance of RequestContext by defining how to access and modify that field.
Here's an example Context from a real (simple) production project:
data Ctxt = Ctxt { _req :: FnRequest , _db :: Pool PG.Connection , _heist :: FnHeistState Ctxt } makeLenses ''Ctxt instance RequestContext Ctxt where requestLens = req instance HeistContext Ctxt where getHeist = _heist
It holds a pool of database connections and the state for our Heist templates as well as the FnRequest.
You pass the context into any function that requires it (routes, handlers, whatever).
In contrast, Yesod, Snap, and Scotty are few examples of popular Haskell web frameworks that use "monad transformer stacks" to represent the state of an application.
[Caption: Transformers robots representing monad transformers. Sadly not stacked. The one that turns into a gun is definitely IO.]
So why doesn’t Fn have types like StateT Ctxt IO a instead of Ctxt -> … -> IO a? If everyone else is using monad transformers, what makes us think our way is better?
For beginners, it’s pretty easy to justify -- you can understand everything about Fn without needing to understand monad transformers, and you can use Fn with only a basic understanding of the IO and Maybe monads.
That seems absolutely worth the tradeoff of having to pass ctxt to functions.
For advanced programmers, it’s a little bit more subtle, but still important.
It essentially boils down to wanting Fn to interact as seemlessly as possibly with non-Fn code.
If we were in StateT Ctxt IO a every time we call a function in IO, we have to call lift or liftIO. So there are places where it cuts down on typing!
Also, related, when writing functions, it encourages you to think if you actually need to have the context.
If you were in StateT Ctxt IO, and want to write a helper, the easiest thing is for it to be in the same monad, which means by default you’re building up functions that are, by definition, harder to re-use (as they all need a Ctxt to run). You can, of course, write helpers in IO, but then you need to call them with liftIO, and you may need to figure out how to extract whatever piece of the Ctxt that the helper needs (for example, if you have a helper that just needs to use the database connection).
In contrast, when you already have a Ctxt in scope, and you are in the IO monad, it is harder to write a helper that needs the Ctxt (as you have to pass it in! So strictly more typing!), so if your helper doesn’t need it, that’s great! Also, it is essentially as easy to write a helper that just needs a subset of the Ctxt than a helper that needs the whole thing. So it also encourages you to have less coupled functions.
Finally! it makes it (slightly) easier to re-use these functions outside of the context of Fn. You can call with myFunction ctxt arg1 arg2 rather than runStateT ctxt $ myFunction arg1 arg2.
For advanced programmers, it’s all pretty marginal, but in our opinion, the margins favor our decision.
And for beginners, it’s an absolute win. So, slight win for advanced programmers, massive win for beginners = win.