calmm-js / partial.lenses

Partial lenses is a comprehensive, high-performance optics library for JavaScript
MIT License
915 stars 36 forks source link

Document object template of lenses #96

Closed ivan-kleshnin closed 7 years ago

ivan-kleshnin commented 7 years ago

Docs mention "object template of lenses" few times but it's never documented. By trial and error I figured out that currently it supports plain values and arrays but not nested values.

For example, I want to pick meta.file property chain from a given object.

let obj = {meta: {file: "./foo.txt", base: "foo", ext: "txt"}}
L.get(L.pick({file: ["meta", "file"]}), obj}) // {file: "./foo.txt"}

now I want to grab both file and ext but not base keeping the original nesting:

let obj = {meta: {file: "./foo.txt", base: "foo", ext: "txt"}}
L.get(L.pick({???}), obj}) // {meta: {file: "./foo.txt", ext: "txt"}}

I guess it's not possible with L.pick (because output is a product hence a plain collection), but not sure because it's unclear what "template of lenses" is.

P.S.

I'm aware of a solution:

R.pipe(
  L.set(["meta", "file"], L.get(["meta", "file"], obj)),
  L.set(["meta", "ext"], L.get(["meta", "ext"], obj))
)({})

I just wondered maybe I missed a shorter option.

polytypic commented 7 years ago

Yes, the documentation could be clarified here. The template for L.pick cannot directly be a nested object. However, you can do nested things by nesting optics. For example (see in playground):

let obj = {meta: {file: "./foo.txt", base: "foo", ext: "txt"}}
L.get(L.pick({meta: ["meta", L.props("file", "ext")]}), obj)

Don't have time to think this through right now, but there might be ways to allow for some form of nested version of L.props to make this kind of usage nicer.

polytypic commented 7 years ago

Here is a kind of nested cross of L.pick and L.props (try in playground):

let obj = {meta: {file: "./foo.txt", base: "foo", ext: "txt"}}

const nested = t => (I.isObject(t)
                     ? L.pick(L.modify(L.values, (v, k) => [k, nested(v)], t))
                     : t)

L.get(nested({meta: {file: [], ext: []}}), obj)

You can give it an arbitrary nested template of (plain) objects whose leaf props must be lenses.

Does this seem like what you'd want and what would be a good name for this? L.pickNested? L.pickIn?

ivan-kleshnin commented 7 years ago

Wow, recursive lensing :)

I'm getting

Error: partial.lenses: pick expects a plain Object template.

with the last Partial.Lenses version available. Unpublished update to L.pick?

polytypic commented 7 years ago

Hmm... Note that the isObject predicate from the infestines library basically tests that the constructor of the given value is Object. IOW, it doesn't just test that the given value inherits from Object.

polytypic commented 7 years ago

BTW, thanks for opening this issue! I have to think about generalizing L.branch and L.pick (to accept nested templates) and possibly adding the new L.pickIn (aka nested above).

Here is a generalized (nested) branch combinator (try in playground):

const branch = t => I.isObject(t) ? L.branch(L.modify(L.values, branch, t)) : t

L.modify(branch({x: {a: []}, y: {b: []}}),
         R.negate,
         {x: {a: 1, b: 2}, y: {a: 3, b: 4}})

Here is a generalized (nested) pick combinator (try in playground):

const pick = t => I.isObject(t) ? L.pick(L.modify(L.values, pick, t)) : t

const data = {s: 1, t: 1}
const lens = pick({x: {a: "s"}, y: {b: "t"}})

R.identity({
  get: L.get(lens, data),
  set: L.set(lens, {x: {a: 1}, y: {b: 2}}, data)
})

Here is also the (newly proposed) generalized pickIn combinator (try in playground):

let obj = {meta: {file: "./foo.txt", base: "foo", ext: "txt"}}

const pickIn = t => (I.isObject(t)
                     ? L.pick(L.modify(L.values, (v, k) => [k, pickIn(v)], t))
                     : t)

L.get(pickIn({meta: {file: [], ext: []}}), obj)

BTW, I believe it is also time to seriously think about obsoleting L.augment (this has been on my mind for some time already).

ivan-kleshnin commented 7 years ago

Hi, Vesa. Sorry for not participating – mad months at work :( pickIn looks like a reasonable solution.

polytypic commented 7 years ago

No problem. I was also just on a tightly scheduled project and finally got some time & energy to finish the initial pickIn implementation. Thanks for opening the issue!