From 7851d39d70de03c2fc28f5a46fa444e0e3e4b951 Mon Sep 17 00:00:00 2001 From: milesfrain Date: Mon, 20 Jul 2020 17:08:49 -0700 Subject: [PATCH 01/12] Add more Type Class deriving details From today's conversation on Slack --- language/Type-Classes.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/language/Type-Classes.md b/language/Type-Classes.md index dee9ce57..ee66d66c 100644 --- a/language/Type-Classes.md +++ b/language/Type-Classes.md @@ -167,6 +167,25 @@ instance showPerson :: Show Person where More information on Generic deriving is available [in the generics-rep library documentation](https://pursuit.purescript.org/packages/purescript-generics-rep). +Note that we _could_ write our original example as: +```purs +derive newtype instance eqPerson :: Eq Person +``` +In the case of `Eq`, there’s no difference between `derive instance` and `derive newtype instance` for `newtype`s, but in general `derive newtype instance` only works for `newtype`s whereas `derive instance` works for `data` too. Some more examples: +* `derive instance eqMyType :: Eq MyType` uses the compiler’s built in knowledge of `Eq` to write the instance for you. It works for both `data` and `newtype`, provided that all of the fields have `Eq` instances. Recall the the compiler has built-in knowledge of the classes listed above (`Eq`, `Ord`, etc.). +* `derive instance myClassMyType :: MyClass MyType` is not valid because the compiler does not have built-in knowledge of `MyClass`. Your options are to either: + 1. If `MyType` is a `newtype` of a type with a `MyClass` instance, use the `newtype` keyword: `derive newtype instance myClassMyType :: MyClass MyType`. This reuses the `MyClass` instance for whatever `MyType` is a newtype of, and it works for any class, not just ones with special built-in compiler support. + 2. If `MyClass` has generic members, then you can first derive a generic instance of `MyType` then use it with `MyClass`. For example: + ```purs + derive instance genericMyType :: Generic MyType _ + + instance myClassMyType :: MyClass MyType where + myClassDoesThis = genericMyClassDoesThis + myClassDoesThat = genericMyClassDoesThat + ``` + 3. Write a `GenericMyClass` implementation. This is a nice option if you're publishing a library. + 4. Write the typeclass instance by hand without `derive`. This is likely easiest if you're not planning on creating lots of `MyClass` instances. + ## Compiler-Solvable Type Classes Some type classes can be automatically solved by the PureScript Compiler without requiring you place a PureScript statement, like `derive instance`, in your source code. From 76ec2bb7570b3dba22c18b6d32fd501330979b10 Mon Sep 17 00:00:00 2001 From: milesfrain Date: Mon, 20 Jul 2020 19:55:30 -0700 Subject: [PATCH 02/12] Three categories of Type Class Deriving --- language/Type-Classes.md | 92 ++++++++++++++++++++++++++++------------ 1 file changed, 64 insertions(+), 28 deletions(-) diff --git a/language/Type-Classes.md b/language/Type-Classes.md index ee66d66c..a76d1407 100644 --- a/language/Type-Classes.md +++ b/language/Type-Classes.md @@ -137,54 +137,90 @@ See also the section in [PureScript by Example](https://book.purescript.org/chap ## Type Class Deriving -Some type class instances can be derived automatically by the PureScript compiler. To derive a type class instance, use the `derive instance` keywords: +The compiler can derive type class instances to spare you the tedium of writing boilerplate. There are a few ways to do this depending on the specific Type and Class being derived. -```purescript -newtype Person = Person { name :: String, age :: Int } +### Derive from `newtype` + +If you'd like to use a wrapped type's instance for your newtype, use the `derive newtype` keywords. + +For example, let's say you want to add two `Points` values using the `Semiring` instance of the wrapped `Int`. + +```purs +newtype Points = Points Int + +derive newtype instance semiringPoints :: Semiring Points -derive instance eqPerson :: Eq Person -derive instance ordPerson :: Ord Person +tenPoints :: Points +tenPoints = (Points 4) + (Points 6) ``` -Currently, the following type classes can be derived by the compiler: +That `derive` line replaced all this code!: +```purs +-- No need to write this +instance semiringPoints :: Semiring Points where + zero = Points 0 + add (Points a) (Points b) = Points (a + b) + mul (Points a) (Points b) = Points (a * b) + one = Points 1 +``` + +### Classes with built-in compiler support + +Some classes have special built-in compiler support, and instances can be derived from all types, not just `newtype`s. + +For example, if you you'd like to be able to remove duplicates from an array of an ADT using `nub`, you need an `Eq` and `Ord` instance. Rather than writing these manually, let the compiler do the work. + +```purs +import Data.Array (nub) + +data MyADT + = Some + | Arbitrary Int + | Contents Number String + +derive instance eqMyADT :: Eq MyADT +derive instance ordMyADT :: Ord MyADT + +nub [Some, Arbitrary 1, Some, Some] == [Some, Arbitrary 1] +``` + +Currently, the following type classes can be derived by the compiler: - [Data.Generic.Rep (class Generic)](https://pursuit.purescript.org/packages/purescript-generics-rep/docs/Data.Generic.Rep#t:Generic) - [Data.Eq (class Eq)](https://pursuit.purescript.org/packages/purescript-prelude/docs/Data.Eq#t:Eq) - [Data.Ord (class Ord)](https://pursuit.purescript.org/packages/purescript-prelude/docs/Data.Ord#t:Ord) - [Data.Functor (class Functor)](https://pursuit.purescript.org/packages/purescript-prelude/docs/Data.Functor#t:Functor) - [Data.Newtype (class Newtype)](https://pursuit.purescript.org/packages/purescript-newtype/docs/Data.Newtype#t:Newtype) -Note that `derive instance` is not the only mechanism for allowing you to avoid writing out boilerplate type class instance code. Many type classes not listed here can be derived through other means, such as via a Generic instance. For example, here's how to create a `Show` instance for `Person` via `genericShow`: +Note that with our earlier `Points` `newtype`, we can use either of these options to derive an `Eq` instance. They are equivalent in this case. +``` +derive instance eqPoints :: Eq Points +derive newtype instance eqPoints :: Eq Points +``` + +### Deriving from `Generic` + +The compiler's built-in support for `Generic` unlocks convenient deriving for many other classes not listed above. + +For example, if we wanted a `Show` instance for `MyADT`, it might seem like we're out of luck because neither `MyADT` is a `newtype` nor is `Show` listed as a class with built-in compiler support. + +But we _can_ use `genricShow`, which works with _any_ type that has a `Generic` instance. And recall that the compiler has built-in support for deriving a `Generic` instance for any type (including the `MyADT` type). We put all those pieces together like so: ```purescript import Data.Generic.Rep (class Generic) import Data.Generic.Rep.Show (genericShow) +import Effect.Console (logShow) -derive instance genericPerson :: Generic Person _ +derive instance genericMyADT :: Generic MyADT _ -instance showPerson :: Show Person where +instance showMyADT :: Show MyADT where show = genericShow + +main = logShow [Some, Arbitrary 1, Contents 2.0 "Three"] +-- Prints: +-- [Some,(Arbitrary 1),(Contents 2.0 "Three")] ``` More information on Generic deriving is available [in the generics-rep library documentation](https://pursuit.purescript.org/packages/purescript-generics-rep). - -Note that we _could_ write our original example as: -```purs -derive newtype instance eqPerson :: Eq Person -``` -In the case of `Eq`, there’s no difference between `derive instance` and `derive newtype instance` for `newtype`s, but in general `derive newtype instance` only works for `newtype`s whereas `derive instance` works for `data` too. Some more examples: -* `derive instance eqMyType :: Eq MyType` uses the compiler’s built in knowledge of `Eq` to write the instance for you. It works for both `data` and `newtype`, provided that all of the fields have `Eq` instances. Recall the the compiler has built-in knowledge of the classes listed above (`Eq`, `Ord`, etc.). -* `derive instance myClassMyType :: MyClass MyType` is not valid because the compiler does not have built-in knowledge of `MyClass`. Your options are to either: - 1. If `MyType` is a `newtype` of a type with a `MyClass` instance, use the `newtype` keyword: `derive newtype instance myClassMyType :: MyClass MyType`. This reuses the `MyClass` instance for whatever `MyType` is a newtype of, and it works for any class, not just ones with special built-in compiler support. - 2. If `MyClass` has generic members, then you can first derive a generic instance of `MyType` then use it with `MyClass`. For example: - ```purs - derive instance genericMyType :: Generic MyType _ - - instance myClassMyType :: MyClass MyType where - myClassDoesThis = genericMyClassDoesThis - myClassDoesThat = genericMyClassDoesThat - ``` - 3. Write a `GenericMyClass` implementation. This is a nice option if you're publishing a library. - 4. Write the typeclass instance by hand without `derive`. This is likely easiest if you're not planning on creating lots of `MyClass` instances. ## Compiler-Solvable Type Classes From b0bdb4ad4f48e478f36937502100f9623ae4bc6b Mon Sep 17 00:00:00 2001 From: milesfrain Date: Mon, 20 Jul 2020 19:56:42 -0700 Subject: [PATCH 03/12] Whitespace edit --- 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 a76d1407..247ad963 100644 --- a/language/Type-Classes.md +++ b/language/Type-Classes.md @@ -221,7 +221,7 @@ main = logShow [Some, Arbitrary 1, Contents 2.0 "Three"] ``` More information on Generic deriving is available [in the generics-rep library documentation](https://pursuit.purescript.org/packages/purescript-generics-rep). - + ## Compiler-Solvable Type Classes Some type classes can be automatically solved by the PureScript Compiler without requiring you place a PureScript statement, like `derive instance`, in your source code. From 3cc8353345420e8b14b89ed8ba4a2440baae83aa Mon Sep 17 00:00:00 2001 From: milesfrain Date: Wed, 29 Jul 2020 18:55:33 -0700 Subject: [PATCH 04/12] Add info on eta expansion --- language/Type-Classes.md | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/language/Type-Classes.md b/language/Type-Classes.md index 247ad963..def3df22 100644 --- a/language/Type-Classes.md +++ b/language/Type-Classes.md @@ -155,6 +155,7 @@ tenPoints = (Points 4) + (Points 6) ``` That `derive` line replaced all this code!: + ```purs -- No need to write this instance semiringPoints :: Semiring Points where @@ -192,7 +193,8 @@ Currently, the following type classes can be derived by the compiler: - [Data.Newtype (class Newtype)](https://pursuit.purescript.org/packages/purescript-newtype/docs/Data.Newtype#t:Newtype) Note that with our earlier `Points` `newtype`, we can use either of these options to derive an `Eq` instance. They are equivalent in this case. -``` + +```purs derive instance eqPoints :: Eq Points derive newtype instance eqPoints :: Eq Points ``` @@ -222,6 +224,41 @@ main = logShow [Some, Arbitrary 1, Contents 2.0 "Three"] More information on Generic deriving is available [in the generics-rep library documentation](https://pursuit.purescript.org/packages/purescript-generics-rep). +#### Avoiding stack overflow errors with recursive types + +Be careful when using generic functions with recursive data types. The instances _cannot_ be written in point free style: + +```purs +import Data.Generic.Rep (class Generic) +import Data.Generic.Rep.Show (genericShow) +import Effect.Console (logShow) + +data Chain a + = End a + | Link a (Chain a) + +derive instance genericChain :: Generic (Chain a) _ + +instance showChain :: Show a => Show (Chain a) where + show c = genericShow c -- Note the use of the seemingly-unnecessary variable `c` + +main = logShow $ Link 1 $ Link 2 $ End 3 +-- Prints: +-- (Link 1 (Link 2 (End 3))) +``` + +If the instance was written in point free style, then would produce a stack overflow error: + +``` purs +instance showChain :: Show a => Show (Chain a) where + show = genericShow -- This line is problematic + +-- Throws this error: +-- RangeError: Maximum call stack size exceeded +``` + +This technique of undoing point free notation is known as _eta expansion_. + ## Compiler-Solvable Type Classes Some type classes can be automatically solved by the PureScript Compiler without requiring you place a PureScript statement, like `derive instance`, in your source code. From a2c60f6fce6107791321143ed18c106500172d12 Mon Sep 17 00:00:00 2001 From: milesfrain Date: Wed, 19 Aug 2020 16:41:35 -0700 Subject: [PATCH 05/12] Apply review feedback --- language/Type-Classes.md | 98 ++++++++++++++++++++++++++-------------- 1 file changed, 65 insertions(+), 33 deletions(-) diff --git a/language/Type-Classes.md b/language/Type-Classes.md index def3df22..45de2714 100644 --- a/language/Type-Classes.md +++ b/language/Type-Classes.md @@ -137,37 +137,11 @@ See also the section in [PureScript by Example](https://book.purescript.org/chap ## Type Class Deriving -The compiler can derive type class instances to spare you the tedium of writing boilerplate. There are a few ways to do this depending on the specific Type and Class being derived. - -### Derive from `newtype` - -If you'd like to use a wrapped type's instance for your newtype, use the `derive newtype` keywords. - -For example, let's say you want to add two `Points` values using the `Semiring` instance of the wrapped `Int`. - -```purs -newtype Points = Points Int - -derive newtype instance semiringPoints :: Semiring Points - -tenPoints :: Points -tenPoints = (Points 4) + (Points 6) -``` - -That `derive` line replaced all this code!: - -```purs --- No need to write this -instance semiringPoints :: Semiring Points where - zero = Points 0 - add (Points a) (Points b) = Points (a + b) - mul (Points a) (Points b) = Points (a * b) - one = Points 1 -``` +The compiler can derive type class instances to spare you the tedium of writing boilerplate. There are a few ways to do this depending on the specific type and class being derived. ### Classes with built-in compiler support -Some classes have special built-in compiler support, and instances can be derived from all types, not just `newtype`s. +Some classes have special built-in compiler support, and their instances can be derived from all types. For example, if you you'd like to be able to remove duplicates from an array of an ADT using `nub`, you need an `Eq` and `Ord` instance. Rather than writing these manually, let the compiler do the work. @@ -185,14 +159,40 @@ derive instance ordMyADT :: Ord MyADT nub [Some, Arbitrary 1, Some, Some] == [Some, Arbitrary 1] ``` -Currently, the following type classes can be derived by the compiler: +Currently, instances for the following classes can be derived by the compiler: - [Data.Generic.Rep (class Generic)](https://pursuit.purescript.org/packages/purescript-generics-rep/docs/Data.Generic.Rep#t:Generic) - [Data.Eq (class Eq)](https://pursuit.purescript.org/packages/purescript-prelude/docs/Data.Eq#t:Eq) - [Data.Ord (class Ord)](https://pursuit.purescript.org/packages/purescript-prelude/docs/Data.Ord#t:Ord) - [Data.Functor (class Functor)](https://pursuit.purescript.org/packages/purescript-prelude/docs/Data.Functor#t:Functor) - [Data.Newtype (class Newtype)](https://pursuit.purescript.org/packages/purescript-newtype/docs/Data.Newtype#t:Newtype) -Note that with our earlier `Points` `newtype`, we can use either of these options to derive an `Eq` instance. They are equivalent in this case. +### Derive from `newtype` + +If you would like your newtype to defer to the instance that the underlying type uses for a given class, then you can use newtype deriving via the `derive newtype` keywords. + +For example, let's say you want to add two `Points` values using the `Semiring` instance of the wrapped `Int`. + +```purs +newtype Points = Points Int + +derive newtype instance semiringPoints :: Semiring Points + +tenPoints :: Points +tenPoints = (Points 4) + (Points 6) +``` + +That `derive` line replaced all this code!: + +```purs +-- No need to write this +instance semiringPoints :: Semiring Points where + zero = Points 0 + add (Points a) (Points b) = Points (a + b) + mul (Points a) (Points b) = Points (a * b) + one = Points 1 +``` + +Note that we can use either of these options to derive an `Eq` instance for a `newtype`, since `Eq` has built-in compiler support. They are equivalent in this case. ```purs derive instance eqPoints :: Eq Points @@ -205,7 +205,7 @@ The compiler's built-in support for `Generic` unlocks convenient deriving for ma For example, if we wanted a `Show` instance for `MyADT`, it might seem like we're out of luck because neither `MyADT` is a `newtype` nor is `Show` listed as a class with built-in compiler support. -But we _can_ use `genricShow`, which works with _any_ type that has a `Generic` instance. And recall that the compiler has built-in support for deriving a `Generic` instance for any type (including the `MyADT` type). We put all those pieces together like so: +But we _can_ use `genericShow`, which works with _any_ type that has a `Generic` instance. And recall that the compiler has built-in support for deriving a `Generic` instance for any type (including the `MyADT` type). We put all those pieces together like so: ```purescript import Data.Generic.Rep (class Generic) @@ -222,11 +222,43 @@ main = logShow [Some, Arbitrary 1, Contents 2.0 "Three"] -- [Some,(Arbitrary 1),(Contents 2.0 "Three")] ``` -More information on Generic deriving is available [in the generics-rep library documentation](https://pursuit.purescript.org/packages/purescript-generics-rep). +Note that the `show` output string can be copy-pasted to reconstruct the original data. This is a good oportunity to emphasize how `newtype` deriving simply reuses the instance of the underlying type. In the case of `show`, this means omitting printing the wrapper. + +```purs +import Effect.Console (logShow) + +newtype Points = Points Int + +-- newtype deriving omits wrapper with show +derive newtype instance showPoints :: Show Points + +main = logShow (Points 5) +-- Prints: +-- 5 +``` + +```purs +import Data.Generic.Rep (class Generic) +import Data.Generic.Rep.Show (genericShow) +import Effect.Console (logShow) + +newtype Points = Points Int + +-- generic deriving prints wrapper with show +derive instance genericPoints :: Generic Points _ +instance showPoints :: Show Points where + show = genericShow + +main = logShow (Points 5) +-- Prints: +-- (Points 5) +``` + +More information on Generic deriving is available [in the generics-rep library documentation](https://pursuit.purescript.org/packages/purescript-generics-rep). See this [blog post](https://harry.garrood.me/blog/write-your-own-generics/) for a tutorial on how to write your own `generic` functions. #### Avoiding stack overflow errors with recursive types -Be careful when using generic functions with recursive data types. The instances _cannot_ be written in point free style: +Be careful when using generic functions with recursive data types. Due to strictness, these instances _cannot_ be written in point free style: ```purs import Data.Generic.Rep (class Generic) From 849e6a6d46e6c044bbfc3a55e3746fa98a057875 Mon Sep 17 00:00:00 2001 From: milesfrain Date: Wed, 19 Aug 2020 16:45:16 -0700 Subject: [PATCH 06/12] Rename Points to Score Points suggests a collection of {x,y} values. --- language/Type-Classes.md | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/language/Type-Classes.md b/language/Type-Classes.md index 45de2714..4b5c22fa 100644 --- a/language/Type-Classes.md +++ b/language/Type-Classes.md @@ -170,33 +170,33 @@ Currently, instances for the following classes can be derived by the compiler: If you would like your newtype to defer to the instance that the underlying type uses for a given class, then you can use newtype deriving via the `derive newtype` keywords. -For example, let's say you want to add two `Points` values using the `Semiring` instance of the wrapped `Int`. +For example, let's say you want to add two `Score` values using the `Semiring` instance of the wrapped `Int`. ```purs -newtype Points = Points Int +newtype Score = Score Int -derive newtype instance semiringPoints :: Semiring Points +derive newtype instance semiringScore :: Semiring Score -tenPoints :: Points -tenPoints = (Points 4) + (Points 6) +tenPoints :: Score +tenPoints = (Score 4) + (Score 6) ``` That `derive` line replaced all this code!: ```purs -- No need to write this -instance semiringPoints :: Semiring Points where - zero = Points 0 - add (Points a) (Points b) = Points (a + b) - mul (Points a) (Points b) = Points (a * b) - one = Points 1 +instance semiringScore :: Semiring Score where + zero = Score 0 + add (Score a) (Score b) = Score (a + b) + mul (Score a) (Score b) = Score (a * b) + one = Score 1 ``` Note that we can use either of these options to derive an `Eq` instance for a `newtype`, since `Eq` has built-in compiler support. They are equivalent in this case. ```purs -derive instance eqPoints :: Eq Points -derive newtype instance eqPoints :: Eq Points +derive instance eqScore :: Eq Score +derive newtype instance eqScore :: Eq Score ``` ### Deriving from `Generic` @@ -227,12 +227,12 @@ Note that the `show` output string can be copy-pasted to reconstruct the origina ```purs import Effect.Console (logShow) -newtype Points = Points Int +newtype Score = Score Int -- newtype deriving omits wrapper with show -derive newtype instance showPoints :: Show Points +derive newtype instance showScore :: Show Score -main = logShow (Points 5) +main = logShow (Score 5) -- Prints: -- 5 ``` @@ -242,16 +242,16 @@ import Data.Generic.Rep (class Generic) import Data.Generic.Rep.Show (genericShow) import Effect.Console (logShow) -newtype Points = Points Int +newtype Score = Score Int -- generic deriving prints wrapper with show -derive instance genericPoints :: Generic Points _ -instance showPoints :: Show Points where +derive instance genericScore :: Generic Score _ +instance showScore :: Show Score where show = genericShow -main = logShow (Points 5) +main = logShow (Score 5) -- Prints: --- (Points 5) +-- (Score 5) ``` More information on Generic deriving is available [in the generics-rep library documentation](https://pursuit.purescript.org/packages/purescript-generics-rep). See this [blog post](https://harry.garrood.me/blog/write-your-own-generics/) for a tutorial on how to write your own `generic` functions. From 7c3c841ba772ed0ceddac834d6ec6cff414a68c9 Mon Sep 17 00:00:00 2001 From: milesfrain Date: Thu, 20 Aug 2020 13:26:42 -0700 Subject: [PATCH 07/12] Apply additional suggestions --- 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 4b5c22fa..76bf3a9d 100644 --- a/language/Type-Classes.md +++ b/language/Type-Classes.md @@ -203,7 +203,7 @@ derive newtype instance eqScore :: Eq Score The compiler's built-in support for `Generic` unlocks convenient deriving for many other classes not listed above. -For example, if we wanted a `Show` instance for `MyADT`, it might seem like we're out of luck because neither `MyADT` is a `newtype` nor is `Show` listed as a class with built-in compiler support. +For example, if we wanted to derive a `Show` instance for `MyADT` it might seem like we're out of luck: `Show` is not a class with built-in compiler support for deriving and `MyADT` is not a `newtype` (so we can't use newtype deriving). But we _can_ use `genericShow`, which works with _any_ type that has a `Generic` instance. And recall that the compiler has built-in support for deriving a `Generic` instance for any type (including the `MyADT` type). We put all those pieces together like so: From 66fcdc26b48ed42002ae36d63699ef55494f0afd Mon Sep 17 00:00:00 2001 From: milesfrain Date: Thu, 20 Aug 2020 13:27:18 -0700 Subject: [PATCH 08/12] Apply change suggestions Co-authored-by: Thomas Honeyman --- language/Type-Classes.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/language/Type-Classes.md b/language/Type-Classes.md index 76bf3a9d..b0523757 100644 --- a/language/Type-Classes.md +++ b/language/Type-Classes.md @@ -181,7 +181,7 @@ tenPoints :: Score tenPoints = (Score 4) + (Score 6) ``` -That `derive` line replaced all this code!: +That `derive` line replaced all this code: ```purs -- No need to write this @@ -222,7 +222,9 @@ main = logShow [Some, Arbitrary 1, Contents 2.0 "Three"] -- [Some,(Arbitrary 1),(Contents 2.0 "Three")] ``` -Note that the `show` output string can be copy-pasted to reconstruct the original data. This is a good oportunity to emphasize how `newtype` deriving simply reuses the instance of the underlying type. In the case of `show`, this means omitting printing the wrapper. +The `Show` type class is most often used for debugging data, so the output of most `Show` instances can be copy-pasted back into a PureScript source file to reconstruct the original data. The `Show` instance we created by deriving `Generic` and then using `genericShow` follows this convention. + +This is a good opportunity to emphasize how newtype deriving is different from instances derived by the compiler or through the `Generic` type class. In the examples below, notice how the instance derived through `Generic` includes the newtype constructor `Score`, but the newtype-derived instance simply reuses the underlying `Show` instance for `Int` and therefore does not include the constructor: ```purs import Effect.Console (logShow) From bb30b1514c406ee24eb6796d86ba282de323c4b7 Mon Sep 17 00:00:00 2001 From: milesfrain Date: Sat, 5 Sep 2020 21:41:52 -0700 Subject: [PATCH 09/12] Remove guide on how to avoid stack overflow errors with recursive types --- language/Type-Classes.md | 35 ----------------------------------- 1 file changed, 35 deletions(-) diff --git a/language/Type-Classes.md b/language/Type-Classes.md index b0523757..c30ffe54 100644 --- a/language/Type-Classes.md +++ b/language/Type-Classes.md @@ -258,41 +258,6 @@ main = logShow (Score 5) More information on Generic deriving is available [in the generics-rep library documentation](https://pursuit.purescript.org/packages/purescript-generics-rep). See this [blog post](https://harry.garrood.me/blog/write-your-own-generics/) for a tutorial on how to write your own `generic` functions. -#### Avoiding stack overflow errors with recursive types - -Be careful when using generic functions with recursive data types. Due to strictness, these instances _cannot_ be written in point free style: - -```purs -import Data.Generic.Rep (class Generic) -import Data.Generic.Rep.Show (genericShow) -import Effect.Console (logShow) - -data Chain a - = End a - | Link a (Chain a) - -derive instance genericChain :: Generic (Chain a) _ - -instance showChain :: Show a => Show (Chain a) where - show c = genericShow c -- Note the use of the seemingly-unnecessary variable `c` - -main = logShow $ Link 1 $ Link 2 $ End 3 --- Prints: --- (Link 1 (Link 2 (End 3))) -``` - -If the instance was written in point free style, then would produce a stack overflow error: - -``` purs -instance showChain :: Show a => Show (Chain a) where - show = genericShow -- This line is problematic - --- Throws this error: --- RangeError: Maximum call stack size exceeded -``` - -This technique of undoing point free notation is known as _eta expansion_. - ## Compiler-Solvable Type Classes Some type classes can be automatically solved by the PureScript Compiler without requiring you place a PureScript statement, like `derive instance`, in your source code. From 9409b9a047aaa176199053928177c92739566a72 Mon Sep 17 00:00:00 2001 From: milesfrain Date: Thu, 8 Oct 2020 10:35:42 -0700 Subject: [PATCH 10/12] Remove content on deriving from Generic --- language/Type-Classes.md | 59 ---------------------------------------- 1 file changed, 59 deletions(-) diff --git a/language/Type-Classes.md b/language/Type-Classes.md index c30ffe54..a6ee68c1 100644 --- a/language/Type-Classes.md +++ b/language/Type-Classes.md @@ -199,65 +199,6 @@ derive instance eqScore :: Eq Score derive newtype instance eqScore :: Eq Score ``` -### Deriving from `Generic` - -The compiler's built-in support for `Generic` unlocks convenient deriving for many other classes not listed above. - -For example, if we wanted to derive a `Show` instance for `MyADT` it might seem like we're out of luck: `Show` is not a class with built-in compiler support for deriving and `MyADT` is not a `newtype` (so we can't use newtype deriving). - -But we _can_ use `genericShow`, which works with _any_ type that has a `Generic` instance. And recall that the compiler has built-in support for deriving a `Generic` instance for any type (including the `MyADT` type). We put all those pieces together like so: - -```purescript -import Data.Generic.Rep (class Generic) -import Data.Generic.Rep.Show (genericShow) -import Effect.Console (logShow) - -derive instance genericMyADT :: Generic MyADT _ - -instance showMyADT :: Show MyADT where - show = genericShow - -main = logShow [Some, Arbitrary 1, Contents 2.0 "Three"] --- Prints: --- [Some,(Arbitrary 1),(Contents 2.0 "Three")] -``` - -The `Show` type class is most often used for debugging data, so the output of most `Show` instances can be copy-pasted back into a PureScript source file to reconstruct the original data. The `Show` instance we created by deriving `Generic` and then using `genericShow` follows this convention. - -This is a good opportunity to emphasize how newtype deriving is different from instances derived by the compiler or through the `Generic` type class. In the examples below, notice how the instance derived through `Generic` includes the newtype constructor `Score`, but the newtype-derived instance simply reuses the underlying `Show` instance for `Int` and therefore does not include the constructor: - -```purs -import Effect.Console (logShow) - -newtype Score = Score Int - --- newtype deriving omits wrapper with show -derive newtype instance showScore :: Show Score - -main = logShow (Score 5) --- Prints: --- 5 -``` - -```purs -import Data.Generic.Rep (class Generic) -import Data.Generic.Rep.Show (genericShow) -import Effect.Console (logShow) - -newtype Score = Score Int - --- generic deriving prints wrapper with show -derive instance genericScore :: Generic Score _ -instance showScore :: Show Score where - show = genericShow - -main = logShow (Score 5) --- Prints: --- (Score 5) -``` - -More information on Generic deriving is available [in the generics-rep library documentation](https://pursuit.purescript.org/packages/purescript-generics-rep). See this [blog post](https://harry.garrood.me/blog/write-your-own-generics/) for a tutorial on how to write your own `generic` functions. - ## Compiler-Solvable Type Classes Some type classes can be automatically solved by the PureScript Compiler without requiring you place a PureScript statement, like `derive instance`, in your source code. From f3cddfadc7e4050ae8603588b18e6446e20f3ef4 Mon Sep 17 00:00:00 2001 From: Miles Frain Date: Thu, 5 Nov 2020 11:31:09 -0800 Subject: [PATCH 11/12] Restore deriving from generic as a separate guide --- guides/Type-Class-Deriving.md | 158 ++++++++++++++++++++++++++++++++++ language/Type-Classes.md | 4 + 2 files changed, 162 insertions(+) create mode 100644 guides/Type-Class-Deriving.md diff --git a/guides/Type-Class-Deriving.md b/guides/Type-Class-Deriving.md new file mode 100644 index 00000000..c5a9ca2b --- /dev/null +++ b/guides/Type-Class-Deriving.md @@ -0,0 +1,158 @@ +## Type Class Deriving + +The compiler can derive type class instances to spare you the tedium of writing boilerplate. There are a few ways to do this depending on the specific type and class being derived. + +### Classes with built-in compiler support + +Some classes have special built-in compiler support, and their instances can be derived from all types. + +For example, if you you'd like to be able to remove duplicates from an array of an ADT using `nub`, you need an `Eq` and `Ord` instance. Rather than writing these manually, let the compiler do the work. + +```purs +import Data.Array (nub) + +data MyADT + = Some + | Arbitrary Int + | Contents Number String + +derive instance eqMyADT :: Eq MyADT +derive instance ordMyADT :: Ord MyADT + +nub [Some, Arbitrary 1, Some, Some] == [Some, Arbitrary 1] +``` + +Currently, instances for the following classes can be derived by the compiler: +- [Data.Generic.Rep (class Generic)](https://pursuit.purescript.org/packages/purescript-generics-rep/docs/Data.Generic.Rep#t:Generic) +- [Data.Eq (class Eq)](https://pursuit.purescript.org/packages/purescript-prelude/docs/Data.Eq#t:Eq) +- [Data.Ord (class Ord)](https://pursuit.purescript.org/packages/purescript-prelude/docs/Data.Ord#t:Ord) +- [Data.Functor (class Functor)](https://pursuit.purescript.org/packages/purescript-prelude/docs/Data.Functor#t:Functor) +- [Data.Newtype (class Newtype)](https://pursuit.purescript.org/packages/purescript-newtype/docs/Data.Newtype#t:Newtype) + +### Derive from `newtype` + +If you would like your newtype to defer to the instance that the underlying type uses for a given class, then you can use newtype deriving via the `derive newtype` keywords. + +For example, let's say you want to add two `Score` values using the `Semiring` instance of the wrapped `Int`. + +```purs +newtype Score = Score Int + +derive newtype instance semiringScore :: Semiring Score + +tenPoints :: Score +tenPoints = (Score 4) + (Score 6) +``` + +That `derive` line replaced all this code: + +```purs +-- No need to write this +instance semiringScore :: Semiring Score where + zero = Score 0 + add (Score a) (Score b) = Score (a + b) + mul (Score a) (Score b) = Score (a * b) + one = Score 1 +``` + +Note that we can use either of these options to derive an `Eq` instance for a `newtype`, since `Eq` has built-in compiler support. They are equivalent in this case. + +```purs +derive instance eqScore :: Eq Score +derive newtype instance eqScore :: Eq Score +``` + +### Deriving from `Generic` + +The compiler's built-in support for `Generic` unlocks convenient deriving for many other classes not listed above. + +For example, if we wanted to derive a `Show` instance for `MyADT` it might seem like we're out of luck: `Show` is not a class with built-in compiler support for deriving and `MyADT` is not a `newtype` (so we can't use newtype deriving). + +But we _can_ use `genericShow`, which works with _any_ type that has a `Generic` instance. And recall that the compiler has built-in support for deriving a `Generic` instance for any type (including the `MyADT` type). We put all those pieces together like so: + +```purescript +import Data.Generic.Rep (class Generic) +import Data.Generic.Rep.Show (genericShow) +import Effect.Console (logShow) + +derive instance genericMyADT :: Generic MyADT _ + +instance showMyADT :: Show MyADT where + show = genericShow + +main = logShow [Some, Arbitrary 1, Contents 2.0 "Three"] +-- Prints: +-- [Some,(Arbitrary 1),(Contents 2.0 "Three")] +``` + +The `Show` type class is most often used for debugging data, so the output of most `Show` instances can be copy-pasted back into a PureScript source file to reconstruct the original data. The `Show` instance we created by deriving `Generic` and then using `genericShow` follows this convention. + +This is a good opportunity to emphasize how newtype deriving is different from instances derived by the compiler or through the `Generic` type class. In the examples below, notice how the instance derived through `Generic` includes the newtype constructor `Score`, but the newtype-derived instance simply reuses the underlying `Show` instance for `Int` and therefore does not include the constructor: + +```purs +import Effect.Console (logShow) + +newtype Score = Score Int + +-- newtype deriving omits wrapper with show +derive newtype instance showScore :: Show Score + +main = logShow (Score 5) +-- Prints: +-- 5 +``` + +```purs +import Data.Generic.Rep (class Generic) +import Data.Generic.Rep.Show (genericShow) +import Effect.Console (logShow) + +newtype Score = Score Int + +-- generic deriving prints wrapper with show +derive instance genericScore :: Generic Score _ +instance showScore :: Show Score where + show = genericShow + +main = logShow (Score 5) +-- Prints: +-- (Score 5) +``` + +More information on Generic deriving is available [in the generics-rep library documentation](https://pursuit.purescript.org/packages/purescript-generics-rep). See this [blog post](https://harry.garrood.me/blog/write-your-own-generics/) for a tutorial on how to write your own `generic` functions. + +#### Avoiding stack overflow errors with recursive types + +Be careful when using generic functions with recursive data types. Due to strictness, these instances _cannot_ be written in point free style: + +```purs +import Data.Generic.Rep (class Generic) +import Data.Generic.Rep.Show (genericShow) +import Effect.Console (logShow) + +data Chain a + = End a + | Link a (Chain a) + +derive instance genericChain :: Generic (Chain a) _ + +instance showChain :: Show a => Show (Chain a) where + show c = genericShow c -- Note the use of the seemingly-unnecessary variable `c` + +main = logShow $ Link 1 $ Link 2 $ End 3 +-- Prints: +-- (Link 1 (Link 2 (End 3))) +``` + +If the instance was written in point free style, then would produce a stack overflow error: + +``` purs +instance showChain :: Show a => Show (Chain a) where + show = genericShow -- This line is problematic + +-- Throws this error: +-- RangeError: Maximum call stack size exceeded +``` + +This technique of undoing point free notation is known as _eta expansion_. + diff --git a/language/Type-Classes.md b/language/Type-Classes.md index a6ee68c1..67c90cc3 100644 --- a/language/Type-Classes.md +++ b/language/Type-Classes.md @@ -199,6 +199,10 @@ derive instance eqScore :: Eq Score derive newtype instance eqScore :: Eq Score ``` +### Deriving from `Generic` + +The compiler's built-in support for `Generic` unlocks convenient deriving for many other classes not listed above. See the [deriving guide](../guides/Type-Class-Deriving.md) for more information. + ## Compiler-Solvable Type Classes Some type classes can be automatically solved by the PureScript Compiler without requiring you place a PureScript statement, like `derive instance`, in your source code. From 2a5e598c5afee29c7ef98f774965e3f6956cac76 Mon Sep 17 00:00:00 2001 From: milesfrain Date: Thu, 5 Nov 2020 11:36:28 -0800 Subject: [PATCH 12/12] Link to generic section of deriving guide --- 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 67c90cc3..8410dfba 100644 --- a/language/Type-Classes.md +++ b/language/Type-Classes.md @@ -201,7 +201,7 @@ derive newtype instance eqScore :: Eq Score ### Deriving from `Generic` -The compiler's built-in support for `Generic` unlocks convenient deriving for many other classes not listed above. See the [deriving guide](../guides/Type-Class-Deriving.md) for more information. +The compiler's built-in support for `Generic` unlocks convenient deriving for many other classes not listed above. See the [deriving guide](../guides/Type-Class-Deriving.md#deriving-from-generic) for more information. ## Compiler-Solvable Type Classes