I wonder if ithere is a deeper reason that we cannot abstract over type classes (or can we?).
For example, when we have
fzip :: (forall a.[a] -> [a]) -> [b] -> [c] -> [(b,c)]
fzip f xs ys = zip (f xs) (f ys)
then we can say
fzip (drop 42) [1..100] ['a'..'z']
fzip reverse [1..100] ['a'..'z']
and so on. But we cannot
fzip (map succ) [1..100] ['a'..'z']
which we can fix with:
ezip :: (Enum b, Enum c) => (forall a.Enum a => [a] -> [a]) -> [b] -> [c] -> [(b,c)]
ezip f xs ys = zip (f xs) (f ys)
and likewise we can fix
fzip (map (5*)) [1..100] [1.5, 2.3, 4.7]
with
nzip :: (Num b, Num c) => (forall a.Num a => [a] -> [a]) -> [b] -> [c] -> [(b,c)]
nzip f xs ys = zip (f xs) (f ys)
But is it not embarassing that we cannot subsume ezip
and nzip
with something like:
gzip :: (g b, g c) => (forall a. g a => [a] -> [a]) -> [b] -> [c] -> [(b,c)]
though the code is absolutly identical, up to the name of the class? Or can we somehow?
Interestingly, when instances were just records that contained functions, this would be easily possible.
The abstract type is often used as a return type (because you don't want the caller to know what concrete type is returned: it could change later, or could vary based on the arguments of the configuration).
A subclass must override all abstract methods of an abstract class. However, if the subclass is declared abstract, it's not mandatory to override abstract methods.
Abstract class: is a restricted class that cannot be used to create objects (to access it, it must be inherited from another class). Abstract method: can only be used in an abstract class, and it does not have a body. The body is provided by the subclass (inherited from).
In general, a class should be abstract when you have absolutely no reason to create an instance of that class. For example, suppose you have a Shape class that is the superclass of Triangle, Square, Circle, etc.
You can almost do this with ConstraintKinds
:
{-# LANGUAGE ConstraintKinds, RankNTypes #-}
import Data.Proxy
gzip :: (g b, g c) => Proxy g -> (forall a . g a => [a] -> [a]) -> [b] -> [c] -> [(b,c)]
gzip _ f xs ys = zip (f xs) (f ys)
test1 = gzip (Proxy :: Proxy Enum) (map succ) [1 .. 100] ['a' .. 'z']
test2 = gzip (Proxy :: Proxy Num) (map (5*)) [1 .. 100] [1.5, 2.3, 4.7]
The main difference is that you need the Proxy
argument, because GHC is unable to infer the right instantiation for g
without help.
Add a Proxy
argument for the constraint:
{-# LANGUAGE PartialTypeSignatures #-}
import Data.Proxy
gzip :: (g b, g c) => Proxy g -> (forall a. g a => [a] -> [a]) -> [b] -> [c] -> [(b,c)]
gzip _ f bs cs = zip (f bs) (f cs)
> gzip (Proxy :: _ Enum) [0, 1] "ab"
[(1,'b'),(2,'c')]
GHC considers constraint parameters that only occur in constraints ambiguous, so we need to record them explicitly in a Proxy.
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