-
Notifications
You must be signed in to change notification settings - Fork 298
Explain rows in typeclass instances #104
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
Changes from 6 commits
44b40c6
a7477e5
1e28e7d
e616185
7bcb558
3bb61e2
40d4abc
f64b4a1
81699b1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,7 +6,7 @@ Types appearing in class instances are must be of the form `String`, `Number`, ` | |
|
||
Type class instances are resolved based on the order in which they appeared in the source files. Overlapping instances are currently permitted but not recommended. In simple cases the compiler will display a warning and list the instances it found and which was chosen. In the future they may be disallowed completely. | ||
|
||
Here is an example of the `Show` typeclass, with instances for `String`, `Boolean` and `Array`: | ||
Here is an example of the `Show` type class, with instances for `String`, `Boolean` and `Array`: | ||
|
||
```purescript | ||
class Show a where | ||
|
@@ -25,6 +25,28 @@ instance showArray :: (Show a) => Show (Array a) where | |
example = show [true, false] | ||
``` | ||
|
||
## Instance selection | ||
|
||
Instance selection works by looking at the "head" types of each type class parameter. Functional dependencies also plays a role, but we'll not consider that for now to simplify the explanation. The "head" of a type is the top-most type constructor, for example the head of `Array Int` is `Array` and the head of `Record r` is `Record`. | ||
|
||
``` purescript | ||
class C t where | ||
f :: t -> String | ||
-- Instances are chosen | ||
instance cRec :: C (Record r) where | ||
f _ = "this is a record" | ||
instance cArr :: C (Array a) where | ||
f _ = "this is an array" | ||
|
||
main = do | ||
-- The `f` function resolves to the instance for the argument type's head. | ||
log (f {}) -- Resolves to cRec's f. | ||
log (f []) -- Resolves to cArr's f. | ||
-- Prints the following when executed. | ||
this is a record | ||
this is an array | ||
``` | ||
|
||
## Multi-Parameter Type Classes | ||
|
||
TODO. For now, see the section in [PureScript by Example](https://leanpub.com/purescript/read#leanpub-auto-multi-parameter-type-classes). | ||
|
@@ -80,6 +102,63 @@ In fact, a type similar to this `AddInt` is provided in `Data.Monoid.Additive`, | |
|
||
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) | ||
|
||
## Kinds | ||
|
||
Type class parameters do not need to be of kind Type. To explicitly specify a different kind, the parameter can be annotated with a kind signature. Unless the kind of a type parameter is explicitly annotated, it will be implicitly inferred based on how the type parameter is used in the type class's method signatures. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's much more common that the kind of a type class parameter can be inferred from the methods than not, and so I think it would make more sense to talk about how the kind can be inferred before mentioning that a kind annotation can be provided. Also I think the "implicitly" is redundant here (is there such a thing as "explicit inference")? |
||
|
||
``` purescript | ||
class A a | ||
-- with a kind signature or methods, `a` defaults to kind `Type`. | ||
class B (b :: Type) | ||
-- `b` is specified as kind `Type`, so its instances must also be this kind. | ||
class C c | ||
f :: forall a b. (a -> b) -> c a -> c b | ||
-- `c`'s kind is unspecified, so it is inferred to be `Type -> Type` by its method `f`. | ||
class D (d :: Type -> Type) | ||
-- `D` has no methods, but we can still declare it to apply to types of kind `Type -> Type`. | ||
class E (e :: # Type) | ||
-- Row is its own kind, so we can specify a class to operate on types of rows, `# Type`. | ||
``` | ||
|
||
The kinds of any types mentioned in an instance declaration must match the kinds of those parameters as determined by the type class's definition. | ||
|
||
For example: | ||
|
||
``` purescript | ||
-- Functor can only be considered for types of kind `Type -> Type`. | ||
class Functor f where | ||
map :: forall a b. (a -> b) -> f a -> f b | ||
-- Either is kind `Type -> Type -> Type`. | ||
data Either a b = Left a | Right b | ||
-- This means we can't write an instance of Functor for Either. | ||
`instance functorEither :: Functor Either where ...` -- compile error | ||
-- To make Either into kind `Type -> Type`, we simply include the first type parameter. | ||
`instance functorEither :: Functor (Either e) where ...` | ||
``` | ||
|
||
An effect of needing to adjust the kind of a data type to fit into a type class which applies to a lower arity kind, like with `Functor` and `Either` above, is that the behavior and laws of an instance will bias towards to the right-most type parameters. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure about this sentence; I think it will only make sense to people who already understand what it's trying to explain. This topic is definitely worth addressing but I think we should do that separately from this PR. |
||
|
||
## Rows | ||
|
||
Concrete Row literals cannot appear in instances unless it's concrete type is fully determined by functional dependencie 9E61 s. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's not quite clear what "it's" refers to here. Also "it's" means "it is"; for something belonging to "it" you should use "its". |
||
|
||
``` purescript | ||
class M (t :: # Type) | ||
-- Cannot write an instance for a specific row. | ||
instance mClosedRow :: M () | ||
-- Can write an instance for a generic row. | ||
instance mAnyRow :: M r | ||
|
||
-- The row's type is determined by the type of `dt`. | ||
class Ctors dt (ct :: # Type) | dt -> ct | ||
-- Can use a row literal in instances of this class. | ||
data X = X0 Int | X1 String | ||
instance ctorsX :: Ctors X ("X0" :: Int, "X1" :: String) | ||
data Y = Y0 Boolean | ||
instance ctorsY :: Ctors Y ("Y0" :: Boolean) | ||
``` | ||
|
||
A reason for rows being disallowed from appearing in instances is that PureScript doesn't allow orphan instances. Row has an unbounded/open set of labels and combination of labels and types. Each unique row has a unique type. A unique row value's type doesn't have a name in a module, so to avoid orphan instances, its instance of a class would need to be defined in the same module as the type class. | ||
|
||
## Functional Dependencies | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is right. For instance:
The compiler has had to look beyond the head of each instance to select it here.