monocle-ts icon indicating copy to clipboard operation
monocle-ts copied to clipboard

Changing the type of the structure

Open atennapel opened this issue 7 years ago • 7 comments

In Haskell you can change the type of the structure: type Lens s t a b = s -> (a, b -> t) (or whatever representation you choose) In none of the typescript lens implementations I've seen this. I tried myself but I couldn't implement it this way without losing type inference. Have you ever thought about it?

atennapel avatar Oct 01 '18 20:10 atennapel

without losing type inference

What do you mean? Could you please show your implementation and an example reproducing the issue?

gcanti avatar Oct 02 '18 06:10 gcanti

My attempt at this:

interface Pair<A, B> { fst: A; snd: B }
const pair = <A, B>(fst: A, snd: B): Pair<A, B> => ({ fst, snd });

type Lens<S, T, A, B> = (val: S) => Pair<A, (val: B) => T>;

const _fst = <A, C>(s: Pair<A, C>) => pair(s.fst, <B>(b: B): Pair<B, C> => pair(b, s.snd));
const _snd = <A, C>(s: Pair<C, A>) => pair(s.snd, <B>(b: B): Pair<C, B> => pair(s.fst, b));

function view<S, T, A, B>(l: Lens<S, T, A, B>, val: S): A {
    return l(val).fst;
}
function set<S, T, A, B>(l: Lens<S, T, A, B>, val: S, x: B): T {
    return l(val).snd(x);
}
function over<S, T, A, B>(l: Lens<S, T, A, B>, val: S, f: (x: A) => B): T {
    const r = l(val);
    return r.snd(f(r.fst));
}

const p = pair(1, 'a');
const stringify = (x: number) => '' + x;
const first = view<Pair<number, string>, Pair<any, string>, number, any>(_fst, p);
const stringifyFstP = over<typeof p, Pair<string, string>, number, string>(_fst, p, stringify);

The last two lines won't work unless you explicitly give the generic type parameters.

atennapel avatar Oct 02 '18 06:10 atennapel

These exist in the original Scala monocle library and I've partially ported them for my own projects. If there's interest, I could finish and file a PR. But it would obviate lots of code, since the current optics would be type aliases of the "full" ones.

kylegoetz avatar Oct 05 '20 01:10 kylegoetz

So that means you know a way to implement this that would not force the user to write down the type arguments explicitly every time?

atennapel avatar Oct 12 '20 12:10 atennapel

interface PLens<S,T,A,B> {
    readonly get: (s:S) => A
    readonly set: (b:B) => (s:S) => T
}

interface Foo{
    x:number
}

interface Bar{
    x:string
}

const l: PLens<Foo,Bar,number,string> = {
    get: s => s.x,
    set: b => s => ({ x: b }),
}

const _ = {
    x: 5
}

const got = l.get(_) // TS infers this is type number
const sat = l.set('howdy')(_) // tS infers this is type Bar

console.log('should be 5:', got)
console.log('should be {x:"howdy"}:', sat)

kylegoetz avatar Dec 13 '20 01:12 kylegoetz

That's cool! But does it work with my Pair type above, a type with type parameters.

atennapel avatar Dec 13 '20 04:12 atennapel

Just a note - I think PLens would be required to implement insertAt and updateAt, which are features under discussion

anthonyjoeseph avatar Mar 14 '22 14:03 anthonyjoeseph