Skip to content

Explain why Orphan Instances are forbidden #313

New issue

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

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

Already on GitHub? # to your account

Merged
merged 5 commits into from
May 25, 2020

Conversation

milesfrain
Copy link
Contributor

No description provided.

@@ -78,7 +78,7 @@ assert false = fail "Assertion failed"

## Orphan Instances

Type class instances which are defined outside of both the module which defined the class and the module which defined the type are called *orphan instances*. Some programming languages (including Haskell) allow orphan instances with a warning, but in PureScript, they are forbidden. Any attempt to define an orphan instance in PureScript will mean that your program does not pass type checking.
Type class instances which are defined outside of both the module which defined the class and the module which defined the type are called *orphan instances*. Only one instance is allowed per type and class combination. Orphan instances enable duplicate instances to be defined in separate modules. These modules are fine independently, but if both modules are ever imported into the same project, then an instance collision would break the build. Some programming languages (including Haskell) allow orphan instances with a warning, but in PureScript, they are forbidden. Any attempt to define an orphan instance in PureScript will mean that your program does not pass type checking.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added this section:

Only one instance is allowed per type and class combination. Orphan instances enable duplicate instances to be defined in separate modules. These modules are fine independently, but if both modules are ever imported into the same project, then an instance collision would break the build.

The diff makes this difficult to see.

@hdgarrood
Copy link
Collaborator

I think the explanation of why they are forbidden should come after the first paragraph, rather than occur within it; we need to wait until the reader knows what an orphan instance is and what will happen if they try to define one before we try to explain why this happens.

Also, there's more to it than just the issue that the compiler won't know what to do if two different choices for a particular instance are in scope. For example, banning orphan instances gives us global uniqueness of instances: the property that there can only ever exist one instance for a particular class and type (or set of types, for multi-parameter classes). This property is useful in quite a few places; you may have made use of it unknowingly. One example is of Ord-based maps and sets: if it were possible to insert some values into a map using one Ord instance, and then try to retrieve them using a different one, you'd have keys just disappear from your map. Another example is if you had a type class which defined serialization and deserialization operations: global uniqueness of instances means that you can't serialize something with one instance and then try to deserialize it with a different one.

@milesfrain
Copy link
Contributor Author

The line of reasoning that makes most sense for me is:

  1. What can happen without any rules (overlapping imports)
  2. What contributes to this overlapping issue (orphan instances)
  3. What the compiler does when it detects orphan instances (error)

But I adjusted this PR based on your feedback.

* If it chooses to report an error, it means that any pair of modules which define the same orphan instance can never be used together.
* If it arbitrarily picks one, we won't be able to determine whether `2 <> 3` will evaluate to 5 or 6. This can make it very difficult to ensure that your program will behave correctly!

Banning orphan instances also ensures global uniques of instances. Without global uniques, you risk operating on data with incompatible instances in different sections of code. For example, in Ord-based maps and sets, if it were possible to insert some values into a map using one `Ord` instance, and then try to retrieve them using a different `Ord` instance, you'd have keys disappear from your map. Another example is if you had a type class which defined serialization and deserialization operations, you'd be able to serialize something with one instance and then try to deserialize it with a different incompatible instance.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Oh sorry, one last thing - I just noticed "global uniques" versus "global uniqueness". Was that a deliberate change? "global uniqueness" makes more sense to me, personally.

@hdgarrood
Copy link
Collaborator

Thanks - there's one more occurrence of "global uniques" in the following sentence, too.

Copy link
Collaborator

@hdgarrood hdgarrood left a comment

Choose a reason for hiding this comment

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

Thank you! I'll merge this in a day or so unless we get any other review comments.

@hdgarrood hdgarrood merged commit 4071a7a into purescript:master May 25, 2020
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants