Closed benjaminweb closed 3 weeks ago
What design would be an improvement on what we have now?
Here are good options that aren't possible:
Other Options I considered
Operators - we could create operators that mimic what handle and load are doing. Something like this (Using Example.Contacts)
-- I'm not saying these are the best operators, just showing what might be possible.
page :: (Hyperbole :> es, Users :> es, Debug :> es) => Page es '[Contacts, Contact]
page = do
contacts :|: handle contact :$: do
...
Type Operators - Similar to Servant, use type operators instead of a type-level list. Then use the same operators to link the handlers together.
page :: (Hyperbole :> es, Users :> es, Debug :> es) => Page es (Contacts :|: Contact)
page = do
contacts :|: handle contact :$: do
...
Tuples - We could make use tuples instead? They get unweidly with a bunch of handlers though! But that could be solved with some sort of factoring / nested tuples?
-- we could keep the type-level list, or a use a type-level tuple
page :: (Hyperbole :> es, Users :> es, Debug :> es) => Page es (Contacts, Contact)
page = do
handle (contacts, contact) $ do
...
Have any other ideas?
When would we not need this at all? I mean, is there any way to register those handlers via instances (or any other way) so they get automatically pulled in? You do that already for actions but why not for the handlers on the Page level? I might be completely off-track here, but just want to bring this up.
Good question. If they are handled explicitly, you can easily pass app context to a handler, or more importantly, to reuse hyperviews
Context - as in a db connection or something. This isn't that useful, since it's easy to use the Reader
to accomplish the same thing
myHandler :: Connection -> MyView -> MyAction -> Eff es (View MyView ())
myHandler conn _ Save = Db.saveSomething "hello"
More likely, you might want to reuse a hyperview to do different things. Here's an example from a [real application])(https://github.com/DKISTDC/level2/blob/main/src/App/Page/Program.hs#L38):
inversions :: (Hyperbole :> es, ...) => Eff es (View InversionStatus ()) -> InversionStatus -> InversionAction -> Eff es (View InversionStatus ())
inversions onCancel (InversionStatus inversionId) = \case
Cancel -> do
send $ Inversions.Remove inversionId
onCancel
page
:: (Hyperbole :> es, Log :> es, Inversions :> es, Datasets :> es, Auth :> es, Globus :> es, Tasks GenFits :> es)
=> Id Proposal
-> Id Inversion
-> Page es [InversionStatus, ...]
page ip i = do
handle (inversions redirectHome) $ ...
redirectHome :: (Hyperbole :> es) => Eff es (View InversionStatus ())
redirectHome = do
redirect $ pathUrl . routePath $ Inversions
The inversions
handler can be reused on different pages with a different effect for onCancel
. Here is the same handler used on a different page
page
:: (Hyperbole :> es, ...)
=> Id Proposal
-> Id InstrumentProgram
-> Page es [InversionStatus, ...]
page ip iip = do
handle (inversions (clearInversion ip iip)) $ ...
Maybe reusing views like this is a bad idea. I'm not sure. It definitely came up in an app of mine. Maybe a user could "newtype" a hyperview instead for reuse?
There may have been another technical reason why that wouldn't work. I can't remember. I definitely considered it. It would be pretty neat to have the typeclass contain the handler, I think. Maybe it's worth the hassle, if we can come up with another way to reuse the views
Do I get you right, we have two things that exclude each other:
btw I didn't realise that it's possible to reuse views since.
Reusing views this way isn't possible, because you couldn't add a parameter to the handler function if it were in a class:
instance HyperView MyView where
type Action MyView = MyAction
-- I can't pass anything into this!
handler view action = ...
That doesn't mean we couldn't come up with different patterns for view reuse.
@benjaminweb I spent a day trying to figure out how to resolve the handlers via the typeclass. I can't figure out how to make it work with effects
But I had an idea: we can't use lists, but what about tuples?
page :: (Hyperbole :> es) => Page es '[Contacts, Contact]
page = do
-- pass a tuple to handle with all your handlers. This resolves to a Page with the proper handlers.
handle (contacts, contact) $ do
us <- usersAll
pure $ do
col (pad 10 . gap 10) $ do
hyper Contacts $ allContactsView Nothing us
Fixed via #41
Current handle load interface goes like
handle central $ handle presets $ handle handler3 $ load $ do
A handler's function is to catch any actions originating from any views.
handle
currently wraps a handler around other handlers so the actions get caught. A handler will not workCurrent architecture:
'[View1, View2, View3]
will let the body only compile if the body wraps load in all handlersDesirable qualities of new
handle / load
interface design: