Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mutually recursive arbitrary builder #377

Merged
merged 14 commits into from
Jun 20, 2019
Merged

Mutually recursive arbitrary builder #377

merged 14 commits into from
Jun 20, 2019

Conversation

dubzzz
Copy link
Owner

@dubzzz dubzzz commented Jun 13, 2019

Fixes #374

@dubzzz
Copy link
Owner Author

dubzzz commented Jun 13, 2019

Tasks to be tackle before merging this PR:

  • Various tests to be added to cover the feature
  • Keep bias support
  • Investigate how too easily avoid Maximum call stack size exceeded errors
  • Investigate alternative implementation based on chain if possible

I made it work with arbitraries defined as:

const { node, leaf } = fc.letrec(tie => ({
  node: fc.tuple(
    fc.frequency({ arbitrary: tie('leaf'), weight: 5 }, { arbitrary: tie('node'), weight: 1 }),
    fc.frequency({ arbitrary: tie('leaf'), weight: 5 }, { arbitrary: tie('node'), weight: 1 })
  ),
  leaf: fc.nat()
}));

Or even:

const { node, leaf } = fc.letrec(tie => ({
  node: fc.tuple(
    fc.oneof(tie('leaf'), tie('node')),
    fc.oneof(tie('leaf'), tie('node'))
  ),
  leaf: fc.nat()
}));

But it failed (sometimes) with definitions like:

const { node, leaf } = fc.letrec(tie => ({
  node: fc.tuple(
    fc.oneof(tie('leaf'), tie('node')),
    fc.oneof(tie('leaf'), tie('node')),
    fc.oneof(tie('leaf'), tie('node'))
  ),
  leaf: fc.nat()
}));

With Maximum call stack size exceeded errors. Ideally the framework should have prefered leaf over node (artificially done in the first snippet).

@coveralls
Copy link

coveralls commented Jun 13, 2019

Coverage Status

Coverage increased (+0.01%) to 96.947% when pulling 7b1e74a on feat/letrec into 02829ce on master.

@dubzzz
Copy link
Owner Author

dubzzz commented Jun 13, 2019

I also tried with another signature for letrec. The advantage is that I get better typings for tie but I lose the types for arbitraries relying on one of the values coming from tie. (see A, B, D)

declare const Int: Arbitrary<number>;
declare const Str: Arbitrary<string>;
declare function MyArr<T>(a: Arbitrary<T>): Arbitrary<T[]>;

declare function letrecA<T>(
  def: { [K in keyof T]: (d: { [KK in keyof T]: Arbitrary<T[KK]> }) => Arbitrary<T[K]> }
): { [K in keyof T]: Arbitrary<T[K]> };

const outA = letrecA({
  a: () => Int,
  b: ({ a }) => MyArr(a),
  c: () => Str,
  d: ({ c }) => c
});

declare function letrecB<T>(
  def: { [K in keyof T]: (tie: <KK extends keyof T>(k: KK) => Arbitrary<T[KK]>) => Arbitrary<T[K]> }
): { [K in keyof T]: Arbitrary<T[K]> };

const outB = letrecB({
  a: () => Int,
  b: tie => MyArr(tie('a')),
  c: () => Str,
  d: tie => tie('c')
});

declare function letrecC<T>(
  builder: (tie: (key: string) => Arbitrary<any>) => { [K in keyof T]: Arbitrary<T[K]> }
): { [K in keyof T]: Arbitrary<T[K]> };

const outC = letrecC(tie => ({
  a: Int,
  b: MyArr(tie('a')),
  c: Str,
  d: tie('c')
}));

declare function letrecD<T>(
  def: { [K in keyof T]: ((tie: <KK extends keyof T>(k: KK) => Arbitrary<T[KK]>) => Arbitrary<T[K]>) | Arbitrary<T[K]> }
): { [K in keyof T]: Arbitrary<T[K]> };

const outD = letrecD({
  a: Int,
  b: tie => MyArr(tie('a')),
  c: () => Str,
  d: tie => tie('c')
});

None of A, B an D is compatible with TypeScript < 3.5 where tie is equivalent to {}.

@dubzzz
Copy link
Owner Author

dubzzz commented Jun 13, 2019

Here are various tries to implement letrec with no built-ins provided by fast-check:

❌ Not compiling

// Not compiling because node is used before being initialized
const leaf = fc.nat();
const node = fc.tuple(fc.oneof(node, leaf), fc.oneof(node, leaf));

❌ Not running

// Unable to instantiate node because it instantiates another one which does the same..
const leaf = () => fc.nat();
const node = () => fc.tuple(fc.oneof(node(), leaf()), fc.oneof(node(), leaf()));

✔️ Working (see #378)

// (+) Explicit terminal case
// (+) Ability to use depth to parametrize the sub arbitraries
// (+) No LazyArbitrary
// (-) Has to pass the n parameter to sub arbitraries
// (-) Typings
const lazy = (def, f) => {
  let previous = {};
  return cur => {
    const n = (cur || 0) + 1;
    if (cur >= 5) return def;
    if (!previous.hasOwnProperty(n)) previous[n] = f(n);
    return previous[n];
  };
};
const leaf = () => fc.nat();
const node = lazy(leaf(), n => fc.tuple(fc.oneof(node(n), leaf()), fc.oneof(node(n), leaf())));

src/check/arbitrary/LetRecArbitrary.ts Show resolved Hide resolved
src/check/arbitrary/LetRecArbitrary.ts Outdated Show resolved Hide resolved
src/check/arbitrary/LetRecArbitrary.ts Show resolved Hide resolved
@dubzzz dubzzz merged commit 0740fcf into master Jun 20, 2019
@dubzzz dubzzz deleted the feat/letrec branch June 20, 2019 05:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add easy mutually recursive arbitrary builder
2 participants