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

Remove boolean -> nil mutations #1018

Closed
wants to merge 1 commit into from
Closed

Remove boolean -> nil mutations #1018

wants to merge 1 commit into from

Conversation

dgollahon
Copy link
Collaborator

@dgollahon dgollahon commented Aug 23, 2020

Removes the following mutations:

  • true -> nil
  • false -> nil

Rationale:

  • Since true is already mutated to false and vice versa, we already have a truthiness change and nil does not provide anything else in that direction. It is very unlikely to get a mutation from mutating to nil that we do not get from inverting the boolean.
  • nil is actually the less primitive construct and easier to misuse.
    • nil.methods - false.methods # => [:to_a, :to_i, :to_f, :to_h, :to_r, :rationalize, :to_c]
      This allows for more cases where a nil is somehow accidentally turned into an empty hash, array, 0 value, etc.
    • false.methods - nil.methods # => []
    • false.methods == true.methods # => true
  • It forces nil to be present in cases that are extremely hard to assert (inside conditionals that do not return the boolean expression) or, even if they are possible to assert, it is often awkward to do so. As a motivating example I like to use hashes as an asserted case statement (ex: if { 'confirmed' => true, 'denied' => false }.fetch(enum_value) ... but mutant would force me to make the value of the 'denied' key to nil which is not an improvement in semantics or readability and very difficult to assert if the result of that expression is never part of a public API.
  • It also results in fewer mutations being generated which is beneficial for CI runs and faster local feedback loops.
  • This change also has the slight side benefit of removing more code from mutant than it adds.

@mbj Let me know what you think. I figured it was probably easier to just go ahead an open a PR for discussion rather than create an issue since it was a fairly easy change to make.

@mbj
Copy link
Owner

mbj commented Aug 23, 2020

@dgollahon I like the rationale. I'm onboard. Interestingly circleCI sneaked in here again, I'll disable it tomorrow and get this merged.

@dgollahon
Copy link
Collaborator Author

Sounds great, thanks!

Should I add a changelog entry? I don't see an "Unreleased" section but I can add one if you'd like.

@mbj
Copy link
Owner

mbj commented Aug 23, 2020

hould I add a changelog entry? I don't see an "Unreleased" section but I can add one if you'd like.

Feel free to do so. I'm very curious on why CI is not green at this point, will debug this in around 4h, once I've the kids in bed.

Removes the following mutations:
- `true` -> `nil`
- `false` -> `nil`

Rationale:
- Since `true` is already mutated to `false` and vice versa, we already have a truthiness change and `nil` does not provide anything else in that direction. It is very unlikely to get a mutation from mutating to `nil` that we do not get from inverting the boolean.
- `nil` is actually the less primitive construct and easier to misuse.
  - nil.methods - false.methods # => [:to_a, :to_i, :to_f, :to_h, :to_r, :rationalize, :to_c]
    This allows for more cases where a `nil` is somehow accidentally turned into an empty hash, array, 0 value, etc.
  - false.methods - nil.methods # => []
  - false.methods == true.methods # => true
- It forces `nil` to be present in cases that are extremely hard to assert (inside conditionals that do not return the boolean expression) or, even if they are possible to assert, it is often awkward to do so. As a motivating example I like to use hashes as an asserted case statement (ex: `if { 'confirmed' => true, 'denied' => false}.fetch(enum_value) ...` but `mutant` would force me to make the value of the `'denied'` key to `nil` which is not an improvement in semantics or readability and very difficult to assert if the result of that expression is never part of a public API.
- It also results in fewer mutations being generated which is beneficial for CI runs and faster local feedback loops.
- This change also has the slight side benefit of removing more code from `mutant` than it adds.
@dgollahon
Copy link
Collaborator Author

Updated the changelog.

@mbj
Copy link
Owner

mbj commented Aug 23, 2020

To put a bit of a more axiomatic reasoning behind this change:

  • Lets assume a function that currently returns true or false in haskell terms is of type Bool.
  • A mutation to nil would turn it into Maybe Bool.

A mutation of the type signature from Bool to Maybe Bool is adding more possible states to the return value, which is NOT the point of mutation testing (in mutants flavor). Mutnat is about:

  • Semantic reductions (reducing the observable value / semantic space strictly)
  • Orthogonal replacement

The exusting mutaiton from true -> nil and false -> nil violates this property.

@mbj
Copy link
Owner

mbj commented Aug 24, 2020

@dgollahon merged in repush as #1019

@mbj mbj closed this Aug 24, 2020
@dgollahon dgollahon deleted the remove-false-to-nil-mutation branch August 24, 2020 16:36
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.

2 participants