Skip to content

Instantly share code, notes, and snippets.

@Phryxia
Last active May 18, 2022 16:25
Show Gist options
  • Select an option

  • Save Phryxia/b5b9089bbe43bdcf2e1a417447b8292b to your computer and use it in GitHub Desktop.

Select an option

Save Phryxia/b5b9089bbe43bdcf2e1a417447b8292b to your computer and use it in GitHub Desktop.
What if there were no function* and yield syntax in the JavaScript?
/*
* Please read the comment first to understand this creepy pasta.
* Unfortunately, I don't have any ideas of implementing complete replica of function* (for return behavior)
*
* @param {function} f Schema of generator function scheme which receives any parameters and
* - may return `{ value, nextF }` or `undefined` if there is nothing to yield.
* - *value* is for `yield` and *nextF* for aftermath of `yield`.
* - *nextF* has the same manner as *f*.
* @return {function} Generator function which returns generator, which behaves like
* - as if returned values of f were 'yielded'.
*/
function createGeneratorFunction(f) {
return (...args) => {
let currentF = f
return {
next() {
if (currentF) {
const nextDetail = currentF(...args)
if (!nextDetail) {
return {
done: true,
}
}
const { value, nextF } = nextDetail
currentF = nextF
return {
value,
done: false,
}
}
return {
done: true,
}
},
[Symbol.iterator]() {
return this
},
// Actually I didn't care the detail of the functions below
return() {
return { done: true }
},
throw() {
return { done: true }
}
}
}
}
@Phryxia
Copy link
Author

Phryxia commented May 18, 2022

Imagine if there were no function* and yield syntax, but you still want to do some 'functional lazy evaluation'.
How horrible it would be!

I was curious enough to see that void... and it was completely absurd idea. To be honest, I was boring and want to do some stupid things without any cheating.

For the simplicity, let's assume that there 'is' Iterator (and Generator) protocols. Also I ignored return and throw properties of generator.

At first, I'd like to feed some callback like _yield...

createGeneratorFunction((_yield) => {
  // ... do some things
  _yield(1)
  // ... do other things
})

But after few seconds, I realized that it is impossible to separate the function inside the program itself.

Statements after the _yield callback (or whatever) should not be executed before any demands. Unless, it is useless and impossible to process Infinity. That's why f returns nextF.
(If you try to do this outside of the source code, you can do that with a nice parser- this is what transpiler does)

(Added after few minutes: Maybe Promise would help such situation, but I'm not sure with a glampse- it may block the thread)

My best approach was abusing chain of the functions... callback hell yeah

// this is equivalent to below
const foo = createGeneratorFunction(() => {
  before()
  return {
    value: x,
    nextF: () => after()
  }
})

// this is equivalent to above
function* foo() {
  before()
  yield x
  yield* after()
}

Example

Implementation of very basic functional tools: map, filter, reduce.
Note that they use lazy evaluation.

For your interests, checkout this TypeScript playground , yet it's JavaScript.

// Number Generator
const generateNaturalNumber = createGeneratorFunction(function f(start, end, step) {
    if (start >= end) {
        return undefined
    }
    if (start + step === end) {
        return {
            value: start
        }
    }
    return {
        value: start,
        nextF: () => f(start + step, end, step)
    }
})

// Generalized map
const map = createGeneratorFunction(function _(callback, itr) {
    const { value, done } = itr.next()

    if (done) {
        return undefined
    }

    return {
        value: callback(value),
        nextF: () => _(callback, itr)
    }
})

// Generalized filter
const filter = createGeneratorFunction(function _(callback, itr) {
    const { value, done } = itr.next()

    if (done) {
        return undefined
    }

    if (callback(value)) {
        return {
            value,
            nextF: () => _(callback, itr)
        }
    }

    return _(callback, itr)
})

// Generalized reduce
function reduce(callback, acc, itr) {
    for (const v of itr) {
        acc = callback(acc, v)
    }
    return acc
}

// Sum of every doubled integers
// which is greator or equal than 0, 
// less than 5 
// and not multiple of 3
const sum = reduce(
    (acc, x) => acc + x,
    0,
    map(
        x => 2 * x, 
        filter(
            x => x % 3 > 0,
            generateNaturalNumber(0, 5, 1)
        )
    )
)

console.log(sum)

Miscellaneous

Q: Why did you do this
A: Just. For. Fun.

Q: Why didn't you use TypeScript
A: It was so messy and was almost impossible to type them correctly. IMO TypeScript is not suitable for functional paradigm, at least at my limitation of ability.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment