Skip to content

Instantly share code, notes, and snippets.

@Gozala
Created October 5, 2021 01:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Gozala/d250287197c8a84b075d8386e226643b to your computer and use it in GitHub Desktop.
Save Gozala/d250287197c8a84b075d8386e226643b to your computer and use it in GitHub Desktop.
import { loop, recur } from './loop.js'
export const factorial = (n) => loop((n, acc) => n <= 1 ? acc : recur(n - 1, n * acc), n, 1)
/**
* @template {unknown[]} Args
* @template Out
* @param {(...args:Args) => Out} fn
* @param {Args} args
* @returns {Out extends (Promise<Recur<Args>>) ? Promise<never> :
* Out extends (Promise<Recur<Args> | infer T>) ? Promise<T> :
* Out extends Recur<Args> ? never :
* Out extends (Recur<Args> | infer T) ? T :
* never
* }
*/
export const loop = (fn, ...args) => {
const result = isAsync(fn) ? asyncLoop(fn, ...args) : syncLoop(fn, ...args)
// @ts-expect-error - TS can't infer `fn` dependent type.
return result
}
/**
* @template {unknown[]} Args
* @template T
* @typedef {Recur<Args>|T} Loop
*/
/**
* @template {unknown[]} Args
* @template T
* @param {((...args:Args) => T) | ((...args:Args) => Promise<T>)} fn
* @returns {fn is (...args:Args) => Promise<T>}
*/
const isAsync = (fn) => Object.toString.call(fn) === '[object AsyncFunction]'
/**
* @template T
* @template {unknown[]} Args
* @param {(...args:Args) => Promise<Recur<Args> | T> | T | Recur<Args>} fn
* @param {Args} args
* @returns {Promise<T>}
*/
export const asyncLoop = async (fn, ...args) => {
while (true) {
const result = await fn(...args)
if (result instanceof Recur) {
args = result.args
} else {
return result
}
}
}
/**
* @template T
* @template {unknown[]} Args
* @param {(...args:Args) => Recur<Args> | T} fn
* @param {Args} args
* @returns {T}
*/
export const syncLoop = (fn, ...args) => {
while (true) {
const result = fn(...args)
if (result instanceof Recur) {
args = result.args
} else {
return result
}
}
}
/**
* @template {unknown[]} Args
* @param {Args} args
* @returns {Recur<Args>}
*/
export const recur = (...args) => new Recur(args)
/**
* @template {unknown[]} Args
*/
class Recur {
/**
* @param {Args} args
*/
constructor(args) {
this.args = args
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment