0

I'm seeing if it's possible to write a factorial method as follows:

class InlineMath {
    constructor(x) {
        this.x = x
    }
    minus(y) {
        this.x -= y;
        return this;
    }
    times(y) {
        this.x *= y;
        return this;
    }
    factorial(n) {
        if (n == 1) {
            return this;
        } else {
            this.x = this.times(this.factorial(this.minus(1)));
        }
    }
}
x = new InlineMath(2);
x.factorial().factorial();
console.log(x);

I know this is a totally non-standard way to do it but was just seeing if this way was possible (i.e., only through side-effects).

But so far the only way I can figure out how to do it is offload it like so:

class InlineMath {
    constructor(x) {
        this.x = x
    }
    minus(y) {
        this.x -= y;
        return this;
    }
    times(y) {
        this.x *= y;
        return this;
    }
    factorial() {
        this.x = _factorial(this.x);
        return this;
    }
}
function _factorial(n) {
    if (n==1) {
        return n;
    } else {
        return n * _factorial(n-1);
    }  
}
x = new InlineMath(2);
x.factorial().factorial();
console.log(x);
David542
  • 104,438
  • 178
  • 489
  • 842

4 Answers4

3

modules

I would suggest writing your math module with ordinary functions. You can provide an InlineMath class as a thin wrapper around the plain functions. This makes it easier to write both the ordinary functions and the class -

// math.js
const minus = (a, b) => a - b
const plus = (a, b) => a + b
const times = (a, b) => a * b
const factorial = a => a == 0 ? 1 : times(a, factorial(minus(a, 1)))
const math = a => new InlineMath(a)

class InlineMath {
  constructor(t) { this.t = t }
  factorial() { return math(factorial(this.t)) }
  minus(x) { return math(minus(this.t, x)) }
  plus(x) { return math(plus(this.t, x)) }
  times(x) { return math(times(this.t, x)) }
  toNumber() { return this.t }
}

export { math, minus, plus, times, factorial }
// main.js
import { math } from "./math.js"

console.log(math(3).factorial().factorial().toNumber())
console.log(math(1).plus(2).times(5).factorial().toNumber())
720
1307674368000

have your cake and eat it too

One understated advantage of the approach above is that we have a dual interface for our math module. We can use it in the proposed object-oriented way as demonstrated above, or we can use it with a functional approach -

// main.js
import { plus, times, factorial } from "./math"

console.log(factorial(factorial(3)))
console.log(factorial(times(5,plus(2,1))))
720
1307674368000

low-hanging fruit

Maybe it would be cool if math could support really big numbers?

// math.js
const minus = (a, b) => BigInt(a) - BigInt(b)
const plus = (a, b) => BigInt(a) + BigInt(b)
const times = (a, b) => BigInt(a) * BigInt(b)
const factorial = a =>  /* unchanged */
const math = a =>  /* unchanged */

class InlineMath {
  /* ... */
  toString() { return this.t.toString() }
}

export { math, minus, plus, times, factorial }
// main.js
import { math } from "./math.js"

console.log(math(5).factorial().factorial().toNumber())

(5!)! = 120! =

6689502913449127057588118054090372586752746333138029810295671352301633557244962989366874165271984981308157637893214090552534408589408121859898481114389650005964960521256960000000000000000000000000000

demo

Run the snippet below to verify the result of in your own browser -

// math.js module
const minus = (a, b) => BigInt(a) - BigInt(b)
const plus = (a, b) => BigInt(a) + BigInt(b)
const times = (a, b) => BigInt(a) * BigInt(b)
const factorial = a => a == 0 ? 1 : times(a, factorial(minus(a, 1)))
const math = a => new InlineMath(a)

class InlineMath {
  constructor(t) { this.t = t }
  factorial() { return math(factorial(this.t)) }
  minus(x) { return math(minus(this.t, x)) }
  plus(x) { return math(plus(this.t, x)) }
  times(x) { return math(times(this.t, x)) }
  toNumber() { return this.t.toString() }
}

// main.js
console.log(math(5).factorial().factorial().toNumber())
Sebastian Simon
  • 18,263
  • 7
  • 55
  • 75
