use-profunctor-state icon indicating copy to clipboard operation
use-profunctor-state copied to clipboard

Rename `promap`

Open masaeedu opened this issue 5 years ago • 10 comments

Hi there. Would you please consider renaming the promap method to lens (or something like this)?

This implementation of promap does not appear to unify with the type signature for the promap operation specified in Fantasy-Land (or more generally with how profunctors are specified in other languages). The expected signature is p a b ~> (a' -> a, b -> b') -> p a' b', which does not line up in terms of arity with:

  const celsiusProf = appProf.promap(
    state => fToC(state.fahrenheit),
    (celsius, state) => ({ ...state, fahrenheit: cToF(celsius) })
  );

AFAICT this is a concrete getter setter lens being passed into a method that applies it to state and setState.

While there is a way to utilize a profunctor lens for the same purpose, to do this you have to lawfully implement promap, and then use a profunctor lens to transform a value of the lawful profunctor type.

masaeedu avatar Apr 30 '19 21:04 masaeedu

Thanks for the note, though this library does not specify compliance with Fantasy Land.

While there is a way to utilize a profunctor lens for the same purpose, to do this you have to lawfully implement promap, and then use a profunctor lens to transform a value of the lawful profunctor type.

Could you suggest how this would be done in this library?

staltz avatar May 30 '19 11:05 staltz

I think his point is the state itself is not a profunctor, a profunctor is a generic type with two generic parameters where one is contravariant, and the other is covariant. The lens is the profunctor, not the state. So calling it useProfunctorState and giving it a method promap is a bit of a misnomer. Maybe useZoomableState or useFractalState would be a better name?

DylanRJohnston avatar Jan 13 '20 01:01 DylanRJohnston

Actually I don't think lenses are profunctors, or at least I'm having a very hard time writing a promap definition for one. This is as close as I could get

export const promap = <T, S, A, B>(
  lens: Lens<T, S>,
  f: (a: A) => T,
  g: (s: S) => B,
): Lens<A, B> => ({
  get: outer => g(lens.get(f(outer))),
  set: (inner: B, outer: A) => /* need the inverse of f*/ lens.set(inner /* need the inverse of g */, f(outer))
});

The getter works fine, but I can't write the setter because I need a function from (t: T) => A and (b: B) => S. I guess this kind of intuitively makes sense, since profunctors are generalisations of arrows, so the getter can be written fine, but the setter works in the opposite direction. So maybe Lenses are actually some kind of pair of profunctors?

DylanRJohnston avatar Jan 13 '20 02:01 DylanRJohnston

From the Haskell definition,

type Lens ta tb a b = forall p. Strong p => p a b -> p ta tb

So a lens Lens ta tb a b takes a profunctor (a function / arrow / some other relation) between a and b, and lifts it into a (function / arrow / some other relation) between ta and tb.

So lenses aren't profunctors, but operations on profunctors?

DylanRJohnston avatar Jan 13 '20 02:01 DylanRJohnston

@DylanRJohnston The "profunctor" in "profunctor optics" is essentially a reference to the fact that there is a particular way to encode optics as transformers of arbitrary types that support an instance of the Profunctor typeclass (and various subclasses). This is as opposed to various other ways of encoding optics, such as "getter setter lenses" (as seen in this library), or "van Laarhoven lenses" (very close to what the Haskell lens library used until recently).

You're actually more or less correct in the first comment that lenses are themselves profunctors. The missing bit is that it's profunctor lenses (which have 4 type parameters) that are profunctors in their S and T parameters (which represent the "large" input and output). This is ultimately what composition of lenses is: you use a profunctor lens (a particular kind of "profunctor adjuster") to adjust another profunctor lens, which itself is a profunctor.

You should check out the PureScript profunctor optics library or this talk for a much better explanation.

masaeedu avatar Jan 13 '20 02:01 masaeedu

That's what I thought, but I was unable to write the profunctor instance for Lenses above? Are you able to figure out how to write it? Or is it not possible for this definition of Lenses because it only has the two type parameters?

DylanRJohnston avatar Jan 13 '20 02:01 DylanRJohnston

@DylanRJohnston For the Haskell definition you gave:

type Lens ta tb a b = forall p. Strong p => p a b -> p ta tb

You can in fact write an instance for profunctor, but you need to shuffle the type parameters around first:

newtype Wat a b ta tb = Wat { unWat :: Lens ta tb a b }

instance Profunctor (Wat a b)
  where
  ...

If you want to do it in TypeScript, try dimapping in the last two type parameters of:

type Lens<A, B, S, T> = { get: (s: S) => A, set: (b: B) => (s: S) => T }

or whatever the closest valid TypeScript is.

masaeedu avatar Jan 13 '20 02:01 masaeedu

Yep, the expanded definition is super easy to write

type Lens<A, B, S, T> = {
  get: (s: S) => A;
  set: (b: B, s: S) => T;
};

const promap = <A, B, S, T, U, V>(
  lens: Lens<A, B, S, T>,
  f: (u: U) => S,
  g: (t: T) => V,
): Lens<A, B, U, V> => ({
  get: u => lens.get(f(u)),
  set: (b, u) => g(lens.set(b, f(u))),
});

DylanRJohnston avatar Jan 13 '20 02:01 DylanRJohnston

Right, so with the expanded definition, promap does make a new Lens quite easily,

type Lens_<S, A> = Lens<A, A, S, S>;

interface Foo {
  foo: string;
}

const fooLens: Lens_<Foo, string> = {
  get: outer => outer.foo,
  set: (inner, outer) => ({ ...outer, foo: inner }),
};

interface Bar extends Foo {
  bar: number;
}

const newLens = promap(
  fooLens,
  (bar: Bar): Foo => ({ foo: bar.foo }),
  (foo: Foo): Bar => ({ ...foo, bar: 3 }),
);

However, the new Lens can no longer be simplified as a Lens_, unless g is the inverse of f, but since g doesn't have access to the previous value it has to completely overwrite it.

DylanRJohnston avatar Jan 13 '20 02:01 DylanRJohnston

The type Lens_<-, -> does not support an instance of Profunctor (although it's probably a profunctor between a different set of categories), if that's what you're trying to figure out.

The type Lens is contravariant in S and B, and covariant in A and T. Once you set S and A to be the same type you can't really contravariantly map A and likewise you can't covariantly map B.

masaeedu avatar Jan 13 '20 03:01 masaeedu