1

Community, hello . I stuck with a small issue. I have such a data structure

{
    "type": "Shoes",
    "gender": "female",
    "userInfo": {
        "whois": {
            "male": {
                "htt": 20.2,
                "referenceBrands": [{
                    "category": "Shoes",
                    "size": 40
                }]
            },
            "female": {
                "height": 191.0,
                "age": 20,
                "referenceBrands": [{
                    "category": "Shoes",
                    "size": 35.5
                }, {
                  "category": "Apparel",
                  "size": 50
                }]
            }
        },
    }
}

I want to update a specific key in a reference brand based on gender and a type which are in a root. Based on this answer (thanks to Scott Sauyet), I've created something like this. But as you see, I have hardcoded gender and category what the best way of using composition and point-free style to change this?

const lensMatch = (propName) => (key) => lens( 
   find(
      propEq(propName, key)
   ), 
   (val, arr, idx = findIndex(propEq (propName, key), arr)) =>
         update(idx > -1 ? idx : length (arr), val, arr)
   )

const lensCategory = lensMatch('category')
const genderLens = lensProp('gender')
const lensReferenceBrand = lensProp('referenceBrands')

const updateSize = (prop, value) => over(
  compose(
    lensPath(['userInfo', 'whois']),
    lensProp('female'),
    lensReferenceBrand,
    lensCategory('Shoes'),
    lensProp(prop),
  ), always(value), data)

updateSize('size', 100)

Playground

Dmytro Filipenko
  • 861
  • 1
  • 9
  • 25

1 Answers1

1

I would probably write it something like this:

const lensMatch = (propName) => (key) => 
  lens
    ( find (propEq (propName, key))
    , (val, arr, idx = findIndex (propEq (propName, key), arr)) =>
        update(idx > -1 ? idx : length (arr), val, arr)
    )

const updateSize = (prop, value, gender, category, data) => 
  set 
    ( compose 
        ( lensPath (['userInfo', 'whois', gender, 'referenceBrands'])
        , lensMatch ('category') (category)
        , lensProp (prop)
        )
    , value
    , data
    )
  

const data = {type: "Shoes", gender: "female", userInfo: {whois: {male: {htt: 20.2, referenceBrands: [{category: "Shoes", size: 40}]}, female: {height: 191, age: 20, referenceBrands: [{category: "Shoes", size: 35.5}, {category: "Apparel", size: 50}]}}}};

console .log (
  updateSize ('size', 100, 'female', 'Shoes', data)
)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
<script>const {lens, find, findIndex, propEq, update, length, set, compose, lensPath, lensProp} = R  </script>

Note that I replaced over with set. over is about creating a new value by applying a function to the old one, set simply sets it to the given value. This means that I can drop the redundant always as well.

Of course you could also use

    ( compose 
        ( lensPath (['userInfo', 'whois'])
        , lensProp (gender)
        , lensProp ('referenceBrands')
        , lensMatch ('category') (category)
        , lensProp (prop)
        )

or even split that lensPath into two lensProps, but I don't see a reason.

This is far from point-free. In fact, the main function has five named parameters, two more than I'm generally comfortable with and four more than I prefer. But they all seem necessary. And I only choose point-free when it improves readability. I suspect that this would be made much worse by a point-free solution.

I don't know if any partially applied version of this would be helpful, but it would be easy enough to change the signature to

const updateSize = (prop, value) => (gender, category) => (data) => ...
// ...
updateSize ('size', 100) ('female', 'Shoes') (data)

or some such, or to use R.curry.

Scott Sauyet
  • 49,207
  • 4
  • 49
  • 103