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

refactor: toBoolean does more; narrower predicates #125

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

kalisjoshua
Copy link
Contributor

the toBoolean function centralizes the logic for coercing a value and allows the predicate functions to be more specific in what they validate rather than them simply being alais names to broad validation.

Closes #122

✅ Pull Request Checklist:

  • Included link to corresponding GitHub Issue.
  • The commit message follows conventional commit extended guidelines.
  • Added/updated unit tests and storybook for this change (for bug fixes / features).
  • Added/updated documentation (for bug fixes / features)
  • Filled out test instructions.

📝 Test Instructions:

❓ Does this PR introduce a breaking change?

  • Yes
  • No

The function toBoolean no longer will consider specific values - 'no', 'off', 'n' to be "false" or 'on', 'yes', 'y' to be "true". If that is being relied on that assumption will break; no tests show this to be extant.

💬 Other information

@kalisjoshua kalisjoshua linked an issue Dec 4, 2024 that may be closed by this pull request
@kalisjoshua kalisjoshua force-pushed the refactor/122-bool-coverter-predicates branch from 46a0902 to a2e1870 Compare December 4, 2024 18:08
the toBoolean function centralizes the logic for coercing a value and allows
the predicate functions to be more specific in what they validate rather than
them simply being alais names to broad validation.

closes #122
@kalisjoshua kalisjoshua force-pushed the refactor/122-bool-coverter-predicates branch from a2e1870 to 7b8e49a Compare December 4, 2024 18:37
@belsrc
Copy link
Contributor

belsrc commented Dec 4, 2024

I don't see them in here, were the benchmarks updated for these changes as well?

@kalisjoshua
Copy link
Contributor Author

I was waiting to update the bench tests till after we agree on a general direction; no need to benchmark something that isn't going to move forward.

return !(
!val ||
`${val}`.trim().toLowerCase() === 'false' ||
Number.parseFloat(val as string) === 0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not wrap it in a string template literal? Since unknown is definitely not string

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you suggesting \${val}`instead of!val`? @ArrayKnight

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think he meant Number.parseFloat(val as string) -> Number.parseFloat(`{val}`)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is why I asked. :)

That makes more sense.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can, and have changed it, but I remember my original reasoning being that as string is only for typescript so it will be removed in production while wrapping in a string template would carry into production.

Copy link
Contributor

@belsrc belsrc Dec 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would agree with that. Number.parseFloat({}) === NaN which doesn't === 0 so the logic checks out. Which I guess a case could be made, are we considering NaN as false?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a good question. I asked myself that as well. What I fell back on is that Javascript considers is to be false so I felt that staying consistent with the language is best.

*/
export const isOff = isFalse;
export const isOff = (val: unknown) => !val || test(['off'], val);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a little bit of a discrepancy between isOff and isOn, where isOff will accept any falsy value || off variants, instead of just false like how isOn only accepts true || on variants

I would expect them to be the same level of rigid/flexible. And I'd expect them both to accept things like 0 or 1 since that's relatively common term for on/off

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point.

@ArrayKnight
Copy link
Contributor

Feels like maybe we also want something that is isYes + isTrue + isOn & isNo + isFalse + isOff, like isTruthy & isFalsy?

@kalisjoshua
Copy link
Contributor Author

kalisjoshua commented Dec 9, 2024

Feels like maybe we also want something that is isYes + isTrue + isOn & isNo + isFalse + isOff, like isTruthy & isFalsy?

The converter toBoolean is the most liberal and could be combined with the more specific calls to gain those benefits I think.

if (toBoolean(val) || isYes(val)) {}

I don't think it is a good idea to include all of the specific cases - yes + true + no or no + off + false - into a single call. My thought is that in cases where the comparison is for "on" or "off" it should not also match "yes" or "no".

I am only able to draw from experience and conjecture without knowing some of the specific scenarios we run into.

@kalisjoshua kalisjoshua closed this Dec 9, 2024
@kalisjoshua kalisjoshua reopened this Dec 9, 2024
@kalisjoshua
Copy link
Contributor Author

I decided to sever the relationship between converters/toBoolean and predicates/is-noyes because I think they are not as compatible as might appear.

First thought, for toBoolean there should only two possible return values: true or false; it should never throw an error or return null or undefined. Therefor it needs to be liberal in it's conversion and that leads to the ethos of, "anything, that is something not explicitly false, is true." The other thought I had along these lines is, "if there is a list for true and list for false and we check against those two lists, then anything not in those lists would be an alternate return value." All of this leads me to believe that there is a small set of "false" values and anything outside of that set is not false, or true; at least for the purposes and domain of this type of function.

Second, for the predicates, toBoolean becomes problematic for truthy values because it's liberal criteria conflicts with the more restrictive ethos of is* functions. For instance because toBoolean('no') will evaluate to true then isYes('no') would evaluate to true without a more complicated check than ['y', 'yes', 1, 'true].includes(val). Also, I felt that it would be better to be consistent within is-noyes function implementations as well as limit interdependencies where not prohibitive.

*/
export const isFalse = (val: unknown) => test(falseValues, val);
export const isFalse = (val: unknown) => test(listFalse, val);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do like the separation of the three checks, although I think in our current use cases of this predicate we want to check against all of these. Would it perhaps make sense to do a isFalsey() function that checks isFalse() || isOff() || isNo() so we can do just a single check in userland (for general use cases)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that a use case? Is a value likely to be "Off" or "No"?

In my experience I would expect a value to be either: "On" or "Off" or true or false but nothing else and another value might be of a different variant.

*/
export function toBoolean(val: unknown) {
return isTrue(val);
return !(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small nit for cleanliness (and any other long term checks we might do):

function notCheck(val) {
  return !val;
}

function stringCheck(val) {
  return string.trim().toLowerCase() === 'false';
}

function numberCheck(val) {
  return Number.parseFloat(`${val}`) === 0;
}

export function toBoolean(val) {
  return !(notCheck(val) || stringCheck(val) || numberCheck(val))
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems verbose for not much value and additional overhead - cognitive and execution - in my opinion. I would more often lean on comments for further documentation of why each exists than additional code to execute.

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.

Evolve toBoolean and isTrue (et.al) implementations
4 participants