dflemstr
dflemstr

Reputation: 26167

Preferred method for referencing sub-Snaplets

In the Snap Framework, Snaplets are used to embed functionality into other Snaplets via a component-based interface: The main web application is a Snaplet that references other Snaplets via a classical "has-a" relationship, and sub-Snaplets can in turn reference other Snaplets.

When looking at various Snaplet implementations, I've seen different patterns being used to embed a Snaplet into a parent Snaplet. Specifically:

As I see it, reference kind 1. makes sense if the Snaplet is read-only, and 2. makes sense if the Snaplet needs to change.

Further, having a class for the method makes sense when MySnaplet can have only one SubSnaplet and no more, and having an absolute reference might make sense for things like databases, that cannot possibly be configured as a component, given that only the top Snaplet has access to credentials and what not. However, making this assumption as a Snaplet writer might be fallacious, and there would be no disadvantages to using a relative references instead.

There's one proglem, though: Existing Snaplets on Hackage do not fit with these assumptions that I make; all of the methods described above are used seemingly at random and in all kinds of circumstances. Also, I see no advantage/disadvantage to some of the other aspects described above (Such as requiring a Snaplet wrapper, or not).

To me, reference kind 2. and one of methods 1, 2, 5 or 6 seem to make the most sense under all circumstances, and I see no reason why there's not a consensus on only using e.g. (2, 1) all the time.

So:

As a Snaplet writer, which method should be preferred when writing a new Snaplet (assuming that it has a general purpose), and

What is the reason why all Snaplets in existence don't already use the same reference method (Even in the core snap package, a ton of different methods are used)?

Upvotes: 2

Views: 292

Answers (1)

mightybyte
mightybyte

Reputation: 7272

TLDR; Most of the time you probably want to use the with function and relative lenses.

The use of a HasSubSnaplet type class is a completely optional pattern that can reduce the "with subSnaplet" boilerplate in situations where it doesn't make sense to have more than one instance of SubSnaplet. We chose to do that for the Heist snaplet that ships in the snap package because it makes sense for the Heist snaplet and provides users an example of the pattern.

Since the type class is completely optional and roughly orthogonal to the choice of lens, for the rest of this answer I'll focus on what to do without the type class.

The intent of the API is that you use lenses (not "plain references" that get you something wrapped in the Snaplet data type) to access your state. This is because mutating state during the course of request processing is a fundamental ability that we wanted Snaplets to provide. For the most part, we intended for Snaplet to be an opaque wrapper that the end user doesn't need to touch except in their snaplet's state type. This is why the MonadState instance gets you directly to your type without the Snaplet wrapper.

With that said, there are four basic access patterns. We can see these looking at the MonadSnaplet type class.

with     :: Lens     v       (Snaplet v') -> m b v' a -> m b v a
withTop  :: Lens     b       (Snaplet v') -> m b v' a -> m b v a
with'    :: Lens (Snaplet v) (Snaplet v') -> m b v' a -> m b v a
withTop' :: Lens (Snaplet b) (Snaplet v') -> m b v' a -> m b v a

The lens pattern embodied in the first two functions is the kind of lenses that are generated for you automatically by the data-lens-template package with TemplateHaskell and are the most natural to use. Therefore, this is the recommended pattern. (Hence the shorter, non-primed name.) The difference between with and withTop is that with takes a relative lens and withTop takes an absolute lens. Most of the time I use relative lenses. But we wanted to allow the use of absolute lenses because I can imagine complex applications where one snaplet might need to get at something provided by another snaplet, but one that is not a descendant of the current snaplet.

Occasionally there are situations where you want to be able to have an identity lens. That requires a Lens (Snaplet a) (Snaplet b). So the second two primed functions are analogous to the first two except they take this kind of lens.

Upvotes: 1

Related Questions