I recently came across a situation where I wanted to be able to compose type constructors in an instance declaration. I would have liked to do this:
instance (SomeClass t, SomeClass t') => SomeClass (t . t') where
with (t . t')
defined such that (t . t') a = t (t' a)
(so t
and t'
have kind * -> *
. We can partially apply type constructors, like functions, so what is the reason we cannot compose them? Also, is there perhaps a workaround for what I was trying to achieve? Perhaps with equality constraints?
(I do know that Control.Compose exists, but it simply creates a newtype
wrapper - I would like a type synonym).
In the area of mathematical logic and computer science known as type theory, a type constructor is a feature of a typed formal language that builds new types from old ones. Basic types are considered to be built using nullary type constructors.
Object and Object/Relational Databases Complex objects within the database must be constructed of other objects found within the database. Type constructors are the mechanism for this purpose. The simplest constructors are base or atomic, tuple, and set.
In a data declaration, a type constructor is the thing on the left hand side of the equals sign. The data constructor(s) are the things on the right hand side of the equals sign. You use type constructors where a type is expected, and you use data constructors where a value is expected.
Making your own data type in HaskellNew data types are created via the data keyword. To create a Point data type, we need to provide a type constructor (the name of our type) and a data constructor (used to construct new instances of the type), followed by the types our type will contain.
(I do know that
Control.Compose
exists, but it simply creates anewtype
wrapper - I would like a type synonym).
This is not allowed in Haskell. A type synonym must be fully applied: you can't write Compose t t'
, only Compose t t' a
.
Allowing partially applied type synonyms would lead to type-level lambdas, which makes type inference undecidable, hence the lack of support for it in Haskell.
For instance, (enabling all the relevant GHC extensions)
type Compose t t' a = t (t' a)
data Proxy (k :: * -> *) = Proxy
pr :: Proxy (Compose [] [])
pr = Proxy
results in:
Type synonym ‘Compose’ should have 3 arguments, but has been given 2
In the type signature for ‘pr’: pr :: Proxy (Compose [] [])
Similarly,
class C k where f :: k Int -> Int
instance C (Compose [] []) where f _ = 6
yields:
Type synonym ‘Compose’ should have 3 arguments, but has been given 2
In the instance declaration for ‘C (Compose [] [])’
Here's an example where type synonym partial application is allowed, instead (enabling LiberalTypeSynonyms
):
type T k = k Int
type S = T (Compose [] [])
bar :: S -> S
bar = map (map succ)
Note however that this works only because after synonym expansion we get a fully applied type [] ([] Int)
(i.e., [[Int]]
). Roughly, this feature does not allow to do anything which one could have done without it, manually expanding the synonyms.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With