Hello, I recently discovered the handle pattern and it looks too good to be true :) I first tried the mtl style approach with the ReaderT pattern but I find it too difficult to implement custom instances for testing. With the Handle pattern it seems simple to create a new handle with pure functions to mock the IOs, without using special extensions or typeclass.
So I am trying this pattern with an application slightly more complicated than i am used to do in Haskell. I am working on a service to query monitoring alerts, then run custom command and send report to operators over irc... Is this a good design to begin with and would you recommend it?
I don't know how you would measure, "good." If it was me I would be satisfied if it allowed me to update my application as it evolved without too much hassle.
If I'm building a web application and I can change the database serialization code without affecting the route-plumbing code then that's good!
If I end up having to structure my application in highly-coupled ways in order to satisfy the architecture... I find that not-so-good.
I would argue that variant of such approach could actually be treated as full-fledged effect system, and that it isn't really that far of existing ones:
Let's have
Just by parametrizing handle over monad, you "recover purity" by abstracting out IO and easily get injection - you can run your app in mocked environment, multiple alternative environemts (e.g. on client and server) or easily modify or swap final monad if it no longer fits your needs. You simply use it like
foo::Appm->..->mStufffooh=do..user<-createUserh"Tristan"logh"Created new user"..
If your app grows in size and responsibilites of specific parts get more concrete, you can split it's fields into subhandles that you can then pass to specific subparts - this way you get more flexible if those subparts need different set of functionality from their monads.
One cherry on top - with RecordDotSyntax we'll be able to write
h.createUser"Tristan"
And another one - you can use ImplicitParams to recover implicit handle passing:
typeHasAppm=?app::Appmmain=let?app=stuffin..foo::HasAppm=>..->mStufffoo=do..user<-?app.createUser"Tristran"?app.log"Created new user"..bar::HasAppm=>..->mOtherStuffbar=do..stuff<-foo....
All of that without traditional "class mess" :smile:
Thank you very much for the insightful comments. If I understand correctly, handles are not as fine grained as other effect system, but designing my application with handles works for me so far, and most importantly, it is easy to explain. I also like the consistency of module that exports a common Config, Handle and withHandle api.
Parametrizing the handle over monad looks like a great improvement, thank you for the snippets @TheMatten ! It's also nice to see how ImplicitParams can be used, though I rather avoid using extensions so that i don't have to explain them :-)
which is, i think, the kind of what you hope to have from a thing that is a whole-application infrastructure thing. because as soon as you touch it you're going to be fixing other parts of the codebase which have nothing to do with what you're working on, so guaranteed annoying
Hello, I recently discovered the handle pattern and it looks too good to be true :) I first tried the mtl style approach with the ReaderT pattern but I find it too difficult to implement custom instances for testing. With the Handle pattern it seems simple to create a new handle with pure functions to mock the IOs, without using special extensions or typeclass.
So I am trying this pattern with an application slightly more complicated than i am used to do in Haskell. I am working on a service to query monitoring alerts, then run custom command and send report to operators over irc... Is this a good design to begin with and would you recommend it?
I don't know how you would measure, "good." If it was me I would be satisfied if it allowed me to update my application as it evolved without too much hassle.
If I'm building a web application and I can change the database serialization code without affecting the route-plumbing code then that's good!
If I end up having to structure my application in highly-coupled ways in order to satisfy the architecture... I find that not-so-good.
If the Handle Pattern works for you, it's probably a good pattern. :)
I would argue that variant of such approach could actually be treated as full-fledged effect system, and that it isn't really that far of existing ones:
Let's have
Just by parametrizing handle over monad, you "recover purity" by abstracting out
IO
and easily get injection - you can run your app in mocked environment, multiple alternative environemts (e.g. on client and server) or easily modify or swap final monad if it no longer fits your needs. You simply use it likeIf your app grows in size and responsibilites of specific parts get more concrete, you can split it's fields into subhandles that you can then pass to specific subparts - this way you get more flexible if those subparts need different set of functionality from their monads.
One cherry on top - with
RecordDotSyntax
we'll be able to writeAnd another one - you can use
ImplicitParams
to recover implicit handle passing:All of that without traditional "class mess" :smile:
isn't that tagless final?
Well, thing is, in Haskell, all "ad-hoc polymorphism" is some sort of "tagless final" at the end :big_smile:
right :smile:
Thank you very much for the insightful comments. If I understand correctly, handles are not as fine grained as other effect system, but designing my application with handles works for me so far, and most importantly, it is easy to explain. I also like the consistency of module that exports a common
Config
,Handle
andwithHandle
api.Parametrizing the handle over monad looks like a great improvement, thank you for the snippets @TheMatten ! It's also nice to see how
ImplicitParams
can be used, though I rather avoid using extensions so that i don't have to explain them :-)we're using it. So far i haven't found it to be too annoying
which is, i think, the kind of what you hope to have from a thing that is a whole-application infrastructure thing. because as soon as you touch it you're going to be fixing other parts of the codebase which have nothing to do with what you're working on, so guaranteed annoying