Ramda: map does fails on nested objects.

Created on 6 Aug 2016  路  3Comments  路  Source: ramda/ramda

Such sorrow.

var _ = ramda

var nestedObj = {
  level1: {
    hello: 'Deepworld'
  },
  hello: 'shallowworld'
}

var operation = function (string) {
  return string + ' SUCCESS!!!'
}
var result = ramda.map(operation, nestedObj)
console.log(result)

> {"level1":"[object Object] SUCCESS!!!","hello":"shallowworld SUCCESS!!!"}
wontfix

Most helpful comment

Let's take a step back to consider the type of map:

Functor f => (a -> b) -> f a -> f b

{level1: {hello: 'Deepworld'}, hello: 'shallowworld'} is incompatible with this function since we cannot resolve a in f a. If f is StrMap we have

{hello: 'Deepworld'} :: StrMap a, { hello :: String }

and

'shallowworld' :: String

as a values, and there is no _standard_ type of which all a values are members.

We could, though, define a recursive string map type: a data type mapping strings to values of a certain type _or_ to recursive string maps of that same type.

Every value of type StrMap a would also be a member of RecStrMap a. For example:

{CA: true, NY: false} :: StrMap Boolean, RecStrMap Boolean

But unlike the StrMap type, the RecStrMap type would support arbitrary nesting:

{R: {a: {m: {d: {a: true}}}}, S: {a: {n: {c: {t: {u: {a: {r: {y: false}}}}}}}}} :: RecStrMap Boolean

Let's assume that the RecStrMap type satisfies the Functor requirements. We can then replace Functor f => f with RecStrMap, giving:

(a -> b) -> RecStrMap a -> RecStrMap b

The implementation of RecStrMap#map would need to be recursive. The problem, though, is that certain values are ambiguous. Is {a: {b: {x: 0, y: 0}}} a value of type RecStrMap :: Number, or does {x: 0, y: 0} represent a "point" meaning that the correct interpretation is RecStrMap :: { x :: Number, y :: Number }. If the latter is what we intended, we'll find map will not do as we expected: the function we provide will be applied to numbers rather than to "points". It's therefore impossible to treat JavaScript objects as recursive string maps while preserving the forall a requirement (if our data type limits the types of values it can contain, it is not a functor).

One could define a RecStrMap data type by providing Leaf and Tree constructors to resolve the ambiguity. The representation of the value in question might then be:

Tree({level1: Tree({hello: Leaf('Deepworld')}), hello: Leaf('shallowworld')})

Were R.map to operate recursively over arbitrarily nested string maps _represented by plain JavaScript objects_ it would no longer be lawful. We could no longer perform this transformation:

> R.map(votes => (votes.yeas / (votes.yeas + votes.nays) * 100).toFixed(1) + '%', {CA: {yeas: 11, nays: 4}, NY: {yeas: 8, nays: 4}})
{CA: '73.3%', NY: '66.7%'}

All 3 comments

i don't know why you would assume map would recursively descend. i don't see any reason to "fix" this, 'cuz it doesn't look like a problem to me.

just saying.

Let's take a step back to consider the type of map:

Functor f => (a -> b) -> f a -> f b

{level1: {hello: 'Deepworld'}, hello: 'shallowworld'} is incompatible with this function since we cannot resolve a in f a. If f is StrMap we have

{hello: 'Deepworld'} :: StrMap a, { hello :: String }

and

'shallowworld' :: String

as a values, and there is no _standard_ type of which all a values are members.

We could, though, define a recursive string map type: a data type mapping strings to values of a certain type _or_ to recursive string maps of that same type.

Every value of type StrMap a would also be a member of RecStrMap a. For example:

{CA: true, NY: false} :: StrMap Boolean, RecStrMap Boolean

But unlike the StrMap type, the RecStrMap type would support arbitrary nesting:

{R: {a: {m: {d: {a: true}}}}, S: {a: {n: {c: {t: {u: {a: {r: {y: false}}}}}}}}} :: RecStrMap Boolean

Let's assume that the RecStrMap type satisfies the Functor requirements. We can then replace Functor f => f with RecStrMap, giving:

(a -> b) -> RecStrMap a -> RecStrMap b

The implementation of RecStrMap#map would need to be recursive. The problem, though, is that certain values are ambiguous. Is {a: {b: {x: 0, y: 0}}} a value of type RecStrMap :: Number, or does {x: 0, y: 0} represent a "point" meaning that the correct interpretation is RecStrMap :: { x :: Number, y :: Number }. If the latter is what we intended, we'll find map will not do as we expected: the function we provide will be applied to numbers rather than to "points". It's therefore impossible to treat JavaScript objects as recursive string maps while preserving the forall a requirement (if our data type limits the types of values it can contain, it is not a functor).

One could define a RecStrMap data type by providing Leaf and Tree constructors to resolve the ambiguity. The representation of the value in question might then be:

Tree({level1: Tree({hello: Leaf('Deepworld')}), hello: Leaf('shallowworld')})

Were R.map to operate recursively over arbitrarily nested string maps _represented by plain JavaScript objects_ it would no longer be lawful. We could no longer perform this transformation:

> R.map(votes => (votes.yeas / (votes.yeas + votes.nays) * 100).toFixed(1) + '%', {CA: {yeas: 11, nays: 4}, NY: {yeas: 8, nays: 4}})
{CA: '73.3%', NY: '66.7%'}
Was this page helpful?
0 / 5 - 0 ratings

Related issues

cjohansen picture cjohansen  路  4Comments

DanielFGray picture DanielFGray  路  3Comments

tylerlong picture tylerlong  路  3Comments

MadDeveloper picture MadDeveloper  路  3Comments

corporatepiyush picture corporatepiyush  路  4Comments