Mulan
  • 129,518
  • 31
  • 228
  • 259
  • 2
    I love these dual-use FP/OO explanations you've been presenting. Here, I'm thinking (or perhaps only hoping) that the goal was simply academic: *Can we do this only through side-effects?* I hope that's the case, because I find little use for these wrappers for plain numbers. – Scott Sauyet Jan 20 '22 at 04:35
  • 1
    thanks Scott :) wishing it's academic as well. not sure what the OP's intentions are but i hope the code here demonstrates that untangling the class from the plain functions will unravel complexity and dissolve the need for "helper" methods. i also hope these demos open readers' eyes to practical ways to write object-oriented interfaces they may not have seen before. – Mulan Jan 20 '22 at 05:12
  • As I said, I love this approach. I mostly avoid OOP, but when I need it, I will definitely use this technique. – Scott Sauyet Jan 20 '22 at 13:51
1

Multiple mistakes here.

  • Don't reassign this.x, keep your instances immutable.
  • the factorial method should not take a parameter, it should use the value stored in the instance
  • consequently, the recursive call of factorial needs to be made on a different instance, and not be given an argument
  • the factorial method must always return a new instance, not nothing like your current else branch.

With some renaming:

class InlineNumber {
    constructor(n) {
        this.n = n
    }
    factorial() {
        if (this.n == 1) {
            // return 1
            return this;
        } else {
            // return n * (n-1)!
            return this.times(this.minus(new InlineNumber(1)).factorial());
        }
    }
    minus(subtrahend) {
        return new InlineNumber(this.n - subtrahend.n);
    }
    times(multiplicand) {
        return new InlineNumber(this.n * multiplicand.n);
    }
    valueOf() {
        return this.n;
    }
}
const x = new InlineNumber(3);
console.log(x.factorial().factorial());
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
1

In the factorial formula n * (n-1)!, you need the value n twice. With a mutable math object, you'll need to clone it for the recursive call:

class InlineMath {
    constructor(x) {
        this.x = x
    }
    clone() {
        return new InlineMath(this.x);
    }
    minus(y) {
        this.x -= y;
        return this;
    }
    times(y) {
        this.x *= y;
        return this;
    }
    factorial() {
        if (this.x > 1)
            this.times(this.clone().minus(1).factorial().x);
        return this;
    }
}
x = new InlineMath(3);
x.factorial().factorial();
console.log(x);

Notice that writing the formula as (n-1)! * n is not easily possible:

this.minus(1).factorial().times(this.clone().x)

won't work as the cloning happens too late. I recommend my other answer which avoids this can of worms…

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • oh boy, that's pretty tricky, thanks for taking the time to do this though, cheers. – David542 Jan 20 '22 at 01:29
  • one question though -- would your `minus` / `times` implementation require an `InlineMath` instance in the other answer, or could it be done with a number? – David542 Jan 20 '22 at 01:30
  • 1
    No, this answer works entirely with mutable stuff. One could write `if (this.x > 1) { const c = this.clone(); c.minus(1); c.factorial(); this.times(c.x); }`. Maybe that would have been even more educational? – Bergi Jan 20 '22 at 01:35
1

I'm hoping this is only a proof-of-concept, and that you never plan on using code like this. But a simple change to your code will achieve this "through side-effects":

class InlineMath {
    constructor(x) {
        this.x = x
    }
    minus(y) {
        this.x -= y;
        return this;
    }
    times(y) {
        this.x *= y;
        return this;
    }
    factorial() {
        if (this.x <= 1) {
          this.x = 1;
        } else {
          this.x *= this.minus(1).factorial().x
        }
        return this;
    }
}
const x = new InlineMath(3);
x.factorial().factorial();
console.log(x);
Mulan
  • 129,518
  • 31
  • 228
  • 259
Scott Sauyet
  • 49,207
  • 4
  • 49
  • 103
  • 1
    oh that's pretty nifty, and actually exactly what I was going for initial. Thanks for that! – David542 Jan 20 '22 at 07:49
  • 1
    didn't see this yesterday, Scott. i understand your comment a lot more now haha. ya this is precisely how i would do it – and i can't believe i'm about to say this – using side effects only... – Mulan Jan 20 '22 at 14:57
  • made a small edit, i think you intended to use `minus`, but now i'm realizing `times` is unused too. my brain is hurting trying to think of a way to write `factorial` using both. rollback my edit if made things worse, lol – Mulan Jan 20 '22 at 15:04
  • Yes, that's an improvement. I think the problem with trying to use both is exactly what @Bergi was describing. – Scott Sauyet Jan 20 '22 at 15:36
  • Alternatively, we might do `else { this.times (new InlineMath (this.x - 1).factorial().x)}`. I think I might prefer this version, but they are very similar. – Scott Sauyet Jan 20 '22 at 16:57