Home > Mobile >  What is a `Prism' s a` but with a context `ctx`?
What is a `Prism' s a` but with a context `ctx`?

Time:03-16

A type Prism' s a = Prism s s a a (hackage) can be thought of as a relation between some structure s and its member a, such that you can always produce the structure from the member (a -> s), but can only optionally retrieve the member from the structure (s -> Maybe a).

This model is helpful in relating a sum type to one of its constructors ... as well as (more relevant here) in route encoding and decoding. If s is the encoded route URL, and a is the route type, then we have a -> s representing the encoding function (always succeeds), and s -> Maybe a representing the decoding function (can fail).

Now, on to these pairs of functions, I want to add a "context" argument that is to be used in the encoding and decoding process (imagine that the decoding process needs to "look up" some database before successfully producing a relevant route). Basically:

encode :: ctx -> a -> s 
decode :: ctx -> s -> Maybe a

Is there a type that models these conversions? It looks very much like a Prism' but with an extra ctx argument.


As a next step, I'd like to define a functor for this prism such that it can transform all three types: ctx, s, a. I currently have a class like this, but it appears I might be missing an existing library that I could use to simplify all of this:

class PartialIsoFunctor (f :: Type -> Type -> Type -> Type) where
  -- x, y are the context
  -- a, b are the structure `s`
  -- c, d are the route types `a`
  pimap ::
    Prism' b a -> -- Note: contravariant prism
    Prism' c d ->
    (y -> x) -> -- Note: this is contravariant
    f x a c ->
    f y b d

The idea here is that there is a RouteEncoder ctx r type (see also) representing a value that knows how to encode/decode routes. And I want to be able to transform these route encoders on the r, ctx, and the URL string (internally a FilePath, actually) it encodes to/ decodes from.

Notes:

  • I use optics-core rather than lens.

EDIT: Here's my current approach:

type RouteEncoder ctx s route = Prism' (ctx, s) (ctx, route)

And the functor that transforms it:

mapRouteEncoder ::
  Prism' b a ->
  Prism' c d ->
  (y -> x) ->
  RouteEncoder x a c ->
  RouteEncoder y b d
mapRouteEncoder = undefined

The encoding/decoding functions:

-- The use of `snd` here suggests that the use of tuples in 
-- RouteEncoder is a bit of a hack

encodeRoute :: RouteEncoder ctx r -> ctx -> r -> FilePath
encodeRoute enc ctx r = snd $ review enc (ctx, r)

decodeRoute :: RouteEncoder ctx r -> ctx -> FilePath -> Maybe r
decodeRoute enc m s = snd <$> preview enc (ctx, s)

How can this be simplified? Note that RouteEncoder's are created ahead, and composed. But the actual encoding/decoding happens later, passing the 'ctx' that varies over time.

CodePudding user response:

I'm with @HTNW. You should just be able to define:

type RouteEncoder ctx s route = ctx -> Prism' s route

Then, pimap is defined as:

pimap :: Prism' b a -> Prism' c d -> (y -> x)
  -> RouteEncoder x a c -> RouteEncoder y b d
pimap p q f r ctx = p . r (f ctx) . q

and encodeRoute and decodeRoute are defined as:

encodeRoute :: RouteEncoder ctx s r -> ctx -> r -> s
encodeRoute enc ctx r = review (enc ctx) r

decodeRoute :: RouteEncoder ctx s r -> ctx -> s -> Maybe r
decodeRoute enc ctx s = preview (enc ctx) s

The only difficulty is that composition of route encoders with deferred context requires some additional syntax. You may need to write:

\ctx -> otherLens . myRouteEncoder ctx . otherPrism

or similar instead of simple composition. However, your existing solution doesn't compose particularly well with other optics either, unless they are "aware" of the context.

I'm not 100% sure what you mean when you ask how getter functions inside the prism access the context outside the prism. If you mean at definition time, then the answer is that you just use something like:

makeRouteEncoder a b c ctx = prism (f a b ctx) (g c ctx)

myRouteEncoder = makeRouteEncoder myA myB myC

CodePudding user response:

If your context is a sort of "configuration" that applies to everything going on in the same region of your program, then you might consider using reflection. You can use

type RouteEncoder ctx s r = ctx => Prism' s r

The context becomes implicit. You'll have something like

ctx ~ HasFoo
s ~ Whatever x

This is only really ergonomical if you have one context that works throughout. However, you can use the native class relationships to add some flexibility there.

  • Related