tail-call-proxy

A template for creating npm packages using TypeScript and VSCode

Downloads in past

Stats

StarsIssuesVersionUpdatedCreatedSize
tail-call-proxy
901.1.12a year agoa year agoMinified + gzip package size for tail-call-proxy in KB

Readme


- Functions
- [lazy](#lazy)
- [parasitic](#parasitic)

tail-call-proxy
Delayed initialized objects that support tail-call optimization.
!npm packagenpm-imgnpm-url !Build Statusbuild-imgbuild-url !Downloadsdownloads-imgdownloads-url !Issuesissues-imgissues-url !Code Coveragecodecov-imgcodecov-url !Commitizen Friendlycommitizen-imgcommitizen-url !Semantic Releasesemantic-release-imgsemantic-release-url

Functions

lazy

lazy<T\>(tailCall): T
Returns an proxy object backed by tailCall, which will be lazily created at the first time its properties or methods are used.
lazy can eliminate tail calls, preventing stack overflow errors in tail recursive functions or mutual recursive functions.
Example
The initializer passed to lazy should not be called until the first time lazyObject.hello is accessed. When lazyObject.hello is accessed more than once, the second access would not trigger the initializer again.
```typescript doctest import { lazy } from 'tail-call-proxy';
let counter = 0; const lazyObject = lazy(() => { counter++; return { hello: 'world' }; }); expect(counter).toBe(0);
expect(lazyObject.hello).toBe('world'); expect(counter).toBe(1);
expect(lazyObject.hello).toBe('world'); expect(counter).toBe(1);
**`Example`**

Note that errors thrown in the initializer will be delayed as well.

```typescript doctest
import { lazy } from 'tail-call-proxy';

let counter = 0;
const lazyError: Record<string, unknown> = lazy(() => {
  counter++;
  throw new Error();
});

// No error is thrown, given that the underlying object have not been created
// yet.
expect(counter).toBe(0);

expect(() => lazyError.toString()).toThrow();
expect(counter).toBe(1);

expect(() => lazyError.toLocaleString()).toThrow();
expect(counter).toBe(1);

Example
The following mutual recursive functions would result in stack overflow:
```typescript doctest function isEven(n: number): Boolean { if (n === 0) {
return new Boolean(true);
} return isOdd(n - 1); }
function isOdd(n: number): Boolean { if (n === 0) {
return new Boolean(false);
} return isEven(n - 1); }
expect(() => isOdd(1000000)).toThrow();
However, if you replace `return xxx` with `return lazy(() => xxx)`, it will
use a constant size of stack memory and avoid the stack overflow.

```typescript doctest
import { lazy } from 'tail-call-proxy';
function isEven(n: number): Boolean {
  if (n === 0) {
    return new Boolean(true);
  }
  return lazy(() => isOdd(n - 1));
}

function isOdd(n: number): Boolean {
  if (n === 0) {
    return new Boolean(false);
  }
  return lazy(() => isEven(n - 1));
}

expect(isOdd(1000000).valueOf()).toBe(false);

Type parameters

| Name | Type | | :------ | :------ | | T | extends object |

Parameters

| Name | Type | Description | | :------ | :------ | :------ | | tailCall | () => T | the function to create the underlying object |

Returns

T

Defined in

index.ts:253

parasitic

parasitic<T\>(tailCall): T
Performs a tail call as soon as possible.
parasitic returns either exactly the object returned by tailCall, or a proxy object backed by the object returned by tailCall, if there are any previously started pending tail calls. In the latter case, the underlying object will be created after all the previous tail calls are finished.
Example
Unlike lazy
, parasitic performs the initialization as soon as possible:
```typescript doctest import { parasitic } from 'tail-call-proxy';
let counter = 0; const parasiticObject = parasitic(() => { counter++; return { hello: 'world' }; }); expect(counter).toBe(1);
expect(parasiticObject.hello).toBe('world'); expect(counter).toBe(1);
expect(parasiticObject.hello).toBe('world'); expect(counter).toBe(1);
**`Example`**

`parasitic` is useful when you need tail call optimization while you don't
need the lazy evaluation. It can be used together with [lazy](#lazy)
alternately.

```typescript doctest
import { lazy, parasitic } from 'tail-call-proxy';

let isEvenCounter = 0;
const trueObject = new Boolean(true);
function isEven(n: number): Boolean {
  isEvenCounter++;
  if (n === 0) {
    return trueObject;
  }
  return lazy(() => isOdd(n - 1));
};

let isOddCounter = 0;
const falseObject = new Boolean(false);
function isOdd(n: number): Boolean {
  isOddCounter++;
  if (n === 0) {
    return falseObject;
  }
  return parasitic(() => isEven(n - 1));
};

try {
  // `isEven` is called, but `lazy(() => isOdd(n - 1))` does not trigger
  // `isOdd` immediately.
  const is1000000Even = isEven(1000000);
  expect(isOddCounter).toBe(0);
  expect(isEvenCounter).toBe(1);

  // `valueOf` triggers the rest of the recursion.
  expect(is1000000Even.valueOf()).toBe(true);
  expect(isOddCounter).toBe(500000);
  expect(isEvenCounter).toBe(500001);

  // `is1000000Even` is a lazy proxy backed by `trueObject`, not the exactly
  // same object of `trueObject`.
  expect(is1000000Even).not.toStrictEqual(trueObject);
  expect(is1000000Even).toEqual(trueObject);
} finally {
  isEvenCounter = 0;
  isOddCounter = 0;
}

// `isOdd` is called, in which `parasitic(() => isEven(n - 1))` triggers the
// rest of the recursion immediately.
const is1000000Odd = isOdd(1000000);
expect(isOddCounter).toBe(500001);
expect(isEvenCounter).toBe(500000);
expect(is1000000Odd.valueOf()).toBe(false);
expect(isOddCounter).toBe(500001);
expect(isEvenCounter).toBe(500000);

// `is1000000Odd` is exactly the same object of `falseObject`, not a lazy
// proxy.
expect(is1000000Odd).toStrictEqual(falseObject);

Type parameters

| Name | Type | | :------ | :------ | | T | extends object |

Parameters

| Name | Type | Description | | :------ | :------ | :------ | | tailCall | () => T | the function to create the underlying object |

Returns

T

Defined in

index.ts:353