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

RFC - Unify signatures accross arbitraries #992

Closed
dubzzz opened this issue Sep 20, 2020 · 3 comments · Fixed by #1080
Closed

RFC - Unify signatures accross arbitraries #992

dubzzz opened this issue Sep 20, 2020 · 3 comments · Fixed by #1080

Comments

@dubzzz
Copy link
Owner

dubzzz commented Sep 20, 2020

💡 Idea

Aim

A single way to customize arbitaries of fast-check.

Contrary to existing: fc.integer(1, 10) (where 1 is for min and 10 for max) vs fc.object({maxKeys: 5}) (and not fc.object(5))

Existing signatures

Today, the signatures of the built-in arbitraries coming with fast-check can be separated into two categories:

  • the ones with overloads
  • the ones with constraints as an objects

For the overloading ones you can think about fc.array. It accepts three main overloads: fc.array(arb), fc.array(maxLength) and fc.array(minLength, maxLength).

For the constraints-based ones, you can think about fc.webUrl. It only has two signatures: fc.webUrl() and fc.webUrl(constraints).

History

Most of the legacy arbitraries have been defined with an overloading strategy.

Over time this strategy proved weak: it did not scale to the increasing number of options required by new arbitraries, it appeared costly to develop (as overloading does not exist in JavaScript) and it was and is ambiguous for users.

Why costly to develop?

Let's suppose we want to develop something like fc.array with the overloads: fc.array(arb), fc.array(maxLength) and fc.array(minLength, maxLength). Our code would be something like:

function array(arb, aLength, bLength) {
  const minLength = bLength !== undefined ? aLength : 0;
  const maxLength = bLength !== undefined ? bLength : aLength !== undefined ? aLength : 10;
  return new ArrayArbitrary(arb, minLength, maxLength);
}

This one is pretty simple as it only exposes two parameters but we already see how complex it can be to just know what would be the minLength or maxLength to apply.

Why ambiguous?

The second main concern is ambiguity. For someone not knowing the API it is not very easy to know what means 2 in the following signatures fc.array(fc.nat(), 2) or fc.array(fc.nat(), 2, 2) or fc.array(fc.nat(), 2, 3).

As a consequence even in the tests of fast-check itself, we have been using fc.array(arb, minLength, maxLength) signature when just wanted to set the maxLength (while we could have used a shorter version: fc.array(arb, maxLength)).

Signatures for fc.set might be even more ambiguous.

Difficult to express everything

The main problem I faced with this API was: how do we let users specify only a minLength for arrays without specifying the maxLength? While keeping the ability to set only the maxLength without the minLength.

