From 087d935fd00c58fe92ad848daf9cf1f0ae2f96f8 Mon Sep 17 00:00:00 2001 From: milesfrain Date: Thu, 7 May 2020 18:29:32 -0600 Subject: [PATCH 1/5] Explain why Orphan Instances are forbidden --- language/Type-Classes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/language/Type-Classes.md b/language/Type-Classes.md index 1e5bffc4..9b7e6ab5 100644 --- a/language/Type-Classes.md +++ b/language/Type-Classes.md @@ -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. For example, the `Semigroup` type class is defined in the module `Data.Semigroup`, and the `Int` type is defined in the module `Prim`. If we attempt to define a `Semigroup Int` instance like this: From 86a9154aaf6c89b0a98717e0edaabc39d1557a1f Mon Sep 17 00:00:00 2001 From: milesfrain Date: Sun, 10 May 2020 17:43:58 -0600 Subject: [PATCH 2/5] review feedback --- language/Type-Classes.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/language/Type-Classes.md b/language/Type-Classes.md index 9b7e6ab5..f1fa8f45 100644 --- a/language/Type-Classes.md +++ b/language/Type-Classes.md @@ -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*. 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. +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. For example, the `Semigroup` type class is defined in the module `Data.Semigroup`, and the `Int` type is defined in the module `Prim`. If we attempt to define a `Semigroup Int` instance like this: @@ -106,6 +106,10 @@ instance semigroupAddInt :: Semigroup AddInt where In fact, a type similar to this `AddInt` is provided in `Data.Monoid.Additive`, in the `monoid` package. +The reason why orphan instances are banned is because they can lead to two types of problems: +* Recall that only one instance is allowed per type and class combination in a module. Orphan instances enable duplicate instances to be defined in separate modules without breaking this overlapping instances rule. So these modules are fine independently, but if both modules are ever imported into the same project (for example from separate libraries), then an instance collision would break the build. +* Even if collisions are avoided, the lack of global uniques of instances would allow 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. + For multi-parameter type classes, the orphan instance check requires that the instance is either in the same module as the class, or the same module as at least one of the types occurring in the instance. (TODO: example) ## Functional Dependencies From 565be002b83b6c23993b4ce3b01d5893628a434b Mon Sep 17 00:00:00 2001 From: Miles Frain Date: Wed, 20 May 2020 18:29:03 -0600 Subject: [PATCH 3/5] Review feedback - Add explicit example --- language/Type-Classes.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/language/Type-Classes.md b/language/Type-Classes.md index f1fa8f45..48ad5310 100644 --- a/language/Type-Classes.md +++ b/language/Type-Classes.md @@ -106,9 +106,12 @@ instance semigroupAddInt :: Semigroup AddInt where In fact, a type similar to this `AddInt` is provided in `Data.Monoid.Additive`, in the `monoid` package. -The reason why orphan instances are banned is because they can lead to two types of problems: -* Recall that only one instance is allowed per type and class combination in a module. Orphan instances enable duplicate instances to be defined in separate modules without breaking this overlapping instances rule. So these modules are fine independently, but if both modules are ever imported into the same project (for example from separate libraries), then an instance collision would break the build. -* Even if collisions are avoided, the lack of global uniques of instances would allow 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. +Orphan instances are banned because they can lead to incompatible duplicated instances for the same type and class. For example, suppose two separate modules define an orphan `Semigroup Int` instance, and one of them uses `+` for `append`, whereas the other uses `*`. Now suppose someone writes a third module which imports both of the first two, and that somewhere in that third module we have the expression `2 <> 3`, which calls for a `Semigroup Int` instance. The compiler now has two instances to choose from. What should it do? It could report an error, or it could arbitrarily pick one of the instances. Neither option is particularly appealing: + + * 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. For multi-parameter type classes, the orphan instance check requires that the instance is either in the same module as the class, or the same module as at least one of the types occurring in the instance. (TODO: example) From 8e57ecb4d2f8f9ba5a8b6d135cb75ad055b2cbdd Mon Sep 17 00:00:00 2001 From: milesfrain Date: Wed, 20 May 2020 18:50:11 -0600 Subject: [PATCH 4/5] Fix uniqueness typo --- language/Type-Classes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/language/Type-Classes.md b/language/Type-Classes.md index 48ad5310..fc76c3fa 100644 --- a/language/Type-Classes.md +++ b/language/Type-Classes.md @@ -111,7 +111,7 @@ Orphan instances are banned because they can lead to incompatible duplicated ins * 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. +Banning orphan instances also ensures global uniqueness 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. For multi-parameter type classes, the orphan instance check requires that the instance is either in the same module as the class, or the same module as at least one of the types occurring in the instance. (TODO: example) From f6bda1a57724db40152493cdd268ad0d227933b3 Mon Sep 17 00:00:00 2001 From: milesfrain Date: Wed, 20 May 2020 18:52:13 -0600 Subject: [PATCH 5/5] Fix another uniques --- language/Type-Classes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/language/Type-Classes.md b/language/Type-Classes.md index fc76c3fa..bd80aacb 100644 --- a/language/Type-Classes.md +++ b/language/Type-Classes.md @@ -111,7 +111,7 @@ Orphan instances are banned because they can lead to incompatible duplicated ins * 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 uniqueness 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. +Banning orphan instances also ensures global uniqueness of instances. Without global uniqueness, 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. For multi-parameter type classes, the orphan instance check requires that the instance is either in the same module as the class, or the same module as at least one of the types occurring in the instance. (TODO: example)