Having a connection between kind * and kind * -> *

I'm trying to update container-classes to duplicate the pre-existing classes defined in the Prelude (Functor, etc.) and am trying to get my approach on how to have functions/classes that work on types of kind * (e.g. Bytestring) as well as kind * -> * (e.g. lists), as my previous approach didn't work. To define the connection, I've copied the style set out by Ganesh's rmonad ( http://hackage.haskell.org/package/rmonad ) package: ,---- | -- | Indicates what kind of value may be stored within a type. Once | -- superclass constraints are available, the @v@ parameter will | -- become an associated type. | class Stores c v | c -> v | | data family Constraints :: (* -> *) -> * -> * | | class (Stores (c v) v) => Suitable c v where | constraints :: Constraints c v `---- This works. However, what doesn't work is when I try to use these classes to re-define Functor: ,---- | class (Stores c v) => Mappable c v where | rigidMap :: (v -> v) -> c -> c | | class (Mappable (c v) v) => Functor c where | fmap :: (Suitable c a, Suitable c b) => (a -> b) -> c a -> c b `---- GHC doesn't like this definition of Functor; the only way to get it to work is to make `v' a parameter of the Functor class as well, even though it doesn't actually get used. Why is this? Is there any way around it? (Limitations like this are making me more and more consider dumping this whole concept as it's just becoming a pain, but more importantly rather boring :s) -- Ivan Lazar Miljenovic Ivan.Miljenovic@gmail.com IvanMiljenovic.wordpress.com

Ivan Lazar Miljenovic wrote:
I'm trying to update container-classes to duplicate the pre-existing classes defined in the Prelude (Functor, etc.) and am trying to get my approach on how to have functions/classes that work on types of kind * (e.g. Bytestring) as well as kind * -> * (e.g. lists), as my previous approach didn't work.
To define the connection, I've copied the style set out by Ganesh's rmonad ( http://hackage.haskell.org/package/rmonad ) package:
,---- | -- | Indicates what kind of value may be stored within a type. Once | -- superclass constraints are available, the @v@ parameter will | -- become an associated type.
The comment above tells enough.
| class Stores c v | c -> v | | data family Constraints :: (* -> *) -> * -> * | | class (Stores (c v) v) => Suitable c v where | constraints :: Constraints c v `----
This works. However, what doesn't work is when I try to use these classes to re-define Functor:
,---- | class (Stores c v) => Mappable c v where
And where is functional dependency?
| rigidMap :: (v -> v) -> c -> c | | class (Mappable (c v) v) => Functor c where | fmap :: (Suitable c a, Suitable c b) => (a -> b) -> c a -> c b `----
GHC doesn't like this definition of Functor; the only way to get it to work is to make `v' a parameter of the Functor class as well, even though it doesn't actually get used.
Why is this? Is there any way around it?
(Limitations like this are making me more and more consider dumping this whole concept as it's just becoming a pain, but more importantly rather boring :s)

Miguel Mitrofanov
Ivan Lazar Miljenovic wrote:
I'm trying to update container-classes to duplicate the pre-existing classes defined in the Prelude (Functor, etc.) and am trying to get my approach on how to have functions/classes that work on types of kind * (e.g. Bytestring) as well as kind * -> * (e.g. lists), as my previous approach didn't work.
To define the connection, I've copied the style set out by Ganesh's rmonad ( http://hackage.haskell.org/package/rmonad ) package:
,---- | -- | Indicates what kind of value may be stored within a type. Once | -- superclass constraints are available, the @v@ parameter will | -- become an associated type.
The comment above tells enough.
About what? The point of the Stores class is that I'm aiming at only having to explicitly state the constraint (in this case the functional dependency) once for all associated classes.
| class Stores c v | c -> v | | data family Constraints :: (* -> *) -> * -> * | | class (Stores (c v) v) => Suitable c v where | constraints :: Constraints c v `----
This works. However, what doesn't work is when I try to use these classes to re-define Functor:
,---- | class (Stores c v) => Mappable c v where
And where is functional dependency?
In Stores. If I put an explicit Functional Dependency here as well, it doesn't make a difference.
| rigidMap :: (v -> v) -> c -> c | | class (Mappable (c v) v) => Functor c where | fmap :: (Suitable c a, Suitable c b) => (a -> b) -> c a -> c b `----
GHC doesn't like this definition of Functor; the only way to get it to work is to make `v' a parameter of the Functor class as well, even though it doesn't actually get used.
Why is this? Is there any way around it?
(Limitations like this are making me more and more consider dumping this whole concept as it's just becoming a pain, but more importantly rather boring :s)
-- Ivan Lazar Miljenovic Ivan.Miljenovic@gmail.com IvanMiljenovic.wordpress.com

On 19/08/10 14:26, Ivan Lazar Miljenovic wrote:
,---- | -- | Indicates what kind of value may be stored within a type. Once | -- superclass constraints are available, the @v@ parameter will | -- become an associated type. | class Stores c v | c -> v | | data family Constraints :: (* -> *) -> * -> * | | class (Stores (c v) v) => Suitable c v where | constraints :: Constraints c v `----
This works. However, what doesn't work is when I try to use these classes to re-define Functor:
,---- | class (Stores c v) => Mappable c v where | rigidMap :: (v -> v) -> c -> c | | class (Mappable (c v) v) => Functor c where | fmap :: (Suitable c a, Suitable c b) => (a -> b) -> c a -> c b `----
I'm no typing expert... but how do you expect the type-checker to be able to infer a type for 'v' here? Even with the fundep from Stores, 'c v' should determine 'v', but what value is the type-checker going to pick for 'v'? It's not mentioned again anywhere else, and it is needed in order to pick the Mappable instance. What would you do with the Mappable instance anyway? You can only use it if type 'a' is the same as type 'b' (and then I think *this* is type you'd want for 'v'), and I don't see that any of your other constraints or machinery can help you to identify when this is the case. Hope that helps your thinking, Neil.

Neil Brown
On 19/08/10 14:26, Ivan Lazar Miljenovic wrote:
,---- | -- | Indicates what kind of value may be stored within a type. Once | -- superclass constraints are available, the @v@ parameter will | -- become an associated type. | class Stores c v | c -> v | | data family Constraints :: (* -> *) -> * -> * | | class (Stores (c v) v) => Suitable c v where | constraints :: Constraints c v `----
This works. However, what doesn't work is when I try to use these classes to re-define Functor:
,---- | class (Stores c v) => Mappable c v where | rigidMap :: (v -> v) -> c -> c | | class (Mappable (c v) v) => Functor c where | fmap :: (Suitable c a, Suitable c b) => (a -> b) -> c a -> c b `----
I'm no typing expert... but how do you expect the type-checker to be able to infer a type for 'v' here? Even with the fundep from Stores, c v' should determine 'v', but what value is the type-checker going to pick for 'v'? It's not mentioned again anywhere else, and it is needed in order to pick the Mappable instance.
Oh, duh, I see your point now; maybe I should stop using `c' for both the instance variable of the kind * classes, as well as the instance variable of the kind * -> * classes.
What would you do with the Mappable instance anyway? You can only use it if type 'a' is the same as type 'b' (and then I think *this* is type you'd want for 'v'), and I don't see that any of your other constraints or machinery can help you to identify when this is the case.
I wanted to express that Functor is an extension of Mappable. Obviously it's still possible to make any Functor instance an instance of Mappable as well, but it would be nice state that this should be so explicitly. It isn't really needed in this case, but I was going to split Foldable up into two classes (the bit that requires kind * -> *, and the bit that doesn't). For example, foldr doesn't actually require kind * -> *, but it probably makes more sense for something like foldMap to only apply to values of kind * -> *. -- Ivan Lazar Miljenovic Ivan.Miljenovic@gmail.com IvanMiljenovic.wordpress.com

On Thu, Aug 19, 2010 at 6:26 AM, Ivan Lazar Miljenovic < ivan.miljenovic@gmail.com> wrote:
I'm trying to update container-classes to duplicate the pre-existing classes defined in the Prelude (Functor, etc.) and am trying to get my approach on how to have functions/classes that work on types of kind * (e.g. Bytestring) as well as kind * -> * (e.g. lists), as my previous approach didn't work.
This is exactly why the iteratee library provides a newtype wrapper around ByteString that adds a phantom type. If I recall correctly, it looks like this (and probably adds some derived instances): newtype WrappedByteString a = WBS ByteString Haskell isn't friendly when it comes to kind polymorphism and I've never really seen a "solution" for it that I like. And the above is actually something that may provide a nice addition to ByteString. You could track the encoding in the phantom type, potentially. Jason

Jason Dagit
On Thu, Aug 19, 2010 at 6:26 AM, Ivan Lazar Miljenovic < ivan.miljenovic@gmail.com> wrote:
I'm trying to update container-classes to duplicate the pre-existing classes defined in the Prelude (Functor, etc.) and am trying to get my approach on how to have functions/classes that work on types of kind * (e.g. Bytestring) as well as kind * -> * (e.g. lists), as my previous approach didn't work.
This is exactly why the iteratee library provides a newtype wrapper around ByteString that adds a phantom type.
If I recall correctly, it looks like this (and probably adds some derived instances): newtype WrappedByteString a = WBS ByteString
Haskell isn't friendly when it comes to kind polymorphism and I've never really seen a "solution" for it that I like.
And the above is actually something that may provide a nice addition to ByteString. You could track the encoding in the phantom type, potentially.
Except then the value that it contains is expected to be what that phantom type is, rather than a Word8. As such, the Foldable instance will be wrong, etc. -- Ivan Lazar Miljenovic Ivan.Miljenovic@gmail.com IvanMiljenovic.wordpress.com
participants (4)
-
Ivan Lazar Miljenovic
-
Jason Dagit
-
Miguel Mitrofanov
-
Neil Brown