5

I stumbled upon Getter definition having both Functor and Contravariant constraint on f.

It's not surprising that "getter" can't do much with the "contained part" but this signature looks like a Phantom in a "van Laarhoven" setting of (a -> f a) -> s -> f s. Is the implicit constraint "s knows about a" represented this way in lens?

How can I find source code of some concrete instances of a Getter so that I can see map and contramap being used?

sevo
  • 4,559
  • 1
  • 15
  • 31

3 Answers3

8

The idea of a Getter is that it is a read-only lens. Given a Getter s a you can pull an a out of an s, but you can't put one in. The type is defined thus:

type Getter s a = forall f. (Contravariant f, Functor f) => (a -> f a) -> s -> f s

When a type is both a Functor and Contravariant, it actually doesn't depend on its type argument at all:

import Data.Void

change :: (Functor f, Contravariant f) => f a -> f b
change = fmap absurd . contramap absurd

Such a functor will always look very much like Const b for some b.

So a Getter s a is essentially

type Getter s a = forall b . (a -> b) -> s -> b

but to make it work with the rest of the lens ecosystem it has extra polymorphism.

dfeuer
  • 48,079
  • 5
  • 63
  • 167
5

Well, a getter is basically just a function. The isomorphism is this:

getter :: (Functor f, Contravariant f) => (s->a) -> (a->f a) -> s->f s
getter f q = contramap f . q . f

Here, the contramap will in effect do nothing but coerce the types, because as you say, combining Functor and Contravariant amounts to requiring that f x doesn't actually contain an x. Basically, ensuring this is also the only reason the Functor constraint is there.

leftaroundabout
  • 117,950
  • 5
  • 174
  • 319
  • 1
    What would you get if you didn't have the Functor constraint? It'd still be producable from an (s -> a), and turnable into an (s -> a), right? – Gurkenglas Aug 21 '16 at 19:50
  • Well, to go back to a plain function you just need to instantiate `f` suitably, usually done with `Const`, which of course happens to have a `Functor` instance too. – leftaroundabout Aug 21 '16 at 19:56
  • Yea but you could also use other `Contravariant`s that happen not to have a Functor instance, right? Or do you mean that wouldn't give us more power, because you can turn a Getter into a function, and use that function to construct the thing that we wanted to do that couldnt provide a Functor constraint? – Gurkenglas Aug 21 '16 at 20:15
  • Follow the types they said! I just revisited this and there is a story right there in the module documentation! I'd love to accept this answer, may I ask for some hint for understanding CPS tranfromation for completeness? https://hackage.haskell.org/package/lens-4.16.1/docs/Control-Lens-Getter.html – sevo Jul 02 '18 at 17:51
1

The type

Getter s a = forall f . (Contravariant f, Functor f) => (a -> f a) -> (s -> f s)

is isomorphic to the type s -> a. The two sides of the isomorphism are given by

toGetter :: (s -> a) -> Getter s a
toGetter h alpha = contramap h . alpha . h
fromGetter :: Getter s a -> (s -> a)
fromGetter getter = getConst . getter Const

It is not hard to see that fromGetter (toGetter h) equals h.

Up to this point (i.e. in order to implement toGetter and fromGetter and to prove that fromGetter . toGetter = id) we have not used the Functor f constraint. This constraint is however necessary in order to prove that toGetter . fromGetter = id.


First assume that f is both a covariant Functor and a Contravariant functor. Then any function g :: x -> y yields functions fmap g :: f x -> f y and contramap g :: f y -> f x. Parametricity, combined with the functor laws, entails that these are mutually inverse, i.e. f x ≅ f y. Hence, f is (up to isomorphism) a constant functor, so we can think of Getter s a as being defined as

Getter s a = forall f0 . (a -> b) -> (s -> b)

(as mentioned in dfeuer's answer). By the Yoneda lemma, this is isomorphic to s -> a.


Remarkably, if we lift the Functor f constraint:

OddGetter s a = forall f . Contravariant f => (a -> f a) -> (s -> f s)

then we obtain a subtype of Getter s a that is no longer isomorphic to s -> a, but instead to s -> Aux a s:

newtype Aux a x = Aux {aAux :: a, gAux :: x -> a}
instance Contravariant (Aux a) where
  contramap f (Aux a g) = Aux a (g . f)

toAux :: a -> Aux a a
toAux a = Aux a id

(Aux a, toAux) is the initial of all pairs (F, toF) where F is a contravariant functor and toF :: a -> F a, similar to how (Const a, Const) is the initial of all pairs (F, toF) where F is a co- and contravariant functor and toF :: a -> F a.

The two sides of the isomorphism can be implemented as follows:

toOddGetter :: (s -> Aux a s) -> OddGetter s a
toOddGetter sigma alpha s1 =
    contramap (\s2 -> gAux (sigma s1) s2) $ alpha $ aAux (sigma s1)
fromOddGetter :: OddGetter s a -> (s -> Aux a s)
fromOddGetter getter = getter toAux

Again, it is straightforward to check that fromOddGetter . toOddGetter = id, which already shows that OddGetter s a is not isomorphic to s -> a. To prove that fromOddGetter . toOddGetter = id, we again need a parametricity argument.

Parametricity entails that, for any natural transformation nu :: forall x . d x -> f x, any getter :: OddGetter s a and any alphaD :: a -> d a, we have

nu . getter alphaD = getter (nu . alphaD) :: s -> f s

Now, we instantiate d with Aux a, alphaD with toAux and nu with factor alpha (for an arbitrary alpha : a -> f a):

factor :: (a -> f a) -> forall x . Aux a x -> f x
factor alpha (Aux a g) = contramap g $ alpha a

which has the property that factor alpha . toAux = alpha. Then we have

factor alpha . getter toAux = getter (factor alpha . toAux) = getter alpha :: s -> f s

Now when we apply this to some s1 :: s, we find that getter alpha s1 (the applied RHS) equals

factor alpha (getter toAux s1)
  = contramap (gAux $ getter toAux s1) $ alpha (aAux $ getter toAux s1)
    {-by definition of factor-}
  = contramap (\s2 -> gAux (getter toAux s1) s2) $ alpha $ aAux (getter toAux s1)
    {-by eta-expansion and regrouping-}
  = toOddGetter (getter toAux) alpha s1
    {-by definition of toOddGetter-}
  = toOddGetter (fromOddGetter getter) alpha s1
    {-by definition of fromOddGetter-}

i.e. getter = toOddGetter (fromOddGetter getter).


Given the isomorphisms, it is perhaps remarkable that the type

OddGetter s a ≅ s -> Aux a s

is a subtype of

Getter s a ≅ s -> a

The "coercion" function

\ getter -> getter :: OddGetter s a -> Getter s a

corresponds to the function

\ sigma -> aAux . sigma :: (s -> Aux a s) -> (s -> a)

under these isomorphisms.

dremodaris
  • 358
  • 1
  • 9
  • Possibly best answer out there but I suspect few people could tell whether it's right or wrong! – sevo Sep 12 '18 at 18:03