0

I would like to be able to concatenate strings by creating an object and passing down the constructor, so every time it is called it keeps the references of the previous one. I would like to achieve something like this:

foo = Chain("h")
bar = foo("e")("l")("l")("o")

foo.toString()          == "h"
bar.toString()          == "hello"
bar.ancestor.toString() == "hell"

What I have so far is a method chaining, when is rather similar but it is not quite what I want to accomplish here. I've been following the following documentation:

Bind

Method binding

Inheritance and prototype chain

function ChainParent() {
}
function Chain(letter) {
    this.letter = letter;
    this.ancestor = this.letter.substring(0, this.letter.length - 1);
}

Chain.prototype = Object.create(ChainParent.prototype) 
Chain.prototype.constructor = Chain;

Chain.prototype.create = function create(letter) {
    const letra = this.letter.concat(letter);
    return new this.constructor(letra)
}

Chain.prototype.toString = function() {
    console.log(this.letter);
}

const foo = new Chain("h");
const bar = foo.create("e").create("l").create("l").create("o");
foo.toString();
bar.toString();

bar.ancestor.toString(); //This won't work
rasfuranku
  • 78
  • 1
  • 1
  • 10
  • 1
    What you want to achieve has nothing to do with constructor. Do you mean `foo = new Chain("h");`? – ibrahim mahrir Oct 27 '20 at 22:04
  • You start by saying you want `foo = Chain("h")`, but then you go on to say you want `foo = new Chain("h")`, ...etc. Which of the two is it? Can you make your question consistent in what it is asking for? – trincot Oct 27 '20 at 22:10
  • https://jsfiddle.net/nvwaefq0/ without the `ancestor` property – ibrahim mahrir Oct 27 '20 at 22:18
  • What is the role of `ChainParent` here? It seems unrelated to your question... – trincot Oct 27 '20 at 22:22

3 Answers3

1

Here's one possible implementation of chain -

const chain = (s = "") =>
  Object.assign
    ( next => chain(s + next)
    , { toString: _ => s }
    , { ancestor: _ => chain(s.slice(0, -1)) }
    )


const foo = chain("h")
const bar = foo("e")("l")("l")("o")

console.log(foo.toString())
console.log(bar.toString())
console.log(bar.ancestor().toString())
console.log(bar.ancestor().ancestor().toString())
h
hello
hell
hel

Per @IbrahimMahrir's comment, we can make an adjustment to chain to accommodate input strings of any length -

function chain (s = "")
{ const loop = (s = []) =>
    Object.assign
      ( next => loop([ ...s, next ])
      , { toString: _ => s.join("") }
      , { ancestor: _ => loop(s.slice(0, -1)) }
      )
  return loop([s])
}

const foo = chain("hh")
const bar = foo("ee")("ll")("ll")("oo")

console.log(foo.toString())
console.log(bar.toString())
console.log(bar.ancestor().toString())
console.log(bar.ancestor().ancestor().toString())
hh
hheelllloo
hheellll
hheell

And here's another implementation of chain I thought about over lunch -

const chain = (s = "", ...ancestor) =>
  Object.assign
    ( next => chain(next, s, ...ancestor)
    , { toString: _ => [...ancestor].reverse().join("") + s }
    , { ancestor: _ => chain(...ancestor) }
    )

const foo = chain("hh")
const bar = foo("ee")("ll")("ll")("oo")

console.log(foo.toString())
console.log(bar.toString())
console.log(bar.ancestor().toString())
console.log(bar.ancestor().ancestor().toString())
hh
hheelllloo
hheellll
hheell
Mulan
  • 129,518
  • 31
  • 228
  • 259
1

Here's a solution using How to extend Function with ES6 classes? to make the class callable, so there's less boilerplate with the chaining.

In this example, each instance of the class keeps the the letter that it was given to start with, then merges them together using recursion in the toString function. So it acts as a linked list with a reference to the parent only.

// https://stackoverflow.com/a/36871498/13175138
class ExtensibleFunction extends Function {
  constructor(f) {
    return Object.setPrototypeOf(f, new.target.prototype);
  }
}

class Chain extends ExtensibleFunction {
  constructor(letter, chain = null) {
    super((letter) => new Chain(letter, this));
    this.ancestor = chain;
    this.letter = letter;
  }
  toString() {
    if (!this.ancestor) {
      return this.letter;
    }
    return this.ancestor.toString() + this.letter;
  }
}

const foo = new Chain('h');
const bar = foo('e')('l')('l')('o');
console.log(foo.toString());
console.log(bar.toString());

console.log(bar.ancestor.toString());

const foobar = new Chain('hel');
const barfoo = foobar('ll')('o');
console.log(foobar.toString());
console.log(barfoo.toString());

console.log(barfoo.ancestor.toString());
Zachary Haber
  • 10,376
  • 1
  • 17
  • 31
0

You should just set the ancestor to the current instance (this).

Note that toString is a special method, as it gets called automatically when the JavaScript engine needs to convert an instance to a primitive value. So you should stick to the "interface" for it: it should return the string (not output it).

Here is an implementation of your final block using the modern class syntax. I allows letter to actually be a string with more than one character. The ancestor property will really return what the string is before the most recent extension:

class Chain {
    constructor(letter, ancestor=null) {
        this.letter = letter;
        this.ancestor = ancestor;
    }
    create(letter) {
        return new Chain(this.letter + letter, this);
    }
    toString() {
        return this.letter;
    }
}

const foo = new Chain("h");
const bar = foo.create("e").create("l").create("l").create("o");
console.log(foo.toString());
console.log(bar.toString());
console.log(bar.ancestor.toString());

If you don't want a constructor -- so not new -- then the above translates to:

function chain(letter, ancestor=null) {
    let that = {
        letter,
        ancestor,
        create(newLetter) {
            return chain(letter + newLetter, that);
        },
        toString() {
            return letter;
        }
    }
    return that;
}

const foo = chain("h");
const bar = foo.create("e").create("l").create("l").create("o");
console.log(foo.toString());
console.log(bar.toString());
console.log(bar.ancestor.toString());
trincot
  • 317,000
  • 35
  • 244
  • 286