In the past, I thought about a placeholder-based system (see #89).

But actually even if this system seems elegant it does not remove the ambiguity of some signatures. Worst it cannot be really applied when the arbitrary comes with a high number of constraints (see fc.object or fc.webUrl for instance).

Target

As a conclusion, while the overloads way proved pretty useful and cool in the past, it seems that it will not be able to cover all the cases.

The aim of this RFC is to reach a single way to build our arbitaries in fast-check, so that whenever you discover a new arbitrary you already knows more or less how it should be called.

If we do a quick tour on existing ones, we can see that arbitraries have the following characteristics in terms of inputs:

  • zero to many compulsory options - fc.anything() has zero compolsary options, fc.array(arb) has one, fc.dictionary(keyArb, valueArb) has two and fc.constantFrom(...args) has many
  • zero to many optional settings

The proposal would be to adopt the following signatures everywhere:
arbitrary(...compulsaryOptions, constraints?)

(1) With maybe a special case (To Be Discussed) when we only have a single constraint, in which case we would have both arbitrary(...compulsaryOptions, constraints?) and arbitrary(...compulsaryOptions, min?) (if our single constraint is min).

(2) With maybe another special case (To Be Discussed): in the case we accept to have a signature containing all the optional parameters in row (if it makes sense) we could have something like: arbitrary(...compulsaryOptions, ...optionalOptions) (with none of them optional in this signature). The main question regarding this option is: does it make sense to specify all optional parameters together? For instance is it frequent in the case of an array to set both a min and a max?

As a consequence, we would have the following signatures:

  • fc.array(arb, {minLength?, maxLength?} = {}) - TBD (2): fc.array(arb, minLength, maxLength)
  • fc.set(arb, {minLength?, maxLength?, compare?} = {}) - TBD (2): fc.array(arb, minLength, maxLength, compare)
  • fc.nat(arb, {max?} = {}) - TBD (1): fc.nat(arb, max)

And depreciate non-compliant ones while providing codemods to convert them easily. While neither (1) nor (2) has been totally accepted or rejected, the documentation of fast-check will show a non recommended - ambiguous message next to all the non compliant signatures (if the constraints-based version is available).

dubzzz added a commit that referenced this issue Sep 20, 2020
Related to #992

For the moment, instead of explicitely depreciating the overloadings for `fc.array`, `fc.set` and more to come. We only mark them as non-recommended.

Two subjects still need to be discussed before going for depreciated flag, see #992 for more details.
dubzzz added a commit that referenced this issue Sep 20, 2020
Related to #992

For the moment, instead of explicitely depreciating the overloads for `fc.array`, `fc.set` and more to come. We only mark them as non-recommended.

Two subjects still need to be discussed before going for depreciated flag, see #992 for more details.
@dubzzz
Copy link
Owner Author

dubzzz commented Sep 20, 2020

Given the target described above, here is the list of arbitraries that would have to be updated. For each of them we give the current set of overloads and the strict destination:

fc.integer (with constraints signature & codemod: ✔✔ since #1076)

Existing signatures:

  • fc.integer()
  • fc.integer(maxValue)
  • fc.integer(minValue, maxValue)

Target:

  • fc.integer()
  • fc.integer({min?, max?})

Signature fc.integer(maxValue):

This signature is ambiguous. By reading fc.integer(1) it is difficult to understand that what we want to generate is a random interger value between -2**31 and 1.

Ccl: should be marked as deprecated

Signature fc.integer(minValue, maxValue):

While this signature might be considered not as self-explanatory as fc.integer({min?, max?}) it has many advantages:

  • really short signature: fc.integer(1, 10) is shorter than fc.integer({min: 1, max: 10})
  • cover a frequent usage: specifying both min and max is not so rare when using integer arbitraries
  • quicker to read

Ccl: should be kept for the moment

fc.nat (with constraints signature & codemod: ✔✔ since #1076)

Existing signatures:

  • fc.nat()
  • fc.nat(maxValue)

Target:

  • fc.nat()
  • fc.nat({max?})

Signature fc.nat(maxValue):

Really useful syntax, it has the same advantages as the one listed for fc.integer(minValue, maxValue):

  • really short signature
  • cover a frequent usage
  • quicker to read

Ccl: should be kept for the moment

fc.float (with constraints signature & codemod: ✔✔ since #1068)

Existing signatures:

  • fc.float()
  • fc.float(maxValue)
  • fc.float(minValue, maxValue)

Target:

  • fc.float()
  • fc.float({min?, max?})

Signatures:

Conclusions are the same than the one for fc.integer:

  • fc.float(maxValue) should be marked as deprecated
  • fc.float(minValue, maxValue) should be kept

Concerning float there is one thing that need to be tackled: default values. For legacy reasons, min is 0 and max is 1. It should not be the case. float as integer should cover the full range by default. Specifying only a min or max should just limit this range to [min, max float] or [min float, max].

As a consequence we will need to change the defaults for float so that the default range is the maximal range of a float.

fc.double (with constraints signature & codemod: ✔✔ since #1068)

Existing signatures:

  • fc.double()
  • fc.double(maxValue)
  • fc.double(minValue, maxValue)

Target:

  • fc.double()
  • fc.double({min?, max?})

Signatures:

Conclusions are the same than the one for fc.integer:

  • fc.double(maxValue) should be marked as deprecated
  • fc.double(minValue, maxValue) should be kept

Concerning double there is one thing that need to be tackled: default values. For legacy reasons, min is 0 and max is 1. It should not be the case. double as integer should cover the full range by default. Specifying only a min or max should just limit this range to [min, max double] or [min double, max].

As a consequence we will need to change the defaults for double so that the default range is the maximal range of a double.

fc.bigInt (with constraints signature & codemod: ✔✔ since #1067)

Existing signatures:

  • fc.bigInt()
  • fc.bigInt(minValue, maxValue)

Target:

  • fc.bigInt()
  • fc.bigInt({min?, max?})

Signature fc.bigInt(minValue, maxValue):

Same conclusion as fc.integer(minValue, maxValue).

Ccl: should be kept for the moment

fc.bigUint (with constraints signature & codemod: ✔✔ since #1067)

Existing signatures:

  • fc.bigUint()
  • fc.bigUint(maxValue)

Target:

  • fc.bigUint()
  • fc.bigUint({max?})

Signature fc.bigUint(maxValue):

Same conclusion as fc.nat(maxValue).

Ccl: should be kept for the moment

fc.*string (with constraints signature & codemod: ✔✔ since #1010)

Existing signatures:

  • fc.string()
  • fc.string(maxLength)
  • fc.string(minLength, maxLength)

Target:

  • fc.string()
  • fc.string({minLength?, maxLength?})
fc.stringOf (with constraints signature & codemod: ✔✔ since #1010)

Existing signatures:

  • fc.stringOf(charArb)
  • fc.stringOf(charArb, maxLength)
  • fc.stringOf(charArb, minLength, maxLength)

Target:

  • fc.stringOf(charArb)
  • fc.stringOf(charArb, {minLength?, maxLength?})
fc.json (with constraints signature & codemod: ✔✔ since #1023)

Existing signatures:

  • fc.json()
  • fc.json(maxDepth)

Target:

  • fc.json()
  • fc.json({maxDepth?})

Signature fc.json(maxDepth):

Ambiguous, not easy to get what means the parameter.

Ccl: should be marked as deprecated

fc.unicodeJson (with constraints signature & codemod: ✔✔ since #1023)

Existing signatures:

  • fc.unicodeJson()
  • fc.unicodeJson(maxDepth)

Target:

  • fc.unicodeJson()
  • fc.unicodeJson({maxDepth?})

Signature fc.unicodeJson(maxDepth):

Ambiguous, not easy to get what means the parameter.

Ccl: should be marked as deprecated

fc.lorem (with constraints signature & codemod: ✔✔ since #1026)

Existing signatures:

  • fc.lorem()
  • fc.lorem(maxWordsCount)
  • fc.lorem(maxCount, sentenceMode)

Target:

  • fc.lorem()
  • fc.lorem({mode?, maxCount?})
fc.option (with constraints signature: ✔ since #401, with codemod: ✔ since #1024)

Existing signatures:

  • fc.option(arb)
  • fc.option(arb, freq)
  • fc.option(arb, {freq?, nil?})

Target:

  • fc.option(arb)
  • fc.option(arb, {freq?, nil?})
fc.array (with constraints signature & codemod: ✔✔ since #986)

Existing signatures:

  • fc.array(arb)
  • fc.array(arb, maxLength)
  • fc.array(arb, minLength, maxLength)

Target:

  • fc.array(arb)
  • fc.array(arb, {minLength?, maxLength?})
fc.set (with constraints signature & codemod: ✔✔ since #988)

Existing signatures:

  • fc.set(arb)
  • fc.set(arb, maxLength)
  • fc.set(arb, minLength, maxLength)
  • fc.set(arb, compare)
  • fc.set(arb, maxLength, compare)
  • fc.set(arb, minLength, maxLength, compare)

Target:

  • fc.set(arb)
  • fc.set(arb, {minLength?, maxLength?, compare?})
fc.subarray (with constraints signature & codemod: ✔✔ since #1011)

Existing signatures:

  • fc.subarray(originalArray)
  • fc.subarray(originalArray, minLength, maxLength)

Target:

  • fc.subarray(originalArray)
  • fc.subarray(originalArray, {minLength?, maxLength?})
fc.shuffledSubarray (with constraints signature & codemod: ✔✔ since #1011)

Existing signatures:

  • fc.shuffledSubarray(originalArray)
  • fc.shuffledSubarray(originalArray, minLength, maxLength)

Target:

  • fc.shuffledSubarray(originalArray)
  • fc.shuffledSubarray(originalArray, {minLength?, maxLength?})
fc.jsonObject (with constraints signature & codemod: ✔✔ since #1023)

Existing signatures:

  • fc.jsonObject()
  • fc.jsonObject(maxDepth)

Target:

  • fc.jsonObject()
  • fc.jsonObject({maxDepth?})

Signature fc.jsonObject(maxDepth):

Ambiguous, not easy to get what means the parameter.

Ccl: should be marked as deprecated

fc.unicodeJsonObject (with constraints signature & codemod: ✔✔ since #1023)

Existing signatures:

  • fc.unicodeJsonObject()
  • fc.unicodeJsonObject(maxDepth)

Target:

  • fc.unicodeJsonObject()
  • fc.unicodeJsonObject({maxDepth?})

Signature fc.unicodeJsonObject(maxDepth):

Ambiguous, not easy to get what means the parameter.

Ccl: should be marked as deprecated

fc.commands (with constraints signature: ✔ since #294, with codemod: ✔ since #1025)

Existing signatures:

  • fc.commands(commandArbs)
  • fc.commands(commandArbs, maxCommands)
  • fc.commands(commandArbs, { disableReplayLog?, maxCommands?, replayPath? })

Target:

  • fc.commands(commandArbs)
  • fc.commands(commandArbs, { disableReplayLog?, maxCommands?, replayPath? })

*with constraints signature: ✔ — an overload based on a constraints-object is already available on master branch
*with codemod: ✔ — a codemod to ease migration towards constraints-object is already available on master branch

@dubzzz
Copy link
Owner Author

dubzzz commented Sep 29, 2020

In order to ease the migration of our users, fast-check will provide a simple to use codemod that should deal with most of the migration on its own: https://github.com/dubzzz/fast-check/tree/master/codemods/unify-signatures

@dubzzz
Copy link
Owner Author

dubzzz commented Oct 19, 2020

For nat and bigUint we will for the moment preserve signatures like fc.nat(max). They seem to be pretty efficient and useful shorthand for users.

Same, for integer, float, double or bigInt we will for the moment preserve signatures like fc.integer(min, max). The syntax is short and pretty efficient. Meanwhile for float for instance it will not offer all the possible options of the arbitrary (coming soon). For instance, the future version of float might have options or flags like step: 1 or noNaN: true. Those options would not be accessible by using such short-hand, but shorthand will just be a syntactic sugar nothing more.

dubzzz added a commit that referenced this issue May 6, 2022
Drop any legacy signatures of `fc.array`as planned in #992.
Any signature marked as deprecated on `fc.array` will be dropped by this PR.

Originally merged as #1494 for alpha versions.
dubzzz added a commit that referenced this issue May 6, 2022
Drop any legacy signatures of `fc.float`/`fc.double` as planned in #992.
Any signature marked as deprecated on `fc.float`/`fc.double` will be dropped by this PR.

Originally merged as #1495 for alpha versions.
dubzzz added a commit that referenced this issue May 6, 2022
Drop any legacy signatures of `fc.string` and related as planned in #992.
Any signature marked as deprecated on `fc.string` and related will be dropped by this PR.

Originally merged as #1496 for alpha versions.
dubzzz added a commit that referenced this issue May 6, 2022
Drop any legacy signatures of `fc.*subarray` as planned in #992.
Any signature marked as deprecated on `fc.*subarray` will be dropped by this PR.

Originally merged as #1505 for alpha versions.
dubzzz added a commit that referenced this issue May 6, 2022
Drop any legacy signatures of `fc.commands` as planned in #992.
Any signature marked as deprecated on `fc.commands` will be dropped by this PR.

Originally merged as #1507 for alpha versions.
dubzzz added a commit that referenced this issue May 6, 2022
Drop any legacy signatures of `fc.option` as planned in #992.
Any signature marked as deprecated on `fc.option` will be dropped by this PR.

Originally merged as #1509 for alpha versions.
dubzzz added a commit that referenced this issue May 6, 2022
Drop any legacy signatures of `fc.json` and related as planned in #992.
Any signature marked as deprecated on `fc.json` and related will be dropped by this PR.

Originally merged as #1508 for alpha versions.
dubzzz added a commit that referenced this issue May 6, 2022
Drop any legacy signatures of `fc.lorem` as planned in #992.
Any signature marked as deprecated on `fc.lorem` will be dropped by this PR.

Originally merged as #1504 for alpha versions.
dubzzz added a commit that referenced this issue May 6, 2022
Drop any legacy signatures of `fc.lorem` as planned in #992.
Any signature marked as deprecated on `fc.lorem` will be dropped by this PR.

Originally merged as #1504 for alpha versions.
dubzzz added a commit that referenced this issue May 6, 2022
Drop any legacy signatures of `fc.array`as planned in #992.
Any signature marked as deprecated on `fc.array` will be dropped by this PR.

Originally merged as #1494 for alpha versions.
dubzzz added a commit that referenced this issue May 6, 2022
Drop any legacy signatures of `fc.string` and related as planned in #992.
Any signature marked as deprecated on `fc.string` and related will be dropped by this PR.

Originally merged as #1496 for alpha versions.
dubzzz added a commit that referenced this issue May 6, 2022
Drop any legacy signatures of `fc.*subarray` as planned in #992.
Any signature marked as deprecated on `fc.*subarray` will be dropped by this PR.

Originally merged as #1505 for alpha versions.
dubzzz added a commit that referenced this issue May 6, 2022
Drop any legacy signatures of `fc.commands` as planned in #992.
Any signature marked as deprecated on `fc.commands` will be dropped by this PR.

Originally merged as #1507 for alpha versions.
dubzzz added a commit that referenced this issue May 6, 2022
Drop any legacy signatures of `fc.option` as planned in #992.
Any signature marked as deprecated on `fc.option` will be dropped by this PR.

Originally merged as #1509 for alpha versions.
dubzzz added a commit that referenced this issue May 6, 2022
Drop any legacy signatures of `fc.json` and related as planned in #992.
Any signature marked as deprecated on `fc.json` and related will be dropped by this PR.

Originally merged as #1508 for alpha versions.
dubzzz added a commit that referenced this issue May 6, 2022
Drop any legacy signatures of `fc.lorem` as planned in #992.
Any signature marked as deprecated on `fc.lorem` will be dropped by this PR.

Originally merged as #1504 for alpha versions.
dubzzz added a commit that referenced this issue May 6, 2022
)

Drop any legacy signatures of `fc.float`/`fc.double` as planned in #992.
Any signature marked as deprecated on `fc.float`/`fc.double` will be dropped by this PR.

Originally merged as #1495 for alpha versions.
dubzzz added a commit that referenced this issue May 6, 2022
Drop any legacy signatures of `fc.integer` as planned in #992.
Any signature marked as deprecated on `fc.integer` will be dropped by this PR.

Originally merged as #1511 for alpha versions.
dubzzz added a commit that referenced this issue May 6, 2022
Drop any legacy signatures of `fc.integer` as planned in #992.
Any signature marked as deprecated on `fc.integer` will be dropped by this PR.

Originally merged as #1511 for alpha versions.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant