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

4
  • 1
    What you want to achieve has nothing to do with constructor. Do you mean foo = new Chain("h");? Commented Oct 27, 2020 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? Commented Oct 27, 2020 at 22:10
  • jsfiddle.net/nvwaefq0 without the ancestor property Commented Oct 27, 2020 at 22:18
  • What is the role of ChainParent here? It seems unrelated to your question... Commented Oct 27, 2020 at 22:22

3 Answers 3

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());

Sign up to request clarification or add additional context in comments.

Comments

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

2 Comments

The ancestor property doesn't work well if you pass strings that do not contain only one character
@ibrahimmahrir thanks for the review. I made an edit with your suggestion.
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());

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.