0

I am trying to solve a specific problem using functional programming. My guess is that a fold should do the job, but so far the solution has eluded me.

Starting from a dot-separated string like "a.b.c" I want to build a Javascript object which in JS literal notation would look like:

obj = {a:{b:{c:"whatever"}}}

The algorithm should accept a seed object to start with. In the previous example the seed would be {}.

If I provided {a:{f:"whatever else"}} as seed, the result would be

{a:{f:"whatever else",b:{c:"whatever"}}}

I hope my description is clear enough. I am not talking about string manipulation. I want to create proper objects.

I am using Javascript because that's the language where this real-world problem has arisen and where I will implement the FP solution which I hope to find by asking here.

EDIT: the main issue I am trying to tackle is how to avoid mutable objects. JS is somehow too lenient about adding/removing attributes and in this case I want to be sure that there will be no side effects during the run of the FP routine.

Marco Faustinelli
  • 3,734
  • 5
  • 30
  • 49
  • Why do you focus on FP? Do you have a solution without FP already? Please show us what you've tried and where you're stuck. – Bergi Apr 04 '14 at 14:56
  • I focus on FP because I want to exploit immutability. The non-FP solution is an iterator that enters the seed and mutates it. I am trying a solution with fold right. I have a result that works fine with seed `{}` but fails in case of complex seeds. – Marco Faustinelli Apr 05 '14 at 16:26
  • Gotta find my current fold right solution and post it here. Sorry, too little time this afternoon :-) – Marco Faustinelli Apr 05 '14 at 16:38
  • You mean for the `{a:{f:"whatever else"}}` example, you want to return a *copy* instead of mutating the seed? – Bergi Apr 05 '14 at 17:21

2 Answers2

2
var seed = {},
    str = "a.b.c";
str.split(".").reduce(function(o, p) {
    return p in o ? o[p] : (o[p] = {});
}, seed);

console.log(seed); // {"a":{"b":{"c":{}}}}
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • This solution, letting aside the mutations, shows me the power of folds in manipulating accumulators other than lists. In a way, there is no difference between cons-ing a head in front of an accumulator list and adding one more level deep down an accumulator object. – Marco Faustinelli Apr 05 '14 at 19:21
  • ...and the way you prepare the accumulator for the next iteration thanks to the self-executing function is absolutely **BRILLIANT**! – Marco Faustinelli Apr 05 '14 at 19:23
  • 1
    self-executing function? There's no IEFE here. – Bergi Apr 05 '14 at 20:06
  • Well, now you're mentioning it. How would you call the `(o[p]={})` idiom? I've tried to reproduce it with `(function(){ o[p] = {};})()` and alikes but I see it's not quite the same. Your code **returns** `o[p]` and that baffles me. – Marco Faustinelli Apr 06 '14 at 05:04
  • Ok, I got it. Didn't know that assignments return stuff. I'd say they returned undefined, but it makes more sense this way. JS is still surprising me after all these years... – Marco Faustinelli Apr 06 '14 at 07:28
1

A fully functional variant:

function traverse(tree, path, leftover) {
    if (!tree || !path.length)
        return leftover(path);
    var ntree = {};
    for (var p in tree)
        ntree[p] = tree[p];
    ntree[path[0]] = traverse(tree[path[0]], path.slice(1), leftover);
    return ntree;
}
function create(path, value) {
    if (!path.length)
        return value;
    var tree = {};
    tree[path[0]] = create(path.slice(1), value);
    return tree;
}

function set(tree, pathstring, value) {
    return traverse(tree, pathstring.split("."), function(path) {
        return create(path, value);
    });
}

var seed = {a:{f:"whatever else"}};
var obj = set(seed, "a.b.c", "whatever")
    // {"a":{"f":"whatever else","b":{"c":"whatever"}}}
set({}, "a.b.c", "whatever")
    // {"a":{"b":{"c":"whatever"}}}
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • OK, rather instructive example. The `ntree[p] = tree[p]` thing is a shallow copy, but I know how to handle that. The construction `ntree[path[0]] = traverse(tree[path[0]],...); return ntree;` is a precious takeaway. I reckon you found all this quite simple, but imho it's not. Thank you... – Marco Faustinelli Apr 12 '14 at 14